plover/import-yum.c
author J. Ali Harlow <ali@juiblex.co.uk>
Sat Jul 16 11:07:18 2016 +0100 (2016-07-16)
changeset 61 31fb35727621
parent 55 8c80282a3888
child 71 bd272d15bea4
permissions -rw-r--r--
Support parallel installations. The idea is that for CAD screener, we want
to be able to install this on the same machine as a standard AVOT setup
(most notably for John's laptop). To allow for the possibility of a second
application that might have the same requirements, we add the concept of
vendor-specific distributions. Thus we can have one distribution for CAD
screener and one for The Next Big Thing. It doesn't seem trivial to have
both CAD screener and AVOT under the same vendor tag so we'll have to have
AVOT under "City Occupational" and CAD screener under "City Occupational Ltd"
or some such kludge.

Most of this is done although we are very short of test cases (in particular
we don't test that it's actually possible to install CAD screener in parallel
with AVOT or to update either of them once installed, which is fundamental).

We also have a lot of baggage left over, including an intercept of razor_set.
The problem that this was introduced to debug has been fixed but it looks
like there are a number of memory leaks which it might be useful to help
track down so it has been left in place for now.

There is still a lot of confusion in plover between path-based and URI-based
API. We should review the API, decide what we want and have a general clear up.

There is also confusion as to the purpose of RAZOR_ROOT (and meaning; path or
URI). This is not used at all in librazor (although it is used in razor.exe).
Ideally we shouldn't use it in plover or plover-gtk either although again, we
might want to support it or an equivalent in (some of) the various executables.

Work that would still to nice to do for CAD screener:

- uninstall (ideally as an installed program that hooks into Add/Remove programs
but even re-running the installer would be acceptable).
- xz support (smaller packages).
- repomd.xml and xml:base (would be needed for an Internet installer).
- graphical installer.
     1 /*
     2  * Copyright (C) 2008  Kristian Høgsberg <krh@redhat.com>
     3  * Copyright (C) 2008  Red Hat, Inc
     4  * Copyright (C) 2009, 2011, 2014, 2016  J. Ali Harlow <ali@juiblex.co.uk>
     5  *
     6  * This program is free software; you can redistribute it and/or modify
     7  * it under the terms of the GNU General Public License as published by
     8  * the Free Software Foundation; either version 2 of the License, or
     9  * (at your option) any later version.
    10  *
    11  * This program is distributed in the hope that it will be useful,
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  * GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License along
    17  * with this program; if not, write to the Free Software Foundation, Inc.,
    18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    19  */
    20 
    21 #define _GNU_SOURCE
    22 
    23 #include <string.h>
    24 #include <stdio.h>
    25 #include <stdint.h>
    26 #include <sys/stat.h>
    27 #include <unistd.h>
    28 #include <fcntl.h>
    29 #include <errno.h>
    30 
    31 #include <glib.h>
    32 #include <expat.h>
    33 #include <zlib.h>
    34 #include <razor.h>
    35 #include "plover/plover.h"
    36 #include "plover/uri-handler.h"
    37 #include "plover/inputstream.h"
    38 
    39 /* Import a yum filelist as a razor package set. */
    40 
    41 enum {
    42 	YUM_STATE_BEGIN,
    43 	YUM_STATE_PACKAGE_NAME,
    44 	YUM_STATE_PACKAGE_ARCH,
    45 	YUM_STATE_SUMMARY,
    46 	YUM_STATE_DESCRIPTION,
    47 	YUM_STATE_URL,
    48 	YUM_STATE_LICENSE,
    49 	YUM_STATE_CHECKSUM,
    50 	YUM_STATE_REQUIRES,
    51 	YUM_STATE_PROVIDES,
    52 	YUM_STATE_OBSOLETES,
    53 	YUM_STATE_CONFLICTS,
    54 	YUM_STATE_SKIPPING_PACKAGE,
    55 	YUM_STATE_FILE
    56 };
    57 
    58 struct yum_context {
    59 	XML_Parser primary_parser;
    60 	XML_Parser filelists_parser;
    61 	XML_Parser current_parser;
    62 
    63 	struct razor_importer *importer;
    64 	struct import_property_context *current_property_context;
    65 	const char *base_uri;
    66 	GTree *uris;
    67 	char name[256], arch[64], summary[512], description[4096];
    68 	char url[256], license[64], buffer[512], *p;
    69 	char pkgid[128], evr[128];
    70 	uint32_t property_type;
    71 	int state;
    72 
    73 	int total, current;
    74 };
    75 
    76 static uint32_t
    77 yum_to_razor_relation (const char *flags)
    78 {
    79 	if (flags[0] == 'L') {
    80 		if (flags[1] == 'T')
    81 			return RAZOR_PROPERTY_LESS;
    82 		else
    83 			return RAZOR_PROPERTY_LESS | RAZOR_PROPERTY_EQUAL;
    84 	} else if (flags[0] == 'G') {
    85 		if (flags[1] == 'T')
    86 			return RAZOR_PROPERTY_GREATER;
    87 		else
    88 			return RAZOR_PROPERTY_GREATER | RAZOR_PROPERTY_EQUAL;
    89 	} else
    90 		return RAZOR_PROPERTY_EQUAL;
    91 }
    92 
    93 static void
    94 yum_primary_start_element(void *data, const char *name, const char **atts)
    95 {
    96 	struct yum_context *ctx = data;
    97 	const char *n, *epoch, *version, *release;
    98 	char buffer[128];
    99 	char *s;
   100 	gchar *nevra;
   101 	uint32_t pre, relation, flags;
   102 	int i;
   103 
   104 	if (ctx->state == YUM_STATE_SKIPPING_PACKAGE)
   105 		return;
   106 
   107 	if (strcmp(name, "metadata") == 0) {
   108 		for (i = 0; atts[i]; i += 2) {
   109 			if (strcmp(atts[i], "packages") == 0)
   110 				ctx->total = atoi(atts[i + 1]);
   111 		}
   112 	} else if (strcmp(name, "package") == 0) {
   113 		*ctx->name=*ctx->arch=*ctx->summary=*ctx->description='\0';
   114 		*ctx->url=*ctx->license='\0';
   115 	} else if (strcmp(name, "name") == 0) {
   116 		ctx->state = YUM_STATE_PACKAGE_NAME;
   117 		ctx->p = ctx->name;
   118 	} else if (strcmp(name, "arch") == 0) {
   119 		ctx->state = YUM_STATE_PACKAGE_ARCH;
   120 		ctx->p = ctx->arch;
   121 	} else if (strcmp(name, "version") == 0) {
   122 		epoch = NULL;
   123 		version = NULL;
   124 		release = NULL;
   125 		for (i = 0; atts[i]; i += 2) {
   126 			if (strcmp(atts[i], "epoch") == 0)
   127 				epoch = atts[i + 1];
   128 			else if (strcmp(atts[i], "ver") == 0)
   129 				version = atts[i + 1];
   130 			else if (strcmp(atts[i], "rel") == 0)
   131 				release = atts[i + 1];
   132 		}
   133 		if (version == NULL || release == NULL) {
   134 			fprintf(stderr, "invalid version tag, "
   135 				"missing version or  release attribute\n");
   136 			return;
   137 		}
   138 
   139 		razor_build_evr(ctx->evr, sizeof ctx->evr, epoch, version,
   140 				release);
   141 		if (!strcmp(ctx->arch, "noarch") ||
   142 		    !strcmp(ctx->arch, razor_system_arch())) {
   143 			razor_importer_begin_package(ctx->importer, ctx->name,
   144 						     ctx->evr, ctx->arch);
   145 		} else
   146 			ctx->state = YUM_STATE_SKIPPING_PACKAGE;
   147 	} else if (strcmp(name, "summary") == 0) {
   148 		ctx->p = ctx->summary;
   149 		ctx->state = YUM_STATE_SUMMARY;
   150 	} else if (strcmp(name, "description") == 0) {
   151 		ctx->p = ctx->description;
   152 		ctx->state = YUM_STATE_DESCRIPTION;
   153 	} else if (strcmp(name, "url") == 0) {
   154 		ctx->p = ctx->url;
   155 		ctx->state = YUM_STATE_URL;
   156 	} else if (strcmp(name, "checksum") == 0) {
   157 		ctx->p = ctx->pkgid;
   158 		ctx->state = YUM_STATE_CHECKSUM;
   159 	} else if (strcmp(name, "location") == 0) {
   160 		if (ctx->state != YUM_STATE_SKIPPING_PACKAGE) {
   161 		    for (i = 0; atts[i]; i += 2)
   162 			if (strcmp(atts[i], "href") == 0) {
   163 			    nevra=g_strconcat(ctx->name,"-",ctx->evr,".",
   164 			      ctx->arch,NULL);
   165 			    s=razor_path_relative_to_uri(ctx->base_uri,
   166 			      atts[i + 1],NULL);
   167 			    g_tree_insert(ctx->uris,nevra,g_strdup(s));
   168 			    free(s);
   169 			    break;
   170 			}
   171 		}
   172 	} else if (strcmp(name, "rpm:license") == 0) {
   173 		ctx->p = ctx->license;
   174 		ctx->state = YUM_STATE_LICENSE;
   175 	} else if (strcmp(name, "rpm:requires") == 0) {
   176 		ctx->state = YUM_STATE_REQUIRES;
   177 		ctx->property_type = RAZOR_PROPERTY_REQUIRES;
   178 	} else if (strcmp(name, "rpm:provides") == 0) {
   179 		ctx->state = YUM_STATE_PROVIDES;
   180 		ctx->property_type = RAZOR_PROPERTY_PROVIDES;
   181 	} else if (strcmp(name, "rpm:obsoletes") == 0) {
   182 		ctx->state = YUM_STATE_OBSOLETES;
   183 		ctx->property_type = RAZOR_PROPERTY_OBSOLETES;
   184 	} else if (strcmp(name, "rpm:conflicts") == 0) {
   185 		ctx->state = YUM_STATE_CONFLICTS;
   186 		ctx->property_type = RAZOR_PROPERTY_CONFLICTS;
   187 	} else if (strcmp(name, "rpm:entry") == 0 &&
   188 		   ctx->state != YUM_STATE_BEGIN) {
   189 		n = NULL;
   190 		epoch = NULL;
   191 		version = NULL;
   192 		release = NULL;
   193 		relation = RAZOR_PROPERTY_EQUAL;
   194 		pre = 0;
   195 		for (i = 0; atts[i]; i += 2) {
   196 			if (strcmp(atts[i], "name") == 0)
   197 				n = atts[i + 1];
   198 			else if (strcmp(atts[i], "epoch") == 0)
   199 				epoch = atts[i + 1];
   200 			else if (strcmp(atts[i], "ver") == 0)
   201 				version = atts[i + 1];
   202 			else if (strcmp(atts[i], "rel") == 0)
   203 				release = atts[i + 1];
   204 			else if (strcmp(atts[i], "flags") == 0)
   205 				relation = yum_to_razor_relation(atts[i + 1]);
   206 			else if (strcmp(atts[i], "pre") == 0)
   207 				pre = RAZOR_PROPERTY_PRE;
   208 		}
   209 
   210 		if (n == NULL) {
   211 			fprintf(stderr, "invalid rpm:entry, "
   212 				"missing name or version attributes\n");
   213 			return;
   214 		}
   215 
   216 		razor_build_evr(buffer, sizeof buffer, epoch, version, release);
   217 		flags = ctx->property_type | relation | pre;
   218 		razor_importer_add_property(ctx->importer, n, flags, buffer);
   219 	}
   220 }
   221 
   222 static void
   223 yum_primary_end_element (void *data, const char *name)
   224 {
   225 	struct yum_context *ctx = data;
   226 
   227 	switch (ctx->state) {
   228 	case YUM_STATE_PACKAGE_NAME:
   229 	case YUM_STATE_PACKAGE_ARCH:
   230 	case YUM_STATE_SUMMARY:
   231 	case YUM_STATE_DESCRIPTION:
   232 	case YUM_STATE_URL:
   233 	case YUM_STATE_LICENSE:
   234 	case YUM_STATE_CHECKSUM:
   235 	case YUM_STATE_FILE:
   236 		ctx->state = YUM_STATE_BEGIN;
   237 		break;
   238 	}
   239 
   240 	if (strcmp(name, "package") == 0) {
   241 		if (ctx->state != YUM_STATE_SKIPPING_PACKAGE)
   242 			razor_importer_add_details(ctx->importer, ctx->summary,
   243 						   ctx->description, ctx->url,
   244 						   ctx->license);
   245 
   246 		XML_StopParser(ctx->current_parser, XML_TRUE);
   247 		ctx->current_parser = ctx->filelists_parser;
   248 	}
   249 }
   250 
   251 static void
   252 yum_character_data (void *data, const XML_Char *s, int len)
   253 {
   254 	struct yum_context *ctx = data;
   255 
   256 	switch (ctx->state) {
   257 	case YUM_STATE_PACKAGE_NAME:
   258 	case YUM_STATE_PACKAGE_ARCH:
   259 	case YUM_STATE_SUMMARY:
   260 	case YUM_STATE_DESCRIPTION:
   261 	case YUM_STATE_URL:
   262 	case YUM_STATE_LICENSE:
   263 	case YUM_STATE_CHECKSUM:
   264 	case YUM_STATE_FILE:
   265 		memcpy(ctx->p, s, len);
   266 		ctx->p += len;
   267 		*ctx->p = '\0';
   268 		break;
   269 	}
   270 }
   271 
   272 static void
   273 yum_filelists_start_element(void *data, const char *name, const char **atts)
   274 {
   275 	struct yum_context *ctx = data;
   276 	const char *pkg, *pkgid;
   277 	int i;
   278 
   279 	if (strcmp(name, "package") == 0 &&
   280 	    ctx->state != YUM_STATE_SKIPPING_PACKAGE) {
   281 		pkg = NULL;
   282 		pkgid = NULL;
   283 		for (i = 0; atts[i]; i += 2) {
   284 			if (strcmp(atts[i], "name") == 0)
   285 				pkg = atts[i + 1];
   286 			else if (strcmp(atts[i], "pkgid") == 0)
   287 				pkgid = atts[i + 1];
   288 		}
   289 		if (strcmp(pkgid, ctx->pkgid) != 0)
   290 			fprintf(stderr, "primary.xml and filelists.xml "
   291 				"mismatch for %s: %s vs %s",
   292 				pkg, pkgid, ctx->pkgid);
   293 	} else if (strcmp(name, "file") == 0) {
   294 		if (ctx->state != YUM_STATE_SKIPPING_PACKAGE)
   295 			ctx->state = YUM_STATE_FILE;
   296 		ctx->p = ctx->buffer;
   297 	}
   298 }
   299 
   300 static void
   301 yum_filelists_end_element (void *data, const char *name)
   302 {
   303 	struct yum_context *ctx = data;
   304 
   305 	if (strcmp(name, "package") == 0) {
   306 		XML_StopParser(ctx->current_parser, XML_TRUE);
   307 		ctx->current_parser = ctx->primary_parser;
   308 		if (ctx->state != YUM_STATE_SKIPPING_PACKAGE)
   309 			razor_importer_finish_package(ctx->importer);
   310 		ctx->state = YUM_STATE_BEGIN;
   311 	} else if (strcmp(name, "file") == 0) {
   312 		if (ctx->state != YUM_STATE_SKIPPING_PACKAGE)
   313 			razor_importer_add_file(ctx->importer, ctx->buffer);
   314 	}
   315 	if (ctx->state != YUM_STATE_SKIPPING_PACKAGE)
   316 		ctx->state = YUM_STATE_BEGIN;
   317 }
   318 
   319 static int plover_system_arch_is_x86(void)
   320 {
   321     const char *arch=razor_system_arch();
   322     if (!arch || arch[0]!='i' || arch[1]<'3' || arch[1]>'6')
   323 	return 0;
   324     else
   325 	return !strcmp(arch+2,"86");
   326 }
   327 
   328 #define XML_BUFFER_SIZE 4096
   329 
   330 PloverRepository *plover_repository_new_from_yum_uri(const char *base_uri,
   331   GError **error)
   332 {
   333     struct yum_context ctx;
   334     gchar *s,**rpm_uris;
   335     GPtrArray *uris;
   336     char *uri;
   337     const char *name,*version,*arch;
   338     void *buf;
   339     gssize len;
   340     GFile *file;
   341     GInputStream *stream;
   342     GInputStream *primary,*filelists;
   343     GZlibDecompressor *decompressor;
   344     XML_ParsingStatus status;
   345     struct razor_set *razor;
   346     struct razor_package_iterator *iter;
   347     struct razor_package *pkg;
   348     PloverPackageSet *set;
   349     PloverRepository *repository;
   350     plover__uri_handler_init();
   351     ctx.importer=razor_importer_create();
   352     ctx.state=YUM_STATE_BEGIN;
   353     ctx.base_uri=base_uri;
   354     ctx.primary_parser=XML_ParserCreate(NULL);
   355     XML_SetUserData(ctx.primary_parser,&ctx);
   356     XML_SetElementHandler(ctx.primary_parser,yum_primary_start_element,
   357       yum_primary_end_element);
   358     XML_SetCharacterDataHandler(ctx.primary_parser,yum_character_data);
   359     ctx.filelists_parser=XML_ParserCreate(NULL);
   360     XML_SetUserData(ctx.filelists_parser,&ctx);
   361     XML_SetElementHandler(ctx.filelists_parser,yum_filelists_start_element,
   362       yum_filelists_end_element);
   363     XML_SetCharacterDataHandler(ctx.filelists_parser,yum_character_data);
   364     uri=razor_path_relative_to_uri(base_uri,"repodata/primary.xml.gz",NULL);
   365     stream=plover_razor_input_stream_new(uri,error);
   366     free(uri);
   367     if (!stream) {
   368 	XML_ParserFree(ctx.primary_parser);
   369 	XML_ParserFree(ctx.filelists_parser);
   370 	return NULL;
   371     }
   372     decompressor=g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
   373     primary=g_converter_input_stream_new(G_INPUT_STREAM(stream),
   374       G_CONVERTER(decompressor));
   375     g_object_unref(stream);
   376     g_object_unref(decompressor);
   377     uri=razor_path_relative_to_uri(base_uri,"repodata/filelists.xml.gz",NULL);
   378     stream=plover_razor_input_stream_new(uri,error);
   379     free(uri);
   380     if (!stream) {
   381 	g_object_unref(primary);
   382 	XML_ParserFree(ctx.primary_parser);
   383 	XML_ParserFree(ctx.filelists_parser);
   384 	return NULL;
   385     }
   386     decompressor=g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
   387     filelists=g_converter_input_stream_new(G_INPUT_STREAM(stream),
   388       G_CONVERTER(decompressor));
   389     g_object_unref(stream);
   390     g_object_unref(decompressor);
   391     ctx.current_parser=ctx.primary_parser;
   392     ctx.uris=g_tree_new_full((GCompareDataFunc)strcmp,NULL,g_free,NULL);
   393     ctx.current=0;
   394     do
   395     {
   396 	XML_GetParsingStatus(ctx.current_parser,&status);
   397 	switch (status.parsing)
   398 	{
   399 	    case XML_SUSPENDED:
   400 		XML_ResumeParser(ctx.current_parser);
   401 		break;
   402 	    case XML_PARSING:
   403 	    case XML_INITIALIZED:
   404 		buf=XML_GetBuffer(ctx.current_parser,XML_BUFFER_SIZE);
   405 		if (ctx.current_parser==ctx.primary_parser)
   406 		    len=g_input_stream_read(G_INPUT_STREAM(primary),buf,
   407 		      XML_BUFFER_SIZE,NULL,error);
   408 		else
   409 		    len=g_input_stream_read(G_INPUT_STREAM(filelists),buf,
   410 		      XML_BUFFER_SIZE,NULL,error);
   411 		if (len<0)
   412 		    return NULL;
   413 		XML_ParseBuffer(ctx.current_parser,len,!len);
   414 		break;
   415 	    case XML_FINISHED:
   416 		break;
   417 	}
   418     } while (status.parsing!=XML_FINISHED);
   419     XML_ParserFree(ctx.primary_parser);
   420     XML_ParserFree(ctx.filelists_parser);
   421     g_object_unref(primary);
   422     g_object_unref(filelists);
   423     razor=razor_importer_finish(ctx.importer);
   424 #if RAZOR_HEADER_VERSION_MIN<=1
   425     /*
   426      * Header version 1 is supported by plover v0.3 and is used on
   427      * 32-bit intel machines which allows the setup and update
   428      * applications from v0.3 to work. On other machines, we don't
   429      * want these old applications to work (since they would do
   430      * the wrong thing) and so we use the current header version
   431      * which they don't support.
   432      */
   433     if (plover_system_arch_is_x86())
   434 	razor_set_set_header_version(razor,1);
   435 #endif
   436     uris=g_ptr_array_new();
   437     iter=razor_package_iterator_create(razor);
   438     while(razor_package_iterator_next(iter,&pkg,RAZOR_DETAIL_NAME,&name,
   439       RAZOR_DETAIL_VERSION,&version,RAZOR_DETAIL_ARCH,&arch,RAZOR_DETAIL_LAST))
   440     {
   441 	s=g_strconcat(name,"-",version,".",arch,NULL);
   442 	g_ptr_array_add(uris,g_tree_lookup(ctx.uris,s));
   443 	g_free(s);
   444     }
   445     razor_package_iterator_destroy(iter);
   446     g_ptr_array_add(uris,NULL);
   447     g_tree_unref(ctx.uris);
   448     rpm_uris=(gchar **)g_ptr_array_free(uris,FALSE);
   449     set=plover_package_set_new_from_razor(razor);
   450     razor_set_unref(razor);
   451     repository=plover_repository_new_from_package_set(set,
   452       (const char **)rpm_uris);
   453     g_object_unref(set);
   454     g_strfreev(rpm_uris);
   455     return repository;
   456 }
   457 
   458 struct razor_set *plover_razor_set_create_from_yum_uri(const char *base_uri,
   459   GError **error)
   460 {
   461     PloverRepository *repository;
   462     PloverPackageSet *set;
   463     struct razor_set *razor;
   464     repository=plover_repository_new_from_yum_uri(base_uri,error);
   465     if (!repository)
   466 	return NULL;
   467     set=plover_repository_get_package_set(repository);
   468     razor=plover_package_set_get_razor(set);
   469     razor_set_ref(razor);
   470     g_object_unref(repository);
   471     return razor;
   472 }
   473 
   474 struct razor_set *plover_razor_set_create_from_yum(const char *base,
   475   GError **error)
   476 {
   477     gchar *base_uri;
   478     GFile *file;
   479     struct razor_set *set;
   480     file=g_file_new_for_path(base);
   481     base_uri=g_file_get_uri(file);
   482     g_object_unref(file);
   483     set=plover_razor_set_create_from_yum_uri(base_uri,error);
   484     g_free(base_uri);
   485     return set;
   486 }