Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 0/9] Introducing virt-builder-repository
Hi all, Here is an update of the series fixing Pino's latest comment. It just doesn't implement the change based on never-accepted run commands patch. 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 a template parameter to get_index builder: add Index.write_entry function mllib: add do_mv helper function to Common_utils mllib: add XPath helper xpath_get_nodes() Add a virt-builder-repository tool .gitignore | 4 + builder/Makefile.am | 124 ++++- builder/builder.ml | 2 +- builder/index.mli | 3 + builder/index_parser.ml | 80 ++- builder/index_parser.mli | 8 +- builder/index_parser_tests.ml | 129 +++++ builder/repository_main.ml | 584 +++++++++++++++++++++ .../{test-virt-builder-docs.sh => test-docs.sh} | 2 + builder/virt-builder-repository.pod | 213 ++++++++ lib/Makefile.am | 2 + lib/osinfo-iso.c | 462 ++++++++++++++++ lib/osinfo.c | 489 ++--------------- lib/osinfo.h | 27 + mllib/Makefile.am | 11 +- mllib/common_utils.ml | 6 + mllib/common_utils.mli | 3 + mllib/osinfo-c.c | 103 ++++ mllib/osinfo.ml | 26 + mllib/osinfo.mli | 31 ++ mllib/xpath_helpers.ml | 9 + mllib/xpath_helpers.mli | 4 + 22 files changed, 1869 insertions(+), 453 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.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 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 | 80 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/lib/osinfo.c b/lib/osinfo.c index ea2a7659a..3514585c7 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,11 @@ 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 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 +89,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 +168,16 @@ 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_xml (guestfs_h *g, const char *pathname, void *data); +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 +190,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 +256,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 +286,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 +309,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 +346,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.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 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 | 420 +------------------------------------------------- lib/osinfo.h | 27 ++++ 4 files changed, 493 insertions(+), 418 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 360ce9c92..2cb83f2bb 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -113,7 +113,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 3514585c7..5ccb554be 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,101 +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 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); +#include "osinfo.h" - if (osinfo_db_size <= 0) - return 0; - - /* Look in the database to see if we can find a match. */ - for (i = 0; i < (size_t) osinfo_db_size; ++i) { - if (osinfo_db[i].re_system_id) { - if (!isoinfo->iso_system_id || - !match (g, isoinfo->iso_system_id, osinfo_db[i].re_system_id)) - continue; - } - - if (osinfo_db[i].re_volume_id) { - if (!isoinfo->iso_volume_id || - !match (g, isoinfo->iso_volume_id, osinfo_db[i].re_volume_id)) - continue; - } - - if (osinfo_db[i].re_publisher_id) { - if (!isoinfo->iso_publisher_id || - !match (g, isoinfo->iso_publisher_id, osinfo_db[i].re_publisher_id)) - continue; - } - - if (osinfo_db[i].re_application_id) { - if (!isoinfo->iso_application_id || - !match (g, isoinfo->iso_application_id, osinfo_db[i].re_application_id)) - continue; - } - - debug (g, "osinfo: mapped disk to database entry %zu", i); - - if (osinfo_ret) - *osinfo_ret = &osinfo_db[i]; - return 1; - } - - debug (g, "osinfo: no mapping found"); - - return 0; -} /* Read the libosinfo XML database files. The lock is held while * this is called. @@ -168,12 +67,11 @@ 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 *pathname, void *data); 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 +int read_osinfo_db (guestfs_h *g, read_osinfo_db_callback callback, void *opaque) { @@ -337,317 +235,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.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 3/9] mllib: ocaml wrapper for lib/osinfo
Provide osinfo database parsing API in OCaml. --- lib/osinfo.c | 39 +++++++++++++++++++++ mllib/Makefile.am | 11 ++++-- mllib/osinfo-c.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ mllib/osinfo.ml | 26 ++++++++++++++ mllib/osinfo.mli | 31 ++++++++++++++++ 5 files changed, 208 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 5ccb554be..9a411b28d 100644 --- a/lib/osinfo.c +++ b/lib/osinfo.c @@ -52,6 +52,45 @@ #include "osinfo.h" +#ifndef GUESTFS_PRIVATE +#undef perrorf +static void perrorf(guestfs_h *g, const char *fmt, ...) +__attribute__((format (printf,2,3))); + +static void perrorf(guestfs_h *g, const char *fmt, ...) +{ + va_list args; + CLEANUP_FREE char *msg = NULL; + CLEANUP_FREE char *fs = NULL; + + ignore_value (asprintf (&fs, "%s\n", fmt)); + + va_start (args, fmt); + /* Ignoring the result is fine since perror + * can take NULL input */ + ignore_value (vasprintf (&msg, fs, args)); + va_end (args); + perror (msg); +} + +#undef debug +static void debug(guestfs_h *g, const char *fmt, ...) +__attribute__((format (printf,2,3))); + +static void +debug(guestfs_h *g, const char *fmt, ...) +{ + va_list args; + CLEANUP_FREE char *fs = NULL; + + ignore_value (asprintf (&fs, "%s\n", fmt)); + + va_start (args, fmt); + vfprintf (stderr, fs, args); + va_end (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..84760a85f --- /dev/null +++ b/mllib/osinfo-c.c @@ -0,0 +1,103 @@ +/* Bindings for osinfo db reading function. + * Copyright (C) 2016 Red Hat Inc. + * 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); + + exn = Val_unit; + + 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..8a21eb215 --- /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 lib/osinfo.h os-info database reading API. *) + +type osinfo_db_callback = string -> unit +(** The osinfo_db_callback is a callback called for each data file + in the os-info 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 os-info database + files and call the callback on them. *) -- 2.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 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.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 5/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 | 26 ++++++++++++++++++-------- builder/index_parser.mli | 4 +++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/builder/builder.ml b/builder/builder.ml index b0a48ea89..99cd488b2 100644 --- a/builder/builder.ml +++ b/builder/builder.ml @@ -207,7 +207,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 468805cf8..c70909e44 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 @@ -99,8 +99,10 @@ let get_index ~downloader ~sigchecker let arch try List.assoc ("arch", None) fields with Not_found -> - eprintf (f_"%s: no ‘arch’ entry for ‘%s’\n") prog n; - corrupt_file () in + if template then "" else ( + eprintf (f_"%s: no ‘arch’ entry for ‘%s’\n") prog n; + corrupt_file () + ) in let signature_uri try Some (make_absolute_uri (List.assoc ("sig", None) fields)) with Not_found -> None in @@ -112,7 +114,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 +124,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 b8d8ddf3d..aa5f84730 100644 --- a/builder/index_parser.mli +++ b/builder/index_parser.mli @@ -16,4 +16,6 @@ * 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.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 6/9] builder: add Index.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 | 54 ++++++++++++++++++ builder/index_parser.mli | 4 ++ builder/index_parser_tests.ml | 129 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 builder/index_parser_tests.ml diff --git a/.gitignore b/.gitignore index 69e1ae160..b9e00ee04 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,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 c70909e44..c56c4b8f8 100644 --- a/builder/index_parser.ml +++ b/builder/index_parser.ml @@ -236,3 +236,57 @@ let get_index ~downloader ~sigchecker ~template 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; + + let format_notes notes + String.concat "\n " (String.nsplit "\n" notes) in + + List.iter ( + fun (lang, notes) -> + 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 aa5f84730..ae757ad6f 100644 --- a/builder/index_parser.mli +++ b/builder/index_parser.mli @@ -19,3 +19,7 @@ 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. *) + +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..c4352d752 --- /dev/null +++ b/builder/index_parser_tests.ml @@ -0,0 +1,129 @@ +(* 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 + +let dummy_downloader = Downloader.create ~curl:"do-not-use-curl" + ~cache:None ~tmpdir + +(* Utils. *) +let write_entries file entries + let chan = open_out (tmpdir // file) in + List.iter ( + fun (entry) -> + Index_parser.write_entry chan entry; + ) entries; + close_out chan + +let read_file file + read_whole_file (tmpdir // "out") + +let parse_file file + let source = { Sources.name = "input"; + uri = tmpdir // file; + gpgkey = Utils.No_Key; + proxy = Curl.SystemProxy; + format = Sources.FormatNative } in + let entries = Index_parser.get_index ~downloader:dummy_downloader + ~sigchecker:dummy_sigchecker + ~template:false + source in + List.map ( + fun (id, e) -> (id, { e with Index.file_uri = Filename.basename e.Index.file_uri }) + ) entries + +let format_entries entries + let format_entry entry + write_entries "out" [entry]; + read_file "out" in + List.map format_entry entries + +let assert_equal_string = assert_equal ~printer:(fun x -> sprintf "\"%s\"" x) +let assert_equal_list formatter + let printer = ( + fun x -> "(" ^ (String.escaped (String.concat "," (formatter x))) ^ ")" + ) in + assert_equal ~printer + +let test_write_complete ctx + let entry + ("test-id", { Index.printable_name = Some "test_name"; + osinfo = Some "osinfo_data"; + file_uri = "image_path"; + arch = "test_arch"; + signature_uri = None; + checksums = Some [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\n with starting space ") ]; + hidden = false; + aliases = Some ["alias1"; "alias2"]; + sigchecker = dummy_sigchecker; + proxy = Curl.SystemProxy }) in + + write_entries "out" [entry]; + let actual = read_file "out" in + let expected = "[test-id] +name=test_name +osinfo=osinfo_data +file=image_path +arch=test_arch +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 parsed_entries = parse_file "out" in + assert_equal_list format_entries [entry] parsed_entries + +let suite + "builder Index_parser" >::: + [ + "write.complete" >:: test_write_complete; + ] + +let () + run_test_tt_main suite -- 2.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 7/9] mllib: add do_mv helper function to Common_utils
--- mllib/common_utils.ml | 6 ++++++ mllib/common_utils.mli | 3 +++ 2 files changed, 9 insertions(+) diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml index 6a9b08973..8ed7c7554 100644 --- a/mllib/common_utils.ml +++ b/mllib/common_utils.ml @@ -1188,3 +1188,9 @@ let inspect_decrypt g * function. *) c_inspect_decrypt g#ocaml_handle (Guestfs.c_pointer g#ocaml_handle) + +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 c088f8497..2f01cdeae 100644 --- a/mllib/common_utils.mli +++ b/mllib/common_utils.mli @@ -498,3 +498,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_mv : string -> string -> unit +(** Run the mv command, and exit with an error if it failed *) -- 2.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 8/9] mllib: add XPath helper xpath_get_nodes()
This function will allow more OCaml-ish processing of XPath queries with multiple results. --- mllib/xpath_helpers.ml | 9 +++++++++ mllib/xpath_helpers.mli | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/mllib/xpath_helpers.ml b/mllib/xpath_helpers.ml index d651fab23..c66a21c44 100644 --- a/mllib/xpath_helpers.ml +++ b/mllib/xpath_helpers.ml @@ -51,3 +51,12 @@ let xpath_eval_default parsefn xpath expr default let xpath_string_default = xpath_eval_default identity let xpath_int_default = xpath_eval_default int_of_string let xpath_int64_default = xpath_eval_default Int64.of_string + +let xpath_get_nodes xpathctx expr + let obj = Xml.xpath_eval_expression xpathctx expr in + let nodes = ref [] in + for i = 0 to Xml.xpathobj_nr_nodes obj - 1 do + let node = Xml.xpathobj_node obj i in + push_back nodes node + done; + !nodes diff --git a/mllib/xpath_helpers.mli b/mllib/xpath_helpers.mli index 7434ba645..83c770281 100644 --- a/mllib/xpath_helpers.mli +++ b/mllib/xpath_helpers.mli @@ -31,3 +31,7 @@ val xpath_int_default : Xml.xpathctx -> string -> int -> int val xpath_int64_default : Xml.xpathctx -> string -> int64 -> int64 (** Parse an xpath expression and return a string/int; if the expression doesn't match, return the default. *) + +val xpath_get_nodes : Xml.xpathctx -> string -> Xml.node list +(** Parse an XPath expression and return a list with the matching + XML nodes. *) -- 2.12.2
Cédric Bosdonnat
2017-Jun-19 08:48 UTC
[Libguestfs] [PATCH v7 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 | 84 +++++- builder/repository_main.ml | 584 ++++++++++++++++++++++++++++++++++++ builder/test-docs.sh | 2 + builder/virt-builder-repository.pod | 213 +++++++++++++ 5 files changed, 883 insertions(+), 3 deletions(-) create mode 100644 builder/repository_main.ml create mode 100644 builder/virt-builder-repository.pod diff --git a/.gitignore b/.gitignore index b9e00ee04..ca0baff64 100644 --- a/.gitignore +++ b/.gitignore @@ -96,13 +96,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..df09c3f3d 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 = \ @@ -101,8 +135,7 @@ virt_builder_CPPFLAGS = \ -I$(shell $(OCAMLC) -where) \ -I$(top_srcdir)/gnulib/lib \ -I$(top_srcdir)/common/utils \ - -I$(top_srcdir)/lib \ - -I$(top_srcdir)/fish + -I$(top_srcdir)/lib virt_builder_CFLAGS = \ -pthread \ $(WARN_CFLAGS) $(WERROR_CFLAGS) \ @@ -115,6 +148,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)/lib \ + -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 +202,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 +220,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 +247,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..079715dd6 --- /dev/null +++ b/builder/repository_main.ml @@ -0,0 +1,584 @@ +(* 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; + no_compression : 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 no_compression = ref false in + let machine_readable = 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"; + [ L"no-compression" ], Getopt.Set no_compression, s_"Don't compress the new images in the index"; + [ L"machine-readable" ], Getopt.Set machine_readable, s_"Make output machine readable"; + ] 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; + + (* Machine-readable mode? Print out some facts about what + * this binary supports. + *) + if !machine_readable then ( + printf "virt-builder-repository\n"; + exit 0 + ); + + (* 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 + let no_compression = !no_compression 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; + no_compression = no_compression; + repo = repo; + } + +let increment_revision = function + | 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 checksums_get_sha512 = function + | None -> Checksums.SHA512 "" + | Some csums -> + try + List.find ( + function + | Checksums.SHA512 _ -> true + | _ -> false + ) csums + with Not_found -> Checksums.SHA512 "" + +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 nodes = xpath_get_nodes xpathctx "/libosinfo/os/short-id" in + List.iter ( + fun node -> + let id = Xml.node_as_string node in + set := StringSet.add id !set + ) nodes + ); + g#close (); + osinfo_ids := Some (!set); + !set + ) + +let compress_to file outdir + let outimg = outdir // (Filename.basename file) ^ ".xz" in + + info "Compressing ...%!"; + let cmd = sprintf "cat \"%s\" | xz -f --best --block-size=16777216 - >\"%s\"" + file outimg in + if shell_command cmd <> 0 then + exit 1; + outimg + +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 compute_short_id distro major minor + match distro with + | "centos" when major >= 7 -> + sprintf "%s%d.0" distro major + | "debian" when major >= 4 -> + sprintf "%s%d" distro major + | ("fedora"|"mageia") -> + sprintf "%s%d" distro major + | "sles" when major = 0 -> + sprintf "%s%d" distro major + | "sles" -> + sprintf "%s%dsp%d" distro major minor + | "ubuntu" -> + sprintf "%s%d.%02d" distro major minor + | _ (* Any other combination. *) -> + sprintf "%s%d.%d" distro major minor + +let has_entry id arch index + List.exists ( + fun (item_id, { Index.arch = item_arch }) -> + item_id = id && item_arch = arch + ) index + +let process_image acc_entries filename repo tmprepo index interactive + no_compression sigchecker + message (f_"Preparing %s") filename; + + let filepath = repo // filename in + let { format = format; size = size } = get_disk_image_info filepath in + let out_path + if no_compression then filepath + else compress_to filepath tmprepo in + let out_filename = Filename.basename out_path in + let checksum = Checksums.compute_checksum "sha512" out_path in + let compressed_size = (Unix.LargeFile.stat out_path).Unix.LargeFile.st_size in + + let ask ?default ?values message + let default_str = match default with + | None -> "" + | Some x -> sprintf " [%s] " x in + + let list_str = match values with + | None -> "" + | Some x -> + sprintf (f_"Choose one from the list below:\n %s\n") + (String.concat "\n " x) in + + printf "%s%s%s" message default_str list_str; + + let value = read_line () in + match value with + | "" -> default + | "-" -> None + | s -> Some s + in + + let rec ask_id default + match ask (s_"Identifier: ") ~default with + | None -> default + | Some id -> + if not (Str.string_match (Str.regexp "[a-zA-Z0-9-_.]+") id 0) then ( + warning (f_"Allowed characters are letters, digits, - _ and ."); + ask_id default + ) else + id in + + let rec ask_arch guess + let arches = [ "x86_64"; "aarch64"; "armv7l"; "i686"; "ppc64"; "ppc64le"; "s390x" ] in + match (ask (s_"Architecture: ") ~default:guess ~values:arches) with + | None -> ask_arch guess + | Some x -> + if x == "" then + ask_arch guess + else + x + in + + let ask_osinfo default + match ask (s_ "osinfo short ID: ") + ~default with + | None -> None + | Some 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 + List.hd ( + List.filter ( + fun (_, { Index.file_uri = file_uri }) -> + let basename = Filename.basename file_uri in + basename = out_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 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 = checksums_get_sha512 checksums in + + if old_checksum = checksum then + let id, entry = file_entry in + (id, { entry with Index.file_uri = out_filename }) + else ( + message (f_"Extracting data from the image..."); + let g = new Guestfs.guestfs () in + g#add_drive_ro filepath; + g#launch (); + + let roots = g#inspect_os () in + let nroots = Array.length roots in + if nroots <> 1 then + error (f_"virt-builder template images must have one and only one root file system, found %d") + nroots; + + let root = Array.get roots 0 in + let product = g#inspect_get_product_name root in + let inspected_arch = g#inspect_get_arch root in + let distro = g#inspect_get_distro root in + let version_major = g#inspect_get_major_version root in + let version_minor = g#inspect_get_minor_version root in + let lvs = g#lvs () in + let filesystems = g#inspect_get_filesystems root in + + let shortid = compute_short_id distro version_major version_minor in + + g#close (); + + let id + if id = "" then ( + if interactive then ask_id shortid + else error (f_"missing image identifier"); + ) else id in + + let arch + if arch = "" then ( + if interactive then ask_arch inspected_arch + else inspected_arch; + ) else arch in + + if arch = "" then + error (f_"missing architecture for %s") id; + + if has_entry id arch acc_entries then + error (f_"Already existing image with id %s and architecture %s") id arch; + + let printable_name + if printable_name = None then + if interactive then ask (s_"Display name: ") ~default:product + else Some product + else + printable_name in + + let osinfo + if osinfo = None then + if interactive then ask_osinfo shortid else Some shortid + else + osinfo in + + let expand + if expand = None then + if interactive then ask (s_"Expandable partition: ") ~default:root + ~values:(Array.to_list filesystems) + else Some root + else + expand in + + let lvexpand + if lvexpand = None && lvs <> [||] then + if interactive then + ask (s_"Expandable volume: ") ~values:(Array.to_list lvs) + else Some (Array.get lvs 0) + 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 out_path; + arch = arch; + signature_uri = None; + checksums = Some [checksum]; + revision = revision; + format = Some format; + size = size; + compressed_size = Some 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 = index_filename; + 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 must 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 is_new file + try + let _, { Index.checksums = checksums } + List.find ( + fun (_, { Index.file_uri = file_uri }) -> + Filename.basename file_uri = file + ) index in + let checksum = checksums_get_sha512 checksums in + let path = cmdline.repo // file in + let file_checksum = Checksums.compute_checksum "sha512" path in + checksum <> file_checksum + with Not_found -> true in + let files = Array.to_list (Sys.readdir cmdline.repo) in + let files = List.filter ( + fun file -> is_regular_file (cmdline.repo // file) + ) files in + List.filter ( + fun file -> + if is_supported_format file then + is_new file + else + false + ) files in + + if images == [] then ( + info (f_ "No new image found"); + exit 0 + ); + + info (f_ "Found new images: %s") (String.concat " " images); + + let outindex_path = tmprepo // "index" in + let index_channel = open_out outindex_path in + + (* Generate entries for uncompressed images *) + let images_entries = List.fold_right ( + fun filename acc -> + let image_entry = process_image acc filename cmdline.repo tmprepo + index cmdline.interactive cmdline.no_compression + sigchecker in + image_entry :: acc + ) images [] in + + (* Filter out entries for newly found images and entries + without a corresponding image file or with empty arch *) + let index = List.filter ( + fun (id, { Index.arch = arch; + Index.file_uri = file_uri }) -> + arch <> "" && not (has_entry id arch images_entries) && + Sys.file_exists file_uri + ) index in + + (* Convert all URIs back to relative ones *) + let index = List.map ( + fun (id, entry) -> + let { Index.file_uri = file_uri } = entry in + let rel_path + try + subdirectory cmdline.repo file_uri + with + | Invalid_argument _ -> + file_uri in + let rel_entry = { entry with Index.file_uri = rel_path } in + (id, rel_entry) + ) index in + + (* Write all the entries *) + List.iter ( + fun entry -> + Index_parser.write_entry index_channel entry; + ) (index @ images_entries); + + 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 = sprintf "%s --armor --output %s --export %s" + (quote (cmdline.gpg // "index.gpg")) + (quote tmprepo) (quote gpgkey) in + if shell_command cmd <> 0 then + error (f_"failed to export GPG key %s") gpgkey; + + let cmd = sprintf "%s --armor --default-key %s --clearsign %s" + (quote cmdline.gpg) (quote gpgkey) + (quote (tmprepo // "index" )) in + if shell_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 *) + if not cmdline.no_compression then + 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..585d39437 --- /dev/null +++ b/builder/virt-builder-repository.pod @@ -0,0 +1,213 @@ +=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 directory specified +as argument, compresses the files with a name ending by C<qcow2>, C<raw>, +C<img> or without extension, 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] + file=template_filename.qcow.xz + +The file value needs to match the image name extended with the ".xz" +suffix if the C<--no-compression> parameter is not provided or the +image name if no compression is involved. Other optional data can be +prefilled. Default values are computed by inspecting the disk image. +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 signed again 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 thus be an email address or a +fingerprint. + +B<NOTE>: by default, virt-builder-repository searches for the key +in the user's GPG keyring. + +=item B<-i> + +=item B<--interactive> + +Prompt for missing data. Default values are computed from the disk +image. + +When prompted for data, inputting C<-> corresponds to leaving the +value empty. This can be used to avoid setting the default computed value. + +=item B<--keep-index> + +When using a GPG key, don't remove the unsigned index. + +=item B<--no-compression> + +Don't compress the template images. + +=item B<--machine-readable> + +This option is used to make the output more machine friendly +when being parsed by other programs. See +L</MACHINE READABLE OUTPUT> below. + + +=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 MACHINE READABLE OUTPUT + +The I<--machine-readable> option can be used to make the output more +machine friendly, which is useful when calling virt-builder-repository from +other programs, GUIs etc. + +Use the option on its own to query the capabilities of the +virt-builder-repository binary. Typical output looks like this: + + $ virt-builder-repository --machine-readable + virt-builder-repository + +A list of features is printed, one per line, and the program exits +with status 0. + +=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.12.2
Pino Toscano
2017-Jul-07 08:58 UTC
Re: [Libguestfs] [PATCH v7 9/9] Add a virt-builder-repository tool
On Monday, 19 June 2017 10:48:37 CEST Cédric Bosdonnat wrote:> +let compress_to file outdir > + let outimg = outdir // (Filename.basename file) ^ ".xz" in > + > + info "Compressing ...%!"; > + let cmd = sprintf "cat \"%s\" | xz -f --best --block-size=16777216 - >\"%s\"" > + file outimg in > + if shell_command cmd <> 0 then > + exit 1;This finally can now be: let cmd = [ "xz"; "-f"; "--best"; "--block-size=16777216"; "-c"; file ] in let file_flags = [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC; ] in let outfd = Unix.openfile outimg file_flags 0o666 in let res = run_command cmd ~stdout_chan:outfd in if res <> 0 then error (f_"'xz' command failed"); Yes, slightly more verbose, but it avoids the shell, and the need for proper quoting. It's also logged, and faster (since the whole data of the disk is not read twice, once by cat, and once by xz via stdin). Thanks, -- Pino Toscano
Pino Toscano
2017-Jul-07 13:04 UTC
Re: [Libguestfs] [PATCH v7 2/9] lib: extract osinfo DB traversing API
On Monday, 19 June 2017 10:48:30 CEST Cédric Bosdonnat wrote:> 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. > ---This (and patch #3) conflict with Rich's patch to deprecate the inspection of ISO images. So we should decide what is the fate of this code: a) move the C code as-is to virt-builder b) apply this (and following) patch to make it a bit more general c) write the osinfo-db code in OCaml, directly in virt-builder d) any other solution I missed above -- Pino Toscano
Maybe Matching Threads
- [PATCH v4 0/9] Introducing virt-builder-repository
- [PATCH v6 00/10] Add a virt-builder-repository tool
- [PATCH v5 00/10] Introducing virt-builder-repository
- [PATCH v3 00/10] Introducing virt-builder-repository
- [PATCH v2 0/7] Introducing virt-builder-repository