Cédric Bosdonnat
2017-Mar-07 14:26 UTC
[Libguestfs] [PATCH v4 0/9] Introducing virt-builder-repository
Hi all, Here is a v4 of my series. It includes the changes according to Pino and Richard's comments. However, the perrorf/debug problem is addressed differently: instead of adding an implementation for the internal function names when building for mllib, I redefine these macros. Obviously this is not perfect, but at least easier to understand. Pino's comment about the Notes regex wasn't quite right... thus I added a unit test for the Index_parser.write_entry As some more code has been moved around, the number of commits in the series increased a little bit ;) Cédric Bosdonnat (9): lib/osinfo.c: Extract xml processing into a callback lib: extract osinfo DB traversing API mllib: ocaml wrapper for lib/osinfo builder: rename docs test script builder: add Index_parser.write_entry function builder: add a template parameter to get_index dib: move do_cp to mllib.Commun_utils mllib: add do_mv helper function to Common_utils Add a virt-builder-repository tool .gitignore | 4 + builder/Makefile.am | 121 +++++- builder/builder.ml | 2 +- builder/index.mli | 3 + builder/index_parser.ml | 72 +++- builder/index_parser.mli | 8 +- builder/index_parser_tests.ml | 95 ++++ builder/repository_main.ml | 466 ++++++++++++++++++++ .../{test-virt-builder-docs.sh => test-docs.sh} | 2 + builder/virt-builder-repository.pod | 183 ++++++++ dib/utils.ml | 4 - lib/Makefile.am | 2 + lib/osinfo-iso.c | 462 ++++++++++++++++++++ lib/osinfo.c | 476 ++------------------- lib/osinfo.h | 27 ++ mllib/Makefile.am | 11 +- mllib/common_utils.ml | 11 + mllib/common_utils.mli | 6 + mllib/osinfo-c.c | 100 +++++ mllib/osinfo.ml | 26 ++ mllib/osinfo.mli | 31 ++ 21 files changed, 1654 insertions(+), 458 deletions(-) create mode 100644 builder/index_parser_tests.ml create mode 100644 builder/repository_main.ml rename builder/{test-virt-builder-docs.sh => test-docs.sh} (93%) create mode 100644 builder/virt-builder-repository.pod 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 -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:26 UTC
[Libguestfs] [PATCH v4 1/9] lib/osinfo.c: Extract xml processing into a callback
In order to further reuse the osinfo database parsing in OCAML, this commit extracts the XML processing for the distro ISOs and places it into a newly created callback. This will later help other code to traverse the osinfo DB files and let them extract what they need from them. --- lib/osinfo.c | 85 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/lib/osinfo.c b/lib/osinfo.c index ea2a7659a..121fac1ba 100644 --- a/lib/osinfo.c +++ b/lib/osinfo.c @@ -52,6 +52,7 @@ #include <string.h> #include <unistd.h> #include <dirent.h> +#include <errno.h> #include <assert.h> #include <sys/types.h> #include <libintl.h> @@ -71,10 +72,14 @@ 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 *); - #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 *); /* 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 @@ -87,14 +92,24 @@ static void free_osinfo_db_entry (struct osinfo *); */ int guestfs_int_osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, - const struct osinfo **osinfo_ret) + 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) { + 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; } @@ -156,19 +171,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); -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_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 (guestfs_h *g) +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 +195,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 +261,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 +291,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 +314,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; } @@ -348,7 +351,7 @@ static int read_os_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, xmlNodePtr o * Only memory allocation failures are fatal errors here. */ static int -read_osinfo_db_xml (guestfs_h *g, const char *pathname) +read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *opaque) { CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL; CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL; -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:26 UTC
[Libguestfs] [PATCH v4 2/9] lib: extract osinfo DB traversing API
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 | 462 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/osinfo.c | 422 +------------------------------------------------- lib/osinfo.h | 27 ++++ 4 files changed, 493 insertions(+), 420 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..059d72def --- /dev/null +++ b/lib/osinfo-iso.c @@ -0,0 +1,462 @@ +/* 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 121fac1ba..11b50d903 100644 --- a/lib/osinfo.c +++ b/lib/osinfo.c @@ -29,20 +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> @@ -58,104 +44,14 @@ #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 *); - -/* 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"); +#include "osinfo.h" - return 0; -} /* Read the libosinfo XML database files. The lock is held while * this is called. @@ -178,7 +74,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) { @@ -342,317 +238,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
Cédric Bosdonnat
2017-Mar-07 14:26 UTC
[Libguestfs] [PATCH v4 3/9] mllib: ocaml wrapper for lib/osinfo
Provide osinfo database parsing API in OCAML. --- lib/osinfo.c | 13 +++++++ mllib/Makefile.am | 11 ++++-- mllib/osinfo-c.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ mllib/osinfo.ml | 26 ++++++++++++++ mllib/osinfo.mli | 31 +++++++++++++++++ 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 mllib/osinfo-c.c create mode 100644 mllib/osinfo.ml create mode 100644 mllib/osinfo.mli diff --git a/lib/osinfo.c b/lib/osinfo.c index 11b50d903..126a645c0 100644 --- a/lib/osinfo.c +++ b/lib/osinfo.c @@ -52,6 +52,19 @@ #include "osinfo.h" +#ifndef GUESTFS_PRIVATE +#undef perrorf +#define perrorf(g,...) \ +{ \ + CLEANUP_FREE char *msg = NULL; \ + ignore_value (asprintf (&msg, __VA_ARGS__)); \ + perror (msg); \ +} + +#undef debug +#define debug(g,...) fprintf (stderr, __VA_ARGS__) +#endif /* GUESTFS_PRIVATE */ + /* Read the libosinfo XML database files. The lock is held while * this is called. diff --git a/mllib/Makefile.am b/mllib/Makefile.am index ee2f1a7a8..ee16fe7ef 100644 --- a/mllib/Makefile.am +++ b/mllib/Makefile.am @@ -36,6 +36,7 @@ SOURCES_MLI = \ curl.mli \ getopt.mli \ JSON.mli \ + osinfo.mli \ planner.mli \ progress.mli \ regedit.mli \ @@ -63,7 +64,8 @@ SOURCES_ML = \ curl.ml \ checksums.ml \ xml.ml \ - xpath_helpers.ml + xpath_helpers.ml \ + osinfo.ml SOURCES_C = \ ../common/visit/visit.c \ @@ -71,8 +73,12 @@ 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 \ getopt-c.c \ + osinfo-c.c \ progress-c.c \ unix_utils-c.c \ uri-c.c \ @@ -106,7 +112,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..9546c5984 --- /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); + CAMLreturnT (int, -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..306cbce0f --- /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
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 4/9] builder: rename docs test script
Rename test-virt-builder-docs.sh into test-docs.sh to include test for another tool's documentation. --- builder/Makefile.am | 4 ++-- builder/{test-virt-builder-docs.sh => test-docs.sh} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename builder/{test-virt-builder-docs.sh => test-docs.sh} (100%) diff --git a/builder/Makefile.am b/builder/Makefile.am index d56b394b7..218f64b4c 100644 --- a/builder/Makefile.am +++ b/builder/Makefile.am @@ -28,7 +28,7 @@ EXTRA_DIST = \ test-simplestreams/streams/v1/index.json \ test-simplestreams/streams/v1/net.cirros-cloud_released_download.json \ test-virt-builder.sh \ - test-virt-builder-docs.sh \ + test-docs.sh \ test-virt-builder-list.sh \ test-virt-builder-list-simplestreams.sh \ test-virt-builder-planner.sh \ @@ -237,7 +237,7 @@ yajl_tests_LINK = \ $(yajl_tests_THEOBJECTS) -o $@ TESTS = \ - test-virt-builder-docs.sh \ + test-docs.sh \ test-virt-builder-list.sh \ test-virt-index-validate.sh \ $(SLOW_TESTS) diff --git a/builder/test-virt-builder-docs.sh b/builder/test-docs.sh similarity index 100% rename from builder/test-virt-builder-docs.sh rename to builder/test-docs.sh -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 5/9] builder: add Index_parser.write_entry function
Add a function to properly write virt-builder source index entries. Note that this function is very similar to Index.print_entry that is meant for debugging purposes. --- .gitignore | 1 + builder/Makefile.am | 36 +++++++++++++++- builder/index.mli | 3 ++ builder/index_parser.ml | 52 +++++++++++++++++++++++ builder/index_parser.mli | 6 +++ builder/index_parser_tests.ml | 95 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 builder/index_parser_tests.ml diff --git a/.gitignore b/.gitignore index e3b6d7b1c..06b16a49a 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,7 @@ Makefile.in /builder/virt-index-validate /builder/virt-index-validate.1 /builder/*.xz +/builder/index_parser_tests /builder/yajl_tests /cat/stamp-virt-*.pod /cat/virt-cat diff --git a/builder/Makefile.am b/builder/Makefile.am index 218f64b4c..bf4ccb7d7 100644 --- a/builder/Makefile.am +++ b/builder/Makefile.am @@ -217,13 +217,36 @@ yajl_tests_BOBJECTS = \ yajl_tests.cmo yajl_tests_XOBJECTS = $(yajl_tests_BOBJECTS:.cmo=.cmx) +index_parser_tests_SOURCES = \ + index-scan.c \ + index-struct.c \ + index-parser-c.c \ + index-parse.c +index_parser_tests_CPPFLAGS = $(virt_builder_CPPFLAGS) +index_parser_tests_BOBJECTS = \ + utils.cmo \ + cache.cmo \ + downloader.cmo \ + sigchecker.cmo \ + index.cmo \ + ini_reader.cmo \ + index_parser.cmo \ + index_parser_tests.cmo +index_parser_tests_XOBJECTS = $(index_parser_tests_BOBJECTS:.cmo=.cmx) + # Can't call the following as <test>_OBJECTS because automake gets confused. if HAVE_OCAMLOPT yajl_tests_THEOBJECTS = $(yajl_tests_XOBJECTS) yajl_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + +index_parser_tests_THEOBJECTS = $(index_parser_tests_XOBJECTS) +index_parser_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) else yajl_tests_THEOBJECTS = $(yajl_tests_BOBJECTS) yajl_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) + +index_parser_tests_THEOBJECTS = $(index_parser_tests_BOBJECTS) +index_parser_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) endif yajl_tests_DEPENDENCIES = \ @@ -236,6 +259,15 @@ yajl_tests_LINK = \ $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) $(OCAMLLINKFLAGS) \ $(yajl_tests_THEOBJECTS) -o $@ +index_parser_tests_DEPENDENCIES = \ + $(index_parser_tests_THEOBJECTS) \ + ../mllib/mllib.$(MLARCHIVE) \ + $(top_srcdir)/ocaml-link.sh +index_parser_tests_LINK = \ + $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \ + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) $(OCAMLLINKFLAGS) \ + $(index_parser_tests_THEOBJECTS) -o $@ + TESTS = \ test-docs.sh \ test-virt-builder-list.sh \ @@ -249,8 +281,8 @@ if ENABLE_APPLIANCE TESTS += test-virt-builder.sh endif ENABLE_APPLIANCE if HAVE_OCAML_PKG_OUNIT -check_PROGRAMS += yajl_tests -TESTS += yajl_tests +check_PROGRAMS += yajl_tests index_parser_tests +TESTS += yajl_tests index_parser_tests endif check-valgrind: diff --git a/builder/index.mli b/builder/index.mli index ff5ec4a35..6202d636e 100644 --- a/builder/index.mli +++ b/builder/index.mli @@ -39,3 +39,6 @@ and entry = { } val print_entry : out_channel -> (string * entry) -> unit +(** Debugging helper function dumping an index entry to a stream. + To write entries for non-debugging purpose, use the + [Index_parser.write_entry] function. *) diff --git a/builder/index_parser.ml b/builder/index_parser.ml index a3cae7d1a..9b3daed24 100644 --- a/builder/index_parser.ml +++ b/builder/index_parser.ml @@ -226,3 +226,55 @@ let get_index ~downloader ~sigchecker in get_index () + +let write_entry chan (name, { Index.printable_name = printable_name; + file_uri = file_uri; + arch = arch; + osinfo = osinfo; + signature_uri = signature_uri; + checksums = checksums; + revision = revision; + format = format; + size = size; + compressed_size = compressed_size; + expand = expand; + lvexpand = lvexpand; + notes = notes; + aliases = aliases; + hidden = hidden }) + let fp fs = fprintf chan fs in + fp "[%s]\n" name; + may (fp "name=%s\n") printable_name; + may (fp "osinfo=%s\n") osinfo; + fp "file=%s\n" file_uri; + fp "arch=%s\n" arch; + may (fp "sig=%s\n") signature_uri; + (match checksums with + | None -> () + | Some checksums -> + List.iter ( + fun c -> + fp "checksum[%s]=%s\n" + (Checksums.string_of_csum_t c) (Checksums.string_of_csum c) + ) checksums + ); + fp "revision=%s\n" (string_of_revision revision); + may (fp "format=%s\n") format; + fp "size=%Ld\n" size; + may (fp "compressed_size=%Ld\n") compressed_size; + may (fp "expand=%s\n") expand; + may (fp "lvexpand=%s\n") lvexpand; + List.iter ( + fun (lang, notes) -> + let format_notes notes + String.trim (Str.global_replace (Str.regexp "^\\([^ ]\\)" ) " \\1" notes) in + match lang with + | "" -> fp "notes=%s\n" (format_notes notes) + | lang -> fp "notes[%s]=%s\n" lang (format_notes notes) + ) notes; + (match aliases with + | None -> () + | Some l -> fp "aliases=%s\n" (String.concat " " l) + ); + if hidden then fp "hidden=true\n"; + fp "\n" diff --git a/builder/index_parser.mli b/builder/index_parser.mli index b8d8ddf3d..7c1c423ad 100644 --- a/builder/index_parser.mli +++ b/builder/index_parser.mli @@ -17,3 +17,9 @@ *) val get_index : downloader:Downloader.t -> sigchecker:Sigchecker.t -> Sources.source -> Index.index +(** [get_index download sigchecker source] will parse the source index file + into an index entry list. *) + +val write_entry : out_channel -> (string * Index.entry) -> unit +(** [write_entry chan entry] writes the index entry to the chan output + stream.*) diff --git a/builder/index_parser_tests.ml b/builder/index_parser_tests.ml new file mode 100644 index 000000000..e254fbf84 --- /dev/null +++ b/builder/index_parser_tests.ml @@ -0,0 +1,95 @@ +(* builder + * 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. + *) + +(* This file tests the Index_parser module. *) + +open OUnit2 +open Printf +open Unix_utils +open Common_utils + +let tmpdir = Mkdtemp.temp_dir "guestfs-tests." "";; +rmdir_on_exit tmpdir + +let dummy_sigchecker = Sigchecker.create ~gpg:"gpg" + ~check_signature:false + ~gpgkey:Utils.No_Key + ~tmpdir + +(* Utils. *) +let assert_equal_string = assert_equal ~printer:(fun x -> sprintf "\"%s\"" x) + +let test_open_out () + open_out (tmpdir // "out") + +let test_read_out chan + close_out chan; + read_whole_file (tmpdir // "out") + +let test_write_complete ctx + let entry + ("test-id", { Index.printable_name = Some "test_name"; + osinfo = Some "osinfo_data"; + file_uri = "test/path"; + arch = "test_arch"; + signature_uri = None; + checksums = Some [Checksums.SHA256 "256checksum"; Checksums.SHA512 "512checksum"]; + revision = Utils.Rev_int 42; + format = Some "qcow2"; + size = Int64 .of_int 123456; + compressed_size = Some (Int64.of_int 12345); + expand = Some "/dev/sda1"; + lvexpand = Some "/some/lv"; + notes = [ ("", "Notes split\non several lines\n with starting space") ]; + hidden = false; + aliases = Some ["alias1"; "alias2"]; + sigchecker = dummy_sigchecker; + proxy = Curl.UnsetProxy }) in + + let chan = test_open_out () in + Index_parser.write_entry chan entry; + let actual = test_read_out chan in + let expected = "[test-id] +name=test_name +osinfo=osinfo_data +file=test/path +arch=test_arch +checksum[sha256]=256checksum +checksum[sha512]=512checksum +revision=42 +format=qcow2 +size=123456 +compressed_size=12345 +expand=/dev/sda1 +lvexpand=/some/lv +notes=Notes split + on several lines + with starting space +aliases=alias1 alias2 + +" in + assert_equal_string expected actual + +let suite + "builder Index_parser" >::: + [ + "write.complete" >:: test_write_complete; + ] + +let () + run_test_tt_main suite -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 6/9] builder: add a template parameter to get_index
get_index now gets a new template parameter. Setting it to true will make the index parsing less picky about missing important data. This can be used to parse a partial index file. --- builder/builder.ml | 2 +- builder/index_parser.ml | 20 ++++++++++++++------ builder/index_parser.mli | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/builder/builder.ml b/builder/builder.ml index e59c763b2..5802b6873 100644 --- a/builder/builder.ml +++ b/builder/builder.ml @@ -209,7 +209,7 @@ let main () ~tmpdir in match source.Sources.format with | Sources.FormatNative -> - Index_parser.get_index ~downloader ~sigchecker source + Index_parser.get_index ~downloader ~sigchecker ~template:false source | Sources.FormatSimpleStreams -> Simplestreams_parser.get_index ~downloader ~sigchecker source ) sources diff --git a/builder/index_parser.ml b/builder/index_parser.ml index 9b3daed24..3869123ed 100644 --- a/builder/index_parser.ml +++ b/builder/index_parser.ml @@ -24,7 +24,7 @@ open Utils open Printf open Unix -let get_index ~downloader ~sigchecker +let get_index ~downloader ~sigchecker ~template { Sources.uri = uri; proxy = proxy } let corrupt_file () error (f_"The index file downloaded from '%s' is corrupt.\nYou need to ask the supplier of this file to fix it and upload a fixed version.") uri @@ -112,7 +112,7 @@ let get_index ~downloader ~sigchecker let revision try Rev_int (int_of_string (List.assoc ("revision", None) fields)) with - | Not_found -> Rev_int 1 + | Not_found -> if template then Rev_int 0 else Rev_int 1 | Failure _ -> eprintf (f_"%s: cannot parse 'revision' field for '%s'\n") prog n; corrupt_file () in @@ -122,11 +122,19 @@ let get_index ~downloader ~sigchecker try Int64.of_string (List.assoc ("size", None) fields) with | Not_found -> - eprintf (f_"%s: no 'size' field for '%s'\n") prog n; - corrupt_file () + if template then + Int64.zero + else ( + eprintf (f_"%s: no 'size' field for '%s'\n") prog n; + corrupt_file () + ) | Failure _ -> - eprintf (f_"%s: cannot parse 'size' field for '%s'\n") prog n; - corrupt_file () in + if template then + Int64.zero + else ( + eprintf (f_"%s: cannot parse 'size' field for '%s'\n") prog n; + corrupt_file () + ) in let compressed_size try Some (Int64.of_string (List.assoc ("compressed_size", None) fields)) with diff --git a/builder/index_parser.mli b/builder/index_parser.mli index 7c1c423ad..ae757ad6f 100644 --- a/builder/index_parser.mli +++ b/builder/index_parser.mli @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) -val get_index : downloader:Downloader.t -> sigchecker:Sigchecker.t -> Sources.source -> Index.index +val get_index : downloader:Downloader.t -> sigchecker:Sigchecker.t -> template:bool -> Sources.source -> Index.index (** [get_index download sigchecker source] will parse the source index file into an index entry list. *) -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 7/9] dib: move do_cp to mllib.Commun_utils
--- dib/utils.ml | 4 ---- mllib/common_utils.ml | 5 +++++ mllib/common_utils.mli | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dib/utils.ml b/dib/utils.ml index da5e738ad..e769ebe28 100644 --- a/dib/utils.ml +++ b/dib/utils.ml @@ -95,10 +95,6 @@ let require_tool tool with Executable_not_found tool -> error (f_"%s needed but not found") tool -let do_cp src destdir - let cmd = [ "cp"; "-t"; destdir; "-a"; src ] in - if run_command cmd <> 0 then exit 1 - let ensure_trailing_newline str if String.length str > 0 && str.[String.length str - 1] <> '\n' then str ^ "\n" else str diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml index e1d63292e..945728b5e 100644 --- a/mllib/common_utils.ml +++ b/mllib/common_utils.ml @@ -1167,3 +1167,8 @@ let inspect_decrypt g * function. *) c_inspect_decrypt g#ocaml_handle (Guestfs.c_pointer g#ocaml_handle) + +let do_cp src destdir + let cmd = [ "cp"; "-t"; destdir; "-a"; src ] in + if run_command cmd <> 0 then + error (f_"copy of %s to %s failed") src destdir diff --git a/mllib/common_utils.mli b/mllib/common_utils.mli index 1cd38ba83..5c376fcb3 100644 --- a/mllib/common_utils.mli +++ b/mllib/common_utils.mli @@ -492,3 +492,6 @@ val inspect_decrypt : Guestfs.guestfs -> unit (** Simple implementation of decryption: look for any [crypto_LUKS] partitions and decrypt them, then rescan for VGs. This only works for Fedora whole-disk encryption. *) + +val do_cp : string -> string -> unit +(** Run the cp command, and exit with an error if it failed *) -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 8/9] mllib: add do_mv helper function to Common_utils
--- mllib/common_utils.ml | 6 ++++++ mllib/common_utils.mli | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml index 945728b5e..7932707c9 100644 --- a/mllib/common_utils.ml +++ b/mllib/common_utils.ml @@ -1172,3 +1172,9 @@ let do_cp src destdir let cmd = [ "cp"; "-t"; destdir; "-a"; src ] in if run_command cmd <> 0 then error (f_"copy of %s to %s failed") src destdir + +let do_mv src dest + let cmd = [ "mv"; src; dest ] in + let r = run_command cmd in + if r <> 0 then + error (f_"moving file '%s' to '%s' failed") src dest diff --git a/mllib/common_utils.mli b/mllib/common_utils.mli index 5c376fcb3..33d576b5e 100644 --- a/mllib/common_utils.mli +++ b/mllib/common_utils.mli @@ -494,4 +494,7 @@ val inspect_decrypt : Guestfs.guestfs -> unit for Fedora whole-disk encryption. *) val do_cp : string -> string -> unit -(** Run the cp command, and exit with an error if it failed *) +(** Run the cp command and exit with an error if it failed *) + +val do_mv : string -> string -> unit +(** Run the mv command and exit with an error if it failed *) -- 2.11.0
Cédric Bosdonnat
2017-Mar-07 14:27 UTC
[Libguestfs] [PATCH v4 9/9] Add a virt-builder-repository tool
virt-builder-repository allows users to easily create or update a virt-builder source repository out of disk images. The tool can be run in either interactive or automated mode. --- .gitignore | 3 + builder/Makefile.am | 81 ++++++- builder/repository_main.ml | 466 ++++++++++++++++++++++++++++++++++++ builder/test-docs.sh | 2 + builder/virt-builder-repository.pod | 183 ++++++++++++++ 5 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 builder/repository_main.ml create mode 100644 builder/virt-builder-repository.pod diff --git a/.gitignore b/.gitignore index 06b16a49a..f505251bc 100644 --- a/.gitignore +++ b/.gitignore @@ -94,13 +94,16 @@ Makefile.in /builder/oUnit-* /builder/*.qcow2 /builder/stamp-virt-builder.pod +/builder/stamp-virt-builder-repository.pod /builder/stamp-virt-index-validate.pod /builder/test-config/virt-builder/repos.d/test-index.conf /builder/test-console-*.sh /builder/test-simplestreams/virt-builder/repos.d/cirros.conf /builder/test-website/virt-builder/repos.d/libguestfs.conf /builder/virt-builder +/builder/virt-builder-repository /builder/virt-builder.1 +/builder/virt-builder-repository.1 /builder/virt-index-validate /builder/virt-index-validate.1 /builder/*.xz diff --git a/builder/Makefile.am b/builder/Makefile.am index bf4ccb7d7..9ae8fe272 100644 --- a/builder/Makefile.am +++ b/builder/Makefile.am @@ -21,6 +21,8 @@ AM_YFLAGS = -d EXTRA_DIST = \ $(SOURCES_MLI) $(SOURCES_ML) $(SOURCES_C) \ + $(REPOSITORY_SOURCES_ML) \ + $(REPOSITORY_SOURCES_MLI) \ libguestfs.gpg \ opensuse.gpg \ test-console.sh \ @@ -38,6 +40,7 @@ EXTRA_DIST = \ test-virt-index-validate-good-2 \ test-virt-index-validate-good-3 \ virt-builder.pod \ + virt-builder-repository.pod \ virt-index-validate.pod \ yajl_tests.ml @@ -85,13 +88,44 @@ SOURCES_C = \ setlocale-c.c \ yajl-c.c +REPOSITORY_SOURCES_ML = \ + utils.ml \ + index.ml \ + cache.ml \ + downloader.ml \ + sigchecker.ml \ + ini_reader.ml \ + index_parser.ml \ + yajl.ml \ + paths.ml \ + sources.ml \ + repository_main.ml + +REPOSITORY_SOURCES_MLI = \ + cache.mli \ + downloader.mli \ + index.mli \ + index_parser.mli \ + ini_reader.mli \ + sigchecker.mli \ + sources.mli \ + yajl.mli + +REPOSITORY_SOURCES_C = \ + index-scan.c \ + index-struct.c \ + index-parse.c \ + index-parser-c.c \ + yajl-c.c + + man_MANS noinst_DATA bin_PROGRAMS if HAVE_OCAML -bin_PROGRAMS += virt-builder +bin_PROGRAMS += virt-builder virt-builder-repository virt_builder_SOURCES = $(SOURCES_C) virt_builder_CPPFLAGS = \ @@ -115,6 +149,26 @@ virt_builder_CFLAGS = \ BOBJECTS = $(SOURCES_ML:.ml=.cmo) XOBJECTS = $(BOBJECTS:.cmo=.cmx) +virt_builder_repository_SOURCES = $(REPOSITORY_SOURCES_C) +virt_builder_repository_CPPFLAGS = \ + -I. \ + -I$(top_builddir) \ + -I$(top_srcdir)/gnulib/lib -I$(top_builddir)/gnulib/lib \ + -I$(shell $(OCAMLC) -where) \ + -I$(top_srcdir)/gnulib/lib \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/fish +virt_builder_repository_CFLAGS = \ + -pthread \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) \ + -Wno-unused-macros \ + $(LIBLZMA_CFLAGS) \ + $(LIBTINFO_CFLAGS) \ + $(LIBXML2_CFLAGS) \ + $(YAJL_CFLAGS) +REPOSITORY_BOBJECTS = $(REPOSITORY_SOURCES_ML:.ml=.cmo) +REPOSITORY_XOBJECTS = $(REPOSITORY_BOBJECTS:.cmo=.cmx) + # -I $(top_builddir)/lib/.libs is a hack which forces corresponding -L # option to be passed to gcc, so we don't try linking against an # installed copy of libguestfs. @@ -149,8 +203,10 @@ OCAMLFLAGS = $(OCAML_FLAGS) $(OCAML_WARN_ERROR) if !HAVE_OCAMLOPT OBJECTS = $(BOBJECTS) +REPOSITORY_OBJECTS = $(REPOSITORY_BOBJECTS) else OBJECTS = $(XOBJECTS) +REPOSITORY_OBJECTS = $(REPOSITORY_XOBJECTS) endif OCAMLLINKFLAGS = mlguestfs.$(MLARCHIVE) mllib.$(MLARCHIVE) customize.$(MLARCHIVE) $(LINK_CUSTOM_OCAMLC_ONLY) @@ -165,6 +221,15 @@ virt_builder_LINK = \ $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLLINKFLAGS) \ $(OBJECTS) -o $@ +virt_builder_repository_DEPENDENCIES = \ + $(REPOSITORY_OBJECTS) \ + ../mllib/mllib.$(MLARCHIVE) \ + $(top_srcdir)/ocaml-link.sh +virt_builder_repository_LINK = \ + $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \ + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLLINKFLAGS) \ + $(REPOSITORY_OBJECTS) -o $@ + # Manual pages and HTML files for the website. man_MANS += virt-builder.1 @@ -183,6 +248,20 @@ stamp-virt-builder.pod: virt-builder.pod $(top_srcdir)/customize/customize-synop $< touch $@ +man_MANS += virt-builder-repository.1 +noinst_DATA += $(top_builddir)/website/virt-builder-repository.1.html + +virt-builder-repository.1 $(top_builddir)/website/virt-builder-repository.1.html: stamp-virt-builder-repository.pod + +stamp-virt-builder-repository.pod: virt-builder-repository.pod + $(PODWRAPPER) \ + --man virt-builder-repository.1 \ + --html $(top_builddir)/website/virt-builder-repository.1.html \ + --license GPLv2+ \ + --warning safe \ + $< + touch $@ + # Tests. TESTS_ENVIRONMENT = $(top_builddir)/run --test diff --git a/builder/repository_main.ml b/builder/repository_main.ml new file mode 100644 index 000000000..16c221b34 --- /dev/null +++ b/builder/repository_main.ml @@ -0,0 +1,466 @@ +(* virt-builder + * Copyright (C) 2016-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. + *) + +open Common_gettext.Gettext +open Common_utils +open Unix_utils +open Getopt.OptionName +open Utils +open Yajl +open Xpath_helpers + +open Printf + +module StringSet = Set.Make(String) + +type cmdline = { + gpg : string; + gpgkey : string option; + interactive : bool; + keep_unsigned : bool; + repo : string; +} + +type disk_image_info = { + format : string; + size : int64; +} + +let parse_cmdline () + let gpg = ref "gpg" in + let gpgkey = ref "" in + let interactive = ref false in + let keep_unsigned = ref false in + + let argspec = [ + [ L"gpg" ], Getopt.Set_string ("gpg", gpg), s_"Set GPG binary/command"; + [ S 'K'; L"gpg-key" ], Getopt.Set_string ("gpgkey", gpgkey), + s_"ID of the GPG key to sign the repo with"; + [ S 'i'; L"interactive" ], Getopt.Set interactive, s_"Ask the user about missing data"; + [ L"keep-index" ], Getopt.Set keep_unsigned, s_"Keep unsigned index"; + ] in + + let args = ref [] in + let anon_fun s = push_front s args in + let usage_msg + sprintf (f_"\ +%s: create a repository for virt-builder + + virt-builder-repository REPOSITORY_PATH + +A short summary of the options is given below. For detailed help please +read the man page virt-builder-repository(1). +") + prog in + let opthandle = create_standard_options argspec ~anon_fun usage_msg in + Getopt.parse opthandle; + + (* Dereference options. *) + let args = List.rev !args in + let gpg = !gpg in + let gpgkey = match !gpgkey with "" -> None | s -> Some s in + let interactive = !interactive in + let keep_unsigned = !keep_unsigned in + + (* Check options *) + let repo + (match args with + | [repo] -> repo + | [] -> + error (f_"virt-builder-repository /path/to/repo\nUse '/path/to/repo' to point to the repository folder.") + | _ -> + error (f_"too many parameters, only one path to repository is allowed") + ) in + + { + gpg = gpg; + gpgkey = gpgkey; + interactive = interactive; + keep_unsigned = keep_unsigned; + repo = repo; + } + +let increment_revision revision + match revision with + | Utils.Rev_int n -> Utils.Rev_int (n + 1) + | Utils.Rev_string s -> + if Str.string_match (Str.regexp "^\\(.*[-._]\\)\\([0-9]+\\)$") s 0 then + let prefix = Str.matched_group 1 s in + let suffix = int_of_string (Str.matched_group 2 s) in + Utils.Rev_string (prefix ^ (string_of_int (suffix + 1))) + else + Utils.Rev_string (s ^ ".1") + +let osinfo_ids = ref None + +let osinfo_get_short_ids () + match !osinfo_ids with + | Some ids -> ids + | None -> ( + let set = ref StringSet.empty in + let g = open_guestfs () in + + Osinfo.read_osinfo_db g#ocaml_handle ( + fun filepath -> + let doc = Xml.parse_file filepath in + let xpathctx = Xml.xpath_new_context doc in + let id = xpath_string_default xpathctx "/libosinfo/os/short-id" "" in + if id <> "" then + set := StringSet.add id !set + ); + g#close (); + osinfo_ids := Some (!set); + !set + ) + +let compress_to file outdir + info "Copying image to temporary folder ...%!"; + let outimg = outdir // (Filename.basename file) in + do_cp file outdir; + + info "Compressing ...%!"; + let cmd = [ "xz"; "-f"; "--best"; + "--block-size=16777216"; outimg ] in + if run_command cmd <> 0 then + exit 1; + outimg ^ ".xz" + +let get_disk_image_info filepath + let qemuimg_cmd = "qemu-img info --output json " ^ (quote filepath) in + let lines = external_command qemuimg_cmd in + let line = String.concat "\n" lines in + let infos = yajl_tree_parse line in + { + format = object_get_string "format" infos; + size = object_get_number "virtual-size" infos + } + +let process_image filename repo tmprepo index interactive sigchecker + message (f_"Preparing %s") filename; + + let filepath = repo // filename in + let { format = format; size = size } = get_disk_image_info filepath in + let xz_path = compress_to filepath tmprepo in + let checksum = Checksums.compute_checksum "sha512" xz_path in + let compressed_size = (Unix.stat xz_path).Unix.st_size in + + let ask message + printf message; + let value = read_line () in + match value with + | "" -> None + | s -> Some s + in + + let rec ask_id () + printf (f_"Identifier: "); + let id = read_line () in + if not (Str.string_match (Str.regexp "[a-zA-Z0-9-_.]+") id 0) then ( + warning (f_"Allowed characters are letters, digits, - _ and ."); + ask_id (); + ) else + id; + in + + let ask_arch () + printf (f_"Architecture. Choose one from the list below:\n"); + let arches = ["x86_64"; "aarch64"; "armv7l"; "i686"; "ppc64"; "ppc64le"; "s390x" ] in + iteri ( + fun i arch -> printf " [%d] %s\n" (i + 1) arch + ) arches; + + let input = read_line () in + if input = "exit" || input = "q" || input = "quit" then + exit 0 + else + try + let i = int_of_string input in + List.nth arches (i - 1) + with Failure _ -> input + in + + let ask_osinfo () + printf (f_ "osinfo short ID (can be found with osinfo-query os command): "); + let value = read_line () in + match value with + | "" -> None + | osinfo -> + let osinfo_ids = osinfo_get_short_ids () in + if not (StringSet.mem osinfo osinfo_ids) then + warning (f_"'%s' is not a recognized osinfo OS id; using it anyway") osinfo; + Some osinfo in + + (* Do we have an entry for that file already? *) + let file_entry + try + let xzfn filename = Filename.basename filename ^ ".xz" in + List.hd ( + List.filter ( + fun (id, { Index.file_uri=file_uri }) -> + (Filename.basename file_uri) = (xzfn (Filename.basename filename)) + ) index + ) + with + | Failure _ -> + let entry = { Index.printable_name = None; + osinfo = None; + file_uri = ""; + arch = ""; + signature_uri = None; + checksums = None; + revision = Utils.Rev_int 0; + format = Some format; + size = size; + compressed_size = Some (Int64.of_int compressed_size); + expand = None; + lvexpand = None; + notes = []; + hidden = false; + aliases = None; + sigchecker = sigchecker; + proxy = Curl.UnsetProxy } in + ("", entry) in + + let id, { Index.printable_name = printable_name; + osinfo = osinfo; + arch = arch; + checksums = checksums; + revision = revision; + expand = expand; + lvexpand = lvexpand; + notes = notes; + hidden = hidden; + aliases = aliases } = file_entry in + + let old_checksum + match checksums with + | Some csums -> ( + try + let csum = List.find ( + fun c -> + match c with + | Checksums.SHA512 _ -> true + | _ -> false + ) csums in + csum + with + | _ -> Checksums.SHA512 "" + ) + | None -> Checksums.SHA512 "" in + + let id + if id = "" then ( + if interactive then ask_id () + else error (f_"missing image identifier"); + ) else id in + + let printable_name + if printable_name = None && interactive then + ask (f_"Display name: ") + else + printable_name in + + let arch + if arch = "" then ( + if interactive then ask_arch () + else error (f_"missing architecture for %s") id; + ) else arch in + + let osinfo + if osinfo = None && interactive then + ask_osinfo () + else + osinfo in + + let expand + if expand = None && interactive then + ask (f_"Expandable partition: ") + else + expand in + + let lvexpand + if lvexpand = None && interactive then + ask (f_"Expandable volume: ") + else + lvexpand in + + let revision + if old_checksum <> checksum then + increment_revision revision + else + revision in + + (id, { Index.printable_name = printable_name; + osinfo = osinfo; + file_uri = Filename.basename xz_path; + arch = arch; + signature_uri = None; + checksums = Some [checksum]; + revision = revision; + format = Some format; + size = size; + compressed_size = Some (Int64.of_int compressed_size); + expand = expand; + lvexpand = lvexpand; + notes = notes; + hidden = hidden; + aliases = aliases; + sigchecker = sigchecker; + proxy = Curl.UnsetProxy }) + +let main () + let cmdline = parse_cmdline () in + + (* If debugging, echo the command line arguments. *) + debug "command line: %s\n" (String.concat " " (Array.to_list Sys.argv)); + + (* Check that the paths are existing *) + if not (Sys.file_exists cmdline.repo) then + error (f_"repository folder '%s' doesn't exist") cmdline.repo; + + (* Create a temporary folder to work in *) + let tmpdir = Mkdtemp.temp_dir ~base_dir:cmdline.repo + "virt-builder-repository." "" in + rmdir_on_exit tmpdir; + + let tmprepo = tmpdir // "repo" in + mkdir_p tmprepo 0o700; + + let sigchecker = Sigchecker.create ~gpg:cmdline.gpg + ~check_signature:false + ~gpgkey:No_Key + ~tmpdir in + + let index + try + let index_filename + List.find ( + fun filename -> Sys.file_exists (cmdline.repo // filename) + ) [ "index.asc"; "index" ] in + + let downloader = Downloader.create ~curl:"do-not-use-curl" + ~cache:None ~tmpdir in + + let source = { Sources.name = "input"; + uri = cmdline.repo // index_filename; + gpgkey = No_Key; + proxy = Curl.SystemProxy; + format = Sources.FormatNative } in + + Index_parser.get_index ~downloader ~sigchecker ~template:true source + with Not_found -> [] in + + (* Check for index/interactive consistency *) + if not cmdline.interactive && index == [] then + error (f_"the repository needs to contain an index file when running in automated mode"); + + debug "Searching for images ...\n"; + + let images + let is_supported_format file + let extension = last_part_of file '.' in + match extension with + | Some ext -> List.mem ext [ "qcow2"; "raw"; "img" ] + | None -> file <> "index" in + let files = Array.to_list (Sys.readdir cmdline.repo) in + List.filter is_supported_format files in + + debug " + %s\n" (String.concat "\n + " images); + + let outindex_path = tmprepo // "index" in + let index_channel = open_out outindex_path in + + (* Generate entries for uncompressed images *) + let written_ids + List.map ( + fun filename -> + let id, new_entry = process_image filename cmdline.repo tmprepo + index cmdline.interactive sigchecker in + + Index_parser.write_entry index_channel (id, new_entry); + id + ) images in + + (* Write the unchanged entries *) + List.iter ( + fun (id, entry) -> + let written = List.mem id written_ids in + if not written then + let { Index.file_uri = file_uri } = entry in + if Sys.file_exists file_uri then ( + let rel_path + try + subdirectory cmdline.repo file_uri + with + | Invalid_argument _ -> + file_uri in + debug "adding unchanged entry: %s" rel_path; + let rel_entry = { entry with Index.file_uri = rel_path } in + Index_parser.write_entry index_channel (id, rel_entry); + ); + ) index; + + close_out index_channel; + + (* GPG sign the generated index *) + (match cmdline.gpgkey with + | None -> + debug "Skip index signing" + | Some gpgkey -> + message (f_"Signing index with GPG key %s") gpgkey; + let cmd = [ cmdline.gpg; "--armor"; + "--output"; (tmprepo // "index.gpg"); + "--export"; gpgkey ] in + if run_command cmd <> 0 then + error (f_"failed to export GPG key %s") gpgkey; + + let cmd = [ cmdline.gpg; "--armor"; + "--default-key"; gpgkey; + "--clearsign"; (tmprepo // "index") ] in + if run_command cmd <> 0 then + error (f_"failed to sign index"); + + (* Remove the index file since we have the signed version of it *) + if not cmdline.keep_unsigned then + Sys.remove (tmprepo // "index") + ); + + message (f_"Creating index backup copy"); + + List.iter ( + fun filename -> + let filepath = cmdline.repo // filename in + if Sys.file_exists filepath then + do_mv filepath (filepath ^ ".bak") + ) ["index"; "index.asc"]; + + message (f_"Moving files to final destination"); + + Array.iter ( + fun filename -> + do_mv (tmprepo // filename) cmdline.repo + ) (Sys.readdir tmprepo); + + debug "Cleanup"; + + (* Remove the processed image files *) + List.iter ( + fun filename -> Sys.remove (cmdline.repo // filename) + ) images + +let () = run_main_and_handle_errors main diff --git a/builder/test-docs.sh b/builder/test-docs.sh index 884135de6..6f39b906d 100755 --- a/builder/test-docs.sh +++ b/builder/test-docs.sh @@ -25,3 +25,5 @@ $top_srcdir/podcheck.pl virt-builder.pod virt-builder \ --insert $top_srcdir/customize/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \ --insert $top_srcdir/customize/customize-options.pod:__CUSTOMIZE_OPTIONS__ \ --ignore=--check-signatures,--no-check-signatures + +$srcdir/../podcheck.pl virt-builder-repository.pod virt-builder-repository diff --git a/builder/virt-builder-repository.pod b/builder/virt-builder-repository.pod new file mode 100644 index 000000000..b5e790403 --- /dev/null +++ b/builder/virt-builder-repository.pod @@ -0,0 +1,183 @@ +=begin html + +<img src="virt-builder.svg" width="250" + style="float: right; clear: right;" /> + +=end html + +=head1 NAME + +virt-builder-repository - Build virt-builder source repository easily + +=head1 SYNOPSIS + + virt-builder-repository /path/to/repository + [-i|--interactive] [--gpg-key KEYID] + +=head1 DESCRIPTION + +Virt-builder is a tool for quickly building new virtual machines. It can +be configured to use template repositories. However creating and +maintaining a repository involves many tasks which can be automated. +virt-builder-repository is a tool helping to manage these repositories. + +Virt-builder-repository loops over the files in the C</path/to/repository> +folder, compresses the files with a name ending by C<qcow2>, C<raw> or +C<img>, extracts data from them and creates or updates the C<index> file. + +Some of the image-related data needed for the index file can't be +computed from the image file. virt-builder-repository first tries to +find them in the existing index file. If data are still missing after +this, they are prompted in interactive mode, otherwise an error will +be triggered. + +If a C<KEYID> is provided, the generated index file will be signed +with this GPG key. + +=head1 EXAMPLES + +=head2 Create the initial repository + +Create a folder and copy the disk image template files in it. Then +run a command like the following one: + + virt-builder-repository --gpg-key "joe@hacker.org" -i /path/to/folder + +Note that this example command runs in interactive mode. To run in +automated mode, a minimal index file needs to be created before running +the command containing sections like this one: + + [template_id] + name=template display name + file=template_filename.qcow.xz + arch=x86_64 + revision=0 + expand=/dev/sda3 + +The file value needs to match the image name extended with the ".xz" +suffix. Other optional data can be prefilled, for more informations, +see L<virt-builder(1)/Creating and signing the index file>. + +=head2 Update images in an existing repository + +In this use case, an new image or a new revision of an existing image +needs to be added to the repository. Place the corresponding image +template files in the repository folder. + +To update the revision of an image, the file needs to have the same +name than the existing one (without the C<xz> extension). + +As in the repository creation use case, a minimal fragment can be +added to the index file for the automated mode. This can be done +on the signed index even if it may sound a strange idea: the index +will be resigned by the tool. + +To remove an image from the repository, just remove the corresponding +compressed image file before running virt-builder-repository. + +Then running the following command will complete and update the index +file: + + virt-builder-repository --gpg-key "joe@hacker.org" -i /path/to/folder + +virt-builder-repository works in a temporary folder inside the repository +one. If anything wrong happens when running the tool, the repository is +left untouched. + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +Display help. + +=item B<--gpg> GPG + +Specify an alternate L<gpg(1)> (GNU Privacy Guard) binary. You can +also use this to add gpg parameters, for example to specify an +alternate home directory: + + virt-builder-repository --gpg "gpg --homedir /tmp" [...] + +This can also be used to avoid gpg asking for the key passphrase: + + virt-builder-repository --gpg "gpg --passphrase-file /tmp/pass --batch" [...] + +=item B<-K> KEYID + +=item B<--gpg-key> KEYID + +Specify the GPG key to be used to sign the repository index file. +If not provided, the index will left unsigned. C<KEYID> is used to +identify the GPG key to use. This value is passed to gpg's +C<--default-key> option and can can thus be an email address or a +fingerprint. + +B<NOTE>: by default, virt-builder-repository searches for the key +in the user's GPG key ring. + +=item B<--keep-index> + +When using a GPG key, don't remove the unsigned index. + +=item B<-i> + +=item B<--interactive> + +Prompt for missing data. + +=item B<--colors> + +=item B<--colours> + +Use ANSI colour sequences to colourize messages. This is the default +when the output is a tty. If the output of the program is redirected +to a file, ANSI colour sequences are disabled unless you use this +option. + +=item B<-q> + +=item B<--quiet> + +Don't print ordinary progress messages. + +=item B<-v> + +=item B<--verbose> + +Enable debug messages and/or produce verbose output. + +When reporting bugs, use this option and attach the complete output to +your bug report. + +=item B<-V> + +=item B<--version> + +Display version number and exit. + +=item B<-x> + +Enable tracing of libguestfs API calls. + + +=back + +=head1 EXIT STATUS + +This program returns 0 if successful, or non-zero if there was an +error. + +=head1 SEE ALSO + +L<virt-builder(1)> +L<http://libguestfs.org/>. + +=head1 AUTHOR + +Cédric Bosdonnat L<mailto:cbosdonnat@suse.com> + +=head1 COPYRIGHT + +Copyright (C) 2016-2017 SUSE Inc. -- 2.11.0
Pino Toscano
2017-Mar-08 17:45 UTC
Re: [Libguestfs] [PATCH v4 1/9] lib/osinfo.c: Extract xml processing into a callback
On Tuesday, 7 March 2017 15:26:57 CET Cédric Bosdonnat wrote:> In order to further reuse the osinfo database parsing in OCAML, this > commit extracts the XML processing for the distro ISOs and places it > into a newly created callback. > > This will later help other code to traverse the osinfo DB files and > let them extract what they need from them. > ---Mostly LGTM, just a couple of style issues.> lib/osinfo.c | 85 +++++++++++++++++++++++++++++++----------------------------- > 1 file changed, 44 insertions(+), 41 deletions(-) > > diff --git a/lib/osinfo.c b/lib/osinfo.c > index ea2a7659a..121fac1ba 100644 > --- a/lib/osinfo.c > +++ b/lib/osinfo.c > @@ -52,6 +52,7 @@ > #include <string.h> > #include <unistd.h> > #include <dirent.h> > +#include <errno.h> > #include <assert.h> > #include <sys/types.h> > #include <libintl.h> > @@ -71,10 +72,14 @@ 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 *); > - > #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);Forward declarations in a single line please. (Actual implementations are fine as wrapped.)> +static int read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *data);This forward declaration could stay where it was before (i.e. below).> +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 > @@ -87,14 +92,24 @@ static void free_osinfo_db_entry (struct osinfo *); > */ > int > guestfs_int_osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, > - const struct osinfo **osinfo_ret) > + const struct osinfo **osinfo_ret)Extra indentation change.> { > 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) { > + 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; > } > @@ -156,19 +171,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); > -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_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);Ditto (wrapping). Thanks, -- Pino Toscano
Pino Toscano
2017-Mar-08 17:59 UTC
Re: [Libguestfs] [PATCH v4 3/9] mllib: ocaml wrapper for lib/osinfo
On Tuesday, 7 March 2017 15:26:59 CET Cédric Bosdonnat wrote:> Provide osinfo database parsing API in OCAML. > --- > lib/osinfo.c | 13 +++++++ > mllib/Makefile.am | 11 ++++-- > mllib/osinfo-c.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ > mllib/osinfo.ml | 26 ++++++++++++++ > mllib/osinfo.mli | 31 +++++++++++++++++ > 5 files changed, 179 insertions(+), 2 deletions(-) > create mode 100644 mllib/osinfo-c.c > create mode 100644 mllib/osinfo.ml > create mode 100644 mllib/osinfo.mli > > diff --git a/lib/osinfo.c b/lib/osinfo.c > index 11b50d903..126a645c0 100644 > --- a/lib/osinfo.c > +++ b/lib/osinfo.c > @@ -52,6 +52,19 @@ > > #include "osinfo.h" > > +#ifndef GUESTFS_PRIVATE > +#undef perrorf > +#define perrorf(g,...) \ > +{ \ > + CLEANUP_FREE char *msg = NULL; \ > + ignore_value (asprintf (&msg, __VA_ARGS__)); \ > + perror (msg); \I'd add a short note here that ignorning the asprintf result is fine, as perror accepts null pointers (so in case of refactoring, eventual changes are done).> +} > + > +#undef debug > +#define debug(g,...) fprintf (stderr, __VA_ARGS__) > +#endif /* GUESTFS_PRIVATE */ > + > > /* Read the libosinfo XML database files. The lock is held while > * this is called. > diff --git a/mllib/Makefile.am b/mllib/Makefile.am > index ee2f1a7a8..ee16fe7ef 100644 > --- a/mllib/Makefile.am > +++ b/mllib/Makefile.am > @@ -36,6 +36,7 @@ SOURCES_MLI = \ > curl.mli \ > getopt.mli \ > JSON.mli \ > + osinfo.mli \ > planner.mli \ > progress.mli \ > regedit.mli \ > @@ -63,7 +64,8 @@ SOURCES_ML = \ > curl.ml \ > checksums.ml \ > xml.ml \ > - xpath_helpers.ml > + xpath_helpers.ml \ > + osinfo.ml > > SOURCES_C = \ > ../common/visit/visit.c \ > @@ -71,8 +73,12 @@ 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 \ > getopt-c.c \ > + osinfo-c.c \ > progress-c.c \ > unix_utils-c.c \ > uri-c.c \ > @@ -106,7 +112,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..9546c5984 > --- /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; > +This needs the same fix as recently done by Rich with commit 57d17ca301095d761163f966222d42b38880a543.> + 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); > + CAMLreturnT (int, -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..306cbce0f > --- /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. *)src -> lib, and libosinfo -> os-info (it's become an independent database) Thanks, -- Pino Toscano
Pino Toscano
2017-Mar-09 16:16 UTC
Re: [Libguestfs] [PATCH v4 5/9] builder: add Index_parser.write_entry function
On Tuesday, 7 March 2017 15:27:01 CET Cédric Bosdonnat wrote:> Add a function to properly write virt-builder source index entries. > Note that this function is very similar to Index.print_entry that is > meant for debugging purposes. > --- > .gitignore | 1 + > builder/Makefile.am | 36 +++++++++++++++- > builder/index.mli | 3 ++ > builder/index_parser.ml | 52 +++++++++++++++++++++++ > builder/index_parser.mli | 6 +++ > builder/index_parser_tests.ml | 95 +++++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 191 insertions(+), 2 deletions(-) > create mode 100644 builder/index_parser_tests.ml > > diff --git a/.gitignore b/.gitignore > index e3b6d7b1c..06b16a49a 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -104,6 +104,7 @@ Makefile.in > /builder/virt-index-validate > /builder/virt-index-validate.1 > /builder/*.xz > +/builder/index_parser_tests > /builder/yajl_tests > /cat/stamp-virt-*.pod > /cat/virt-cat > diff --git a/builder/Makefile.am b/builder/Makefile.am > index 218f64b4c..bf4ccb7d7 100644 > --- a/builder/Makefile.am > +++ b/builder/Makefile.am > @@ -217,13 +217,36 @@ yajl_tests_BOBJECTS = \ > yajl_tests.cmo > yajl_tests_XOBJECTS = $(yajl_tests_BOBJECTS:.cmo=.cmx) > > +index_parser_tests_SOURCES = \ > + index-scan.c \ > + index-struct.c \ > + index-parser-c.c \ > + index-parse.c > +index_parser_tests_CPPFLAGS = $(virt_builder_CPPFLAGS) > +index_parser_tests_BOBJECTS = \ > + utils.cmo \ > + cache.cmo \ > + downloader.cmo \ > + sigchecker.cmo \ > + index.cmo \ > + ini_reader.cmo \ > + index_parser.cmo \ > + index_parser_tests.cmo > +index_parser_tests_XOBJECTS = $(index_parser_tests_BOBJECTS:.cmo=.cmx) > + > # Can't call the following as <test>_OBJECTS because automake gets confused. > if HAVE_OCAMLOPT > yajl_tests_THEOBJECTS = $(yajl_tests_XOBJECTS) > yajl_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) > + > +index_parser_tests_THEOBJECTS = $(index_parser_tests_XOBJECTS) > +index_parser_tests.cmx: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) > else > yajl_tests_THEOBJECTS = $(yajl_tests_BOBJECTS) > yajl_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) > + > +index_parser_tests_THEOBJECTS = $(index_parser_tests_BOBJECTS) > +index_parser_tests.cmo: OCAMLPACKAGES += $(OCAMLPACKAGES_TESTS) > endif > > yajl_tests_DEPENDENCIES = \ > @@ -236,6 +259,15 @@ yajl_tests_LINK = \ > $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) $(OCAMLLINKFLAGS) \ > $(yajl_tests_THEOBJECTS) -o $@ > > +index_parser_tests_DEPENDENCIES = \ > + $(index_parser_tests_THEOBJECTS) \ > + ../mllib/mllib.$(MLARCHIVE) \ > + $(top_srcdir)/ocaml-link.sh > +index_parser_tests_LINK = \ > + $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \ > + $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLPACKAGES_TESTS) $(OCAMLLINKFLAGS) \ > + $(index_parser_tests_THEOBJECTS) -o $@ > + > TESTS = \ > test-docs.sh \ > test-virt-builder-list.sh \ > @@ -249,8 +281,8 @@ if ENABLE_APPLIANCE > TESTS += test-virt-builder.sh > endif ENABLE_APPLIANCE > if HAVE_OCAML_PKG_OUNIT > -check_PROGRAMS += yajl_tests > -TESTS += yajl_tests > +check_PROGRAMS += yajl_tests index_parser_tests > +TESTS += yajl_tests index_parser_tests > endif > > check-valgrind: > diff --git a/builder/index.mli b/builder/index.mli > index ff5ec4a35..6202d636e 100644 > --- a/builder/index.mli > +++ b/builder/index.mli > @@ -39,3 +39,6 @@ and entry = { > } > > val print_entry : out_channel -> (string * entry) -> unit > +(** Debugging helper function dumping an index entry to a stream. > + To write entries for non-debugging purpose, use the > + [Index_parser.write_entry] function. *) > diff --git a/builder/index_parser.ml b/builder/index_parser.ml > index a3cae7d1a..9b3daed24 100644 > --- a/builder/index_parser.ml > +++ b/builder/index_parser.ml > @@ -226,3 +226,55 @@ let get_index ~downloader ~sigchecker > in > > get_index () > + > +let write_entry chan (name, { Index.printable_name = printable_name; > + file_uri = file_uri; > + arch = arch; > + osinfo = osinfo; > + signature_uri = signature_uri; > + checksums = checksums; > + revision = revision; > + format = format; > + size = size; > + compressed_size = compressed_size; > + expand = expand; > + lvexpand = lvexpand; > + notes = notes; > + aliases = aliases; > + hidden = hidden }) > + let fp fs = fprintf chan fs in > + fp "[%s]\n" name; > + may (fp "name=%s\n") printable_name; > + may (fp "osinfo=%s\n") osinfo; > + fp "file=%s\n" file_uri; > + fp "arch=%s\n" arch; > + may (fp "sig=%s\n") signature_uri; > + (match checksums with > + | None -> () > + | Some checksums -> > + List.iter ( > + fun c -> > + fp "checksum[%s]=%s\n" > + (Checksums.string_of_csum_t c) (Checksums.string_of_csum c) > + ) checksums > + ); > + fp "revision=%s\n" (string_of_revision revision); > + may (fp "format=%s\n") format; > + fp "size=%Ld\n" size; > + may (fp "compressed_size=%Ld\n") compressed_size; > + may (fp "expand=%s\n") expand; > + may (fp "lvexpand=%s\n") lvexpand; > + List.iter ( > + fun (lang, notes) -> > + let format_notes notes > + String.trim (Str.global_replace (Str.regexp "^\\([^ ]\\)" ) " \\1" notes) inHmm this is still expensive, and partially incorrect (which your tests shows too): if the index entry was: Notes=SomeDistro This is the version of SomeDistro 1.0 then it will be parsed as "SomeDistro\n\nThis is the version of\n SomeDistro 1.0" but then the above format_notes will produce: Notes=SomeDistro This is the version of SomeDistro 1.0 (note one missing space at the beginning of the last line.) Also, the trim will remove extra spaces at the beginning or end of descriptions, which were kept when parsing. Also, I'd put this helper function outside the loop, since it does not depend on anything of the loop itself (it already takes the string as parameter).> + match lang with > + | "" -> fp "notes=%s\n" (format_notes notes) > + | lang -> fp "notes[%s]=%s\n" lang (format_notes notes) > + ) notes; > + (match aliases with > + | None -> () > + | Some l -> fp "aliases=%s\n" (String.concat " " l) > + ); > + if hidden then fp "hidden=true\n"; > + fp "\n" > diff --git a/builder/index_parser.mli b/builder/index_parser.mli > index b8d8ddf3d..7c1c423ad 100644 > --- a/builder/index_parser.mli > +++ b/builder/index_parser.mli > @@ -17,3 +17,9 @@ > *) > > val get_index : downloader:Downloader.t -> sigchecker:Sigchecker.t -> Sources.source -> Index.index > +(** [get_index download sigchecker source] will parse the source index file > + into an index entry list. *) > + > +val write_entry : out_channel -> (string * Index.entry) -> unit > +(** [write_entry chan entry] writes the index entry to the chan output > + stream.*) > diff --git a/builder/index_parser_tests.ml b/builder/index_parser_tests.ml > new file mode 100644 > index 000000000..e254fbf84 > --- /dev/null > +++ b/builder/index_parser_tests.ml > @@ -0,0 +1,95 @@ > +(* builder > + * 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. > + *) > + > +(* This file tests the Index_parser module. *) > + > +open OUnit2 > +open Printf > +open Unix_utils > +open Common_utils > + > +let tmpdir = Mkdtemp.temp_dir "guestfs-tests." "";; > +rmdir_on_exit tmpdir > + > +let dummy_sigchecker = Sigchecker.create ~gpg:"gpg" > + ~check_signature:false > + ~gpgkey:Utils.No_Key > + ~tmpdir > + > +(* Utils. *) > +let assert_equal_string = assert_equal ~printer:(fun x -> sprintf "\"%s\"" x) > + > +let test_open_out () > + open_out (tmpdir // "out") > + > +let test_read_out chan > + close_out chan; > + read_whole_file (tmpdir // "out")These two functions are somehow odd -- I'd have: - one that takes a list of pairs (name * entry), and writes it to a file - one that reads a file from the temporary directory i.e. something like: val write_entries : string -> (string * entry) list -> unit val read_file : string -> string so the code below can be...> +let test_write_complete ctx > + let entry > + ("test-id", { Index.printable_name = Some "test_name"; > + osinfo = Some "osinfo_data"; > + file_uri = "test/path"; > + arch = "test_arch"; > + signature_uri = None; > + checksums = Some [Checksums.SHA256 "256checksum"; Checksums.SHA512 "512checksum"]; > + revision = Utils.Rev_int 42; > + format = Some "qcow2"; > + size = Int64 .of_int 123456;Extra space right after Int64.> + compressed_size = Some (Int64.of_int 12345); > + expand = Some "/dev/sda1"; > + lvexpand = Some "/some/lv"; > + notes = [ ("", "Notes split\non several lines\n with starting space") ]; > + hidden = false; > + aliases = Some ["alias1"; "alias2"]; > + sigchecker = dummy_sigchecker; > + proxy = Curl.UnsetProxy }) in > + > + let chan = test_open_out () in > + Index_parser.write_entry chan entry; > + let actual = test_read_out chan inwrite_entries "out" [entry]; let actual = read_file "out" in> + let expected = "[test-id] > +name=test_name > +osinfo=osinfo_data > +file=test/path > +arch=test_arch > +checksum[sha256]=256checksum > +checksum[sha512]=512checksum > +revision=42 > +format=qcow2 > +size=123456 > +compressed_size=12345 > +expand=/dev/sda1 > +lvexpand=/some/lv > +notes=Notes split > + on several lines > + with starting space > +aliases=alias1 alias2 > + > +" in > + assert_equal_string expected actualAn addendum here could be to parse the file again, and compare the newly parsed entry with what was written in the file. Thanks, -- Pino Toscano
Pino Toscano
2017-Mar-09 16:19 UTC
Re: [Libguestfs] [PATCH v4 8/9] mllib: add do_mv helper function to Common_utils
On Tuesday, 7 March 2017 15:27:04 CET Cédric Bosdonnat wrote:> --- > mllib/common_utils.ml | 6 ++++++ > mllib/common_utils.mli | 5 ++++- > 2 files changed, 10 insertions(+), 1 deletion(-) > > diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml > index 945728b5e..7932707c9 100644 > --- a/mllib/common_utils.ml > +++ b/mllib/common_utils.ml > @@ -1172,3 +1172,9 @@ let do_cp src destdir > let cmd = [ "cp"; "-t"; destdir; "-a"; src ] in > if run_command cmd <> 0 then > error (f_"copy of %s to %s failed") src destdir > + > +let do_mv src dest > + let cmd = [ "mv"; src; dest ] in > + let r = run_command cmd in > + if r <> 0 then > + error (f_"moving file '%s' to '%s' failed") src dest > diff --git a/mllib/common_utils.mli b/mllib/common_utils.mli > index 5c376fcb3..33d576b5e 100644 > --- a/mllib/common_utils.mli > +++ b/mllib/common_utils.mli > @@ -494,4 +494,7 @@ val inspect_decrypt : Guestfs.guestfs -> unit > for Fedora whole-disk encryption. *) > > val do_cp : string -> string -> unit > -(** Run the cp command, and exit with an error if it failed *) > +(** Run the cp command and exit with an error if it failed *)This fix should be folder in the previous patch, i.e. the one that moves do_cp from virt-dib to Common_utils. OTOH, the comma there fits, so you could just leave it (and the same in the description of do_mv.) Thanks, -- Pino Toscano
Pino Toscano
2017-Mar-09 17:39 UTC
Re: [Libguestfs] [PATCH v4 9/9] Add a virt-builder-repository tool
On Tuesday, 7 March 2017 15:27:05 CET Cédric Bosdonnat wrote:> virt-builder-repository allows users to easily create or update > a virt-builder source repository out of disk images. The tool can > be run in either interactive or automated mode. > --- > [...] > diff --git a/builder/repository_main.ml b/builder/repository_main.ml > new file mode 100644 > index 000000000..16c221b34 > +let osinfo_get_short_ids () > + match !osinfo_ids with > + | Some ids -> ids > + | None -> ( > + let set = ref StringSet.empty in > + let g = open_guestfs () in > + > + Osinfo.read_osinfo_db g#ocaml_handle ( > + fun filepath -> > + let doc = Xml.parse_file filepath in > + let xpathctx = Xml.xpath_new_context doc in > + let id = xpath_string_default xpathctx "/libosinfo/os/short-id" "" inNow that I look at this again: the above expression will just read only one short-id from each XML file, while it can be repeated more than once (for example the Ubuntu ones have two each, "ubuntuXX.YY" and "ubuntuCODENAME").> +let process_image filename repo tmprepo index interactive sigchecker > + message (f_"Preparing %s") filename; > + > + let filepath = repo // filename in > + let { format = format; size = size } = get_disk_image_info filepath in > + let xz_path = compress_to filepath tmprepo in > + let checksum = Checksums.compute_checksum "sha512" xz_path in > + let compressed_size = (Unix.stat xz_path).Unix.st_size inSince later on this is turned into a int64, then most probably you could use Unix.LargeFile directly.> + debug " + %s\n" (String.concat "\n + " images);This could be: info (f_"Found new images: %s") (String.concat " " images);> + (* Generate entries for uncompressed images *) > + let written_ids > + List.map ( > + fun filename -> > + let id, new_entry = process_image filename cmdline.repo tmprepo > + index cmdline.interactive sigchecker in > + > + Index_parser.write_entry index_channel (id, new_entry); > + id > + ) images inThis block is not correctly indented.> + (* Write the unchanged entries *) > + List.iter ( > + fun (id, entry) -> > + let written = List.mem id written_ids inNote this will exclude an entry in the existing index, if a new image with the same identifier but a different architecture is found; see selected_cli_item in builder.ml.> + if not written then > + let { Index.file_uri = file_uri } = entry in > + if Sys.file_exists file_uri then ( > + let rel_path > + try > + subdirectory cmdline.repo file_uri > + with > + | Invalid_argument _ -> > + file_uri inThis block is not correctly indented.> + debug "adding unchanged entry: %s" rel_path; > + let rel_entry = { entry with Index.file_uri = rel_path } in > + Index_parser.write_entry index_channel (id, rel_entry); > + ); > + ) index;While the flow of the code from the comment (* Generate entries for uncompressed images *) above up to this point is clear, IMHO it'd be slightly better (and slightly more OCaml-ish) to: 1) first just process 'images', mapping that to the (name * index) list 2) filter out from 'index' the entries that are in the result of (1) 3) map the result of (2) with what currently done (i.e. processing their file_uri) 4) join (3) + (1) 5) write all the entries of (4) at once Thanks, -- Pino Toscano
Pino Toscano
2017-Mar-09 17:41 UTC
Re: [Libguestfs] [PATCH v4 0/9] Introducing virt-builder-repository
On Tuesday, 7 March 2017 15:26:56 CET Cédric Bosdonnat wrote:> Hi all, > > Here is a v4 of my series. It includes the changes according to > Pino and Richard's comments. > > However, the perrorf/debug problem is addressed differently: > instead of adding an implementation for the internal function > names when building for mllib, I redefine these macros. Obviously > this is not perfect, but at least easier to understand. > > Pino's comment about the Notes regex wasn't quite right... thus > I added a unit test for the Index_parser.write_entry > > As some more code has been moved around, the number of commits > in the series increased a little bit ;)I reviewed so far patches 1-8, and they LGTM except where I left comments. I left few comments for patch 9 -- I will continue reviewing it tomorrow. Thanks, -- Pino Toscano
Apparently Analagous Threads
- [PATCH v5 01/10] lib/osinfo.c: Extract xml processing into a callback
- [PATCH v6 01/10] lib/osinfo.c: Extract xml processing into a callback
- [PATCH v3 04/10] lib/osinfo.c: Extract xml processing into a callback
- [PATCH v2 3/7] mllib: expose libosinfo DB reading functions in mllib
- Re: [PATCH v4 1/9] lib/osinfo.c: Extract xml processing into a callback