aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MANIFEST9
-rw-r--r--Makefile.PL13
-rw-r--r--README9
-rw-r--r--URPM.pm129
-rw-r--r--URPM.xs1514
-rw-r--r--URPM/Build.pm383
-rw-r--r--t/rpmdb.t39
-rw-r--r--t/synthesis.t141
-rw-r--r--typemap2
9 files changed, 2239 insertions, 0 deletions
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..61e6b3b
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,9 @@
+README
+MANIFEST
+Makefile.PL
+typemap
+URPM.xs
+URPM.pm
+URPM/Build.pm
+t/rpmdb.t
+t/synthesis.t
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..eba032e
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,13 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+
+WriteMakefile(
+ 'NAME' => 'URPM',
+ 'OPTIMIZE' => '-O3 -fomit-frame-pointer -fno-exceptions -fno-rtti -pipe -s -ffast-math -fexpensive-optimizations',
+ 'MAKEFILE' => 'Makefile',
+ 'OBJECT' => 'URPM.o',
+ 'VERSION_FROM' => 'URPM.pm',
+ 'LIBS' => ['-lrpm -lrpmio -lrpmdb -lpopt -lz -lbz2'], # e.g., '-lm'
+ 'INC' => '-I/usr/include/rpm', # e.g., '-I/usr/include/other'
+);
diff --git a/README b/README
new file mode 100644
index 0000000..504a8ce
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+The URPM module allows you to manipulate rpm files, rpm header files and
+hdlist files and manage them in memory.
+
+You will need perl version 5.6.1 or better to install this module.
+
+Copyright 2002 MandrakeSoft (François Pons <fpons@mandrakesoft.com>)
+
+This library is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
diff --git a/URPM.pm b/URPM.pm
new file mode 100644
index 0000000..82d4faf
--- /dev/null
+++ b/URPM.pm
@@ -0,0 +1,129 @@
+package URPM;
+
+use strict;
+use vars qw($VERSION @ISA);
+
+require DynaLoader;
+
+@ISA = qw(DynaLoader);
+$VERSION = '0.01';
+
+bootstrap URPM $VERSION;
+
+sub new {
+ my ($class) = @_;
+ bless {
+ depslist => [],
+ provides => {},
+ }, $class;
+}
+
+#- relocate depslist array id to use only the most recent packages,
+#- reorder info hashes to give only access to best packages.
+sub relocate_depslist {
+ my ($urpm, %options) = @_;
+ my $relocated_entries = 0;
+
+ #- reset names hash now, will be filled after.
+ $urpm->{names} = {};
+
+ foreach (@{$urpm->{depslist} || []}) {
+ #- remove access to info if arch is incompatible and only
+ #- take into account compatible arch to examine.
+ #- set names hash by prefering first better version,
+ #- then better release, then better arch.
+ if ($_->is_arch_compat) {
+ my $p = $urpm->{names}{$_->name};
+ if ($p) {
+ if ($_->compare_pkg($p) > 0) {
+ $urpm->{names}{$_->name} = $_;
+ ++$relocated_entries;
+ }
+ } else {
+ $urpm->{names}{$_->name} = $_;
+ }
+ } elsif ($_->arch ne 'src') {
+ #- the package is removed, make it invisible (remove id).
+ my $id = $_->set_id;
+
+ #- the architecture is not compatible, this means the package is dropped.
+ #- we have to remove its reference in provides.
+ foreach ($_->provides) {
+ delete $urpm->{provides}{$_}{$id};
+ }
+ }
+ }
+
+ #- relocate id used in depslist array, delete id if the package
+ #- should NOT be used.
+ #- if no entries have been relocated, we can safely avoid this computation.
+ if ($relocated_entries) {
+ foreach (@{$urpm->{depslist}}) {
+ my $p = $urpm->{names}{$_->name} or next;
+ $_->set_id($p->id);
+ }
+ }
+
+ $relocated_entries;
+}
+
+sub traverse {
+ my ($urpm, $callback) = @_;
+
+ if ($callback) {
+ foreach (@{$urpm->{depslist} || []}) {
+ $callback->($_);
+ }
+ }
+
+ scalar @{$urpm->{depslist} || []};
+}
+
+sub traverse_tag {
+ my ($urpm, $tag, $names, $callback) = @_;
+ my ($count, %names) = (0);
+
+ if (@{$names || []}) {
+ @names{@$names} = ();
+ if ($tag eq 'name') {
+ foreach (@{$urpm->{depslist} || []}) {
+ if (exists $names{$_->name}) {
+ $callback and $callback->($_);
+ ++$count;
+ }
+ }
+ } elsif ($tag eq 'whatprovides') {
+ foreach (@$names) {
+ foreach (keys %{$urpm->{provides}{$_} || {}}) {
+ $callback and $callback->($urpm->{depslist}[$_]);
+ ++$count;
+ }
+ }
+ } elsif ($tag eq 'whatrequires') {
+ foreach (@{$urpm->{depslist} || []}) {
+ if (grep { /^([^ \[]*)/ && exists $names{$1} } $_->requires) {
+ $callback and $callback->($_);
+ ++$count;
+ }
+ }
+ } elsif ($tag eq 'group') {
+ foreach (@{$urpm->{depslist} || []}) {
+ if (exists $names{$_->group}) {
+ $callback and $callback->($_);
+ ++$count;
+ }
+ }
+ } elsif ($tag eq 'triggeredby' || $tag eq 'path') {
+ foreach (@{$urpm->{depslist} || []}) {
+ if (grep { exists $names{$_} } $_->files) {
+ $callback and $callback->($_);
+ ++$count;
+ }
+ }
+ } else {
+ die "unknown tag";
+ }
+ }
+
+ $count;
+}
diff --git a/URPM.xs b/URPM.xs
new file mode 100644
index 0000000..eb60871
--- /dev/null
+++ b/URPM.xs
@@ -0,0 +1,1514 @@
+/* Copyright (c) 2002 MandrakeSoft <fpons@mandrakesoft.com>
+ * All rights reserved.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the same terms as Perl itself.
+ */
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#include <sys/utsname.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#undef Fflush
+#undef Mkdir
+#undef Stat
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+
+struct s_Package {
+ char *info;
+ char *requires;
+ char *obsoletes;
+ char *conflicts;
+ char *provides;
+ char *rates;
+ int id;
+ unsigned flag;
+ Header h;
+};
+
+typedef rpmdb URPM__DB;
+typedef struct s_Package* URPM__Package;
+
+#define FLAG_SELECTED 0x00ffffff
+#define FLAG_FORCE 0x01000000
+#define FLAG_INSTALLED 0x02000000
+#define FLAG_BASE 0x04000000
+#define FLAG_UPGRADE 0x20000000
+
+#define FILENAME_TAG 1000000
+#define FILESIZE_TAG 1000001
+
+#define FILTER_MODE_ALL_FILES 0
+#define FILTER_MODE_UPGRADE_FILES 1
+#define FILTER_MODE_CONF_FILES 2
+
+
+static void
+get_fullname_parts(URPM__Package pkg, char **name, char **version, char **release, char **arch, char **eos) {
+ char *_version = NULL, *_release = NULL, *_arch = NULL, *_eos = NULL;
+
+ if ((_eos = strchr(pkg->info, '@')) != NULL) {
+ *_eos = 0; /* mark end of string to enable searching backwards */
+ if ((_arch = strrchr(pkg->info, '.')) != NULL) {
+ *_arch = 0;
+ if ((release != NULL || version != NULL || name != NULL) && (_release = strrchr(pkg->info, '-')) != NULL) {
+ *_release = 0;
+ if ((version != NULL || name != NULL) && (_version = strrchr(pkg->info, '-')) != NULL) {
+ if (name != NULL) *name = pkg->info;
+ if (version != NULL) *version = _version + 1;
+ }
+ if (release != NULL) *release = _release + 1;
+ *_release = '-';
+ }
+ if (arch != NULL) *arch = _arch + 1;
+ *_arch = '.';
+ }
+ if (eos != NULL) *eos = _eos;
+ *_eos = '@';
+ }
+}
+
+static char *
+get_name(Header header, int_32 tag) {
+ int_32 type, count;
+ char *name;
+
+ headerGetEntry(header, tag, &type, (void **) &name, &count);
+ return name;
+}
+
+static int
+get_int(Header header, int_32 tag) {
+ int_32 type, count;
+ int *i;
+
+ headerGetEntry(header, tag, &type, (void **) &i, &count);
+ return i ? *i : 0;
+}
+
+static int
+print_list_entry(char *buff, int sz, char *name, int_32 flags, char *evr) {
+ int len = strlen(name);
+ char *p = buff;
+
+ if (len >= sz || !strncmp(name, "rpmlib(", 7)) return -1;
+ memcpy(p, name, len); p += len;
+
+ if (flags & RPMSENSE_PREREQ) {
+ if (p - buff + 3 >= sz) return -1;
+ memcpy(p, "[*]", 4); p += 3;
+ }
+ if (evr != NULL) {
+ len = strlen(evr);
+ if (len > 0) {
+ if (p - buff + 6 + len >= sz) return -1;
+ *p++ = '[';
+ if (flags & RPMSENSE_LESS) *p++ = '<';
+ if (flags & RPMSENSE_GREATER) *p++ = '>';
+ if (flags & RPMSENSE_EQUAL) *p++ = '=';
+ if ((flags & (RPMSENSE_LESS|RPMSENSE_EQUAL|RPMSENSE_GREATER)) == RPMSENSE_EQUAL) *p++ = '=';
+ *p++ = ' ';
+ memcpy(p, evr, len); p+= len;
+ *p++ = ']';
+ }
+ }
+ *p = 0; /* make sure to mark null char, Is it really necessary ? */
+
+ return p - buff;
+}
+
+/* hacked to allow function outside XS code part to return object on perl stack,
+ the function return SP which must be set to caller SP */
+static SV **
+xreturn_list_str(register SV **sp, char *s, Header header, int_32 tag_name, int_32 tag_flags, int_32 tag_version) {
+ if (s != NULL) {
+ char *ps = strchr(s, '@');
+ if (tag_flags && tag_version) {
+ while(ps != NULL) {
+ XPUSHs(sv_2mortal(newSVpv(s, ps-s)));
+ s = ps + 1; ps = strchr(s, '@');
+ }
+ XPUSHs(sv_2mortal(newSVpv(s, 0)));
+ } else {
+ char *eos;
+ while(ps != NULL) {
+ *ps = 0; eos = strchr(s, '['); if (!eos) eos = strchr(s, ' ');
+ XPUSHs(sv_2mortal(newSVpv(s, eos ? eos-s : ps-s)));
+ *ps = '@'; /* restore in memory modified char */
+ s = ps + 1; ps = strchr(s, '@');
+ }
+ eos = strchr(s, '['); if (!eos) eos = strchr(s, ' ');
+ XPUSHs(sv_2mortal(newSVpv(s, eos ? eos-s : 0)));
+ }
+ } else if (header) {
+ char buff[4096];
+ int_32 type, count;
+ char **list = NULL;
+ int_32 *flags = NULL;
+ char **list_evr = NULL;
+ int i;
+
+ headerGetEntry(header, tag_name, &type, (void **) &list, &count);
+ if (list) {
+ if (tag_flags) headerGetEntry(header, tag_flags, &type, (void **) &flags, &count);
+ if (tag_version) headerGetEntry(header, tag_version, &type, (void **) &list_evr, &count);
+ for(i = 0; i < count; i++) {
+ int len = print_list_entry(buff, sizeof(buff)-1, list[i], flags ? flags[i] : 0, list_evr ? list_evr[i] : NULL);
+ if (len < 0) continue;
+ XPUSHs(sv_2mortal(newSVpv(buff, len)));
+ }
+
+ free(list);
+ free(list_evr);
+ }
+ }
+ return sp;
+}
+
+static SV **
+xreturn_list_int_32(register SV **sp, Header header, int_32 tag_name) {
+ if (header) {
+ int_32 type, count;
+ int_32 *list = NULL;
+ int i;
+
+ headerGetEntry(header, tag_name, &type, (void **) &list, &count);
+ if (list) {
+ for(i = 0; i < count; i++) {
+ XPUSHs(sv_2mortal(newSViv(list[i])));
+ }
+ }
+ }
+ return sp;
+}
+
+static SV **
+xreturn_list_uint_16(register SV **sp, Header header, int_32 tag_name) {
+ if (header) {
+ int_32 type, count;
+ uint_16 *list = NULL;
+ int i;
+
+ headerGetEntry(header, tag_name, &type, (void **) &list, &count);
+ if (list) {
+ for(i = 0; i < count; i++) {
+ XPUSHs(sv_2mortal(newSViv(list[i])));
+ }
+ }
+ }
+ return sp;
+}
+
+static SV **
+xreturn_files(register SV **sp, Header header, int filter_mode) {
+ if (header) {
+ char buff[4096];
+ char *p, *s;
+ STRLEN len;
+ int_32 type, count;
+ char **list = NULL;
+ char **baseNames = NULL;
+ char **dirNames = NULL;
+ int_32 *dirIndexes = NULL;
+ int_32 *flags = NULL;
+ uint_16 *fmodes = NULL;
+ int i;
+
+ if (filter_mode) {
+ headerGetEntry(header, RPMTAG_FILEFLAGS, &type, (void **) &flags, &count);
+ headerGetEntry(header, RPMTAG_FILEMODES, &type, (void **) &fmodes, &count);
+ }
+
+ headerGetEntry(header, RPMTAG_BASENAMES, &type, (void **) &baseNames, &count);
+ headerGetEntry(header, RPMTAG_DIRINDEXES, &type, (void **) &dirIndexes, NULL);
+ headerGetEntry(header, RPMTAG_DIRNAMES, &type, (void **) &dirNames, NULL);
+ if (!baseNames || !dirNames || !dirIndexes) {
+ headerGetEntry(header, RPMTAG_OLDFILENAMES, &type, (void **) &list, &count);
+ if (!list) return sp;
+ }
+
+ for(i = 0; i < count; i++) {
+ if (list) {
+ s = list[i];
+ len = strlen(list[i]);
+ } else {
+ len = strlen(dirNames[dirIndexes[i]]);
+ if (len >= sizeof(buff)) continue;
+ memcpy(p = buff, dirNames[dirIndexes[i]], len + 1); p += len;
+ len = strlen(baseNames[i]);
+ if (p - buff + len >= sizeof(buff)) continue;
+ memcpy(p, baseNames[i], len + 1); p += len;
+ s = buff;
+ len = p-buff;
+ }
+
+ if (filter_mode) {
+ if ((filter_mode & FILTER_MODE_CONF_FILES) && flags && (flags[i] & RPMFILE_CONFIG) == 0) continue;
+ if ((filter_mode & FILTER_MODE_UPGRADE_FILES) && fmodes &&
+ (S_ISDIR(fmodes[i]) || S_ISLNK(fmodes[i]) ||
+ !strncmp(s, "/dev", 4) || !strncmp(s, "/etc/rc.d", 9) ||
+ len >= 3 && !strncmp(s+len-3, ".la", 3))) continue;
+ }
+
+ XPUSHs(sv_2mortal(newSVpv(s, len)));
+ }
+
+ free(baseNames);
+ free(dirNames);
+ free(list);
+ }
+ return sp;
+}
+
+static char *
+pack_list(Header header, int_32 tag_name, int_32 tag_flags, int_32 tag_version) {
+ char buff[65536];
+ int_32 type, count;
+ char **list = NULL;
+ int_32 *flags = NULL;
+ char **list_evr = NULL;
+ int i;
+ char *p = buff;
+
+ headerGetEntry(header, tag_name, &type, (void **) &list, &count);
+ if (list) {
+ if (tag_flags) headerGetEntry(header, tag_flags, &type, (void **) &flags, &count);
+ if (tag_version) headerGetEntry(header, tag_version, &type, (void **) &list_evr, &count);
+ for(i = 0; i < count; i++) {
+ int len = print_list_entry(p, sizeof(buff)-(p-buff)-1, list[i], flags ? flags[i] : 0, list_evr ? list_evr[i] : NULL);
+ if (len < 0) continue;
+ p += len;
+ *p++ = '@';
+ }
+ if (p > buff) p[-1] = 0;
+
+ free(list);
+ free(list_evr);
+ }
+
+ return p > buff ? memcpy(malloc(p-buff), buff, p-buff) : NULL;
+}
+
+static void
+pack_header(URPM__Package pkg) {
+ if (pkg->h) {
+ if (pkg->info == NULL) {
+ char buff[1024];
+ char *p = buff;
+ char *name = get_name(pkg->h, RPMTAG_NAME);
+ char *version = get_name(pkg->h, RPMTAG_VERSION);
+ char *release = get_name(pkg->h, RPMTAG_RELEASE);
+ char *arch = headerIsEntry(pkg->h, RPMTAG_SOURCEPACKAGE) ? "src" : get_name(pkg->h, RPMTAG_ARCH);
+ char *filename = get_name(pkg->h, FILENAME_TAG);
+
+ p += snprintf(buff, sizeof(buff), "%s-%s-%s.%s@%d@%d@%s@", name, version, release, arch,
+ get_int(pkg->h, RPMTAG_EPOCH), get_int(pkg->h, RPMTAG_SIZE), get_name(pkg->h, RPMTAG_GROUP));
+ if (filename) snprintf(p, sizeof(buff) - (p-buff), "%s-%s-%s.%s.rpm", name, version, release, arch);
+ if (!filename || !strcmp(p, filename)) {
+ p[-1] = 0;
+ } else {
+ p = p + snprintf(p, sizeof(buff) - (p-buff), "%s", filename);
+ }
+ pkg->info = memcpy(malloc(p-buff), buff, p-buff);
+ }
+ if (pkg->requires == NULL)
+ pkg->requires = pack_list(pkg->h, RPMTAG_REQUIRENAME, RPMTAG_REQUIREFLAGS, RPMTAG_REQUIREVERSION);
+ if (pkg->obsoletes == NULL)
+ pkg->obsoletes = pack_list(pkg->h, RPMTAG_OBSOLETENAME, RPMTAG_OBSOLETEFLAGS, RPMTAG_OBSOLETEVERSION);
+ if (pkg->conflicts == NULL)
+ pkg->conflicts = pack_list(pkg->h, RPMTAG_CONFLICTNAME, RPMTAG_CONFLICTFLAGS, RPMTAG_CONFLICTVERSION);
+ if (pkg->provides == NULL)
+ pkg->provides = pack_list(pkg->h, RPMTAG_PROVIDENAME, RPMTAG_PROVIDEFLAGS, RPMTAG_PROVIDEVERSION);
+
+ /* free header if it is not an iteration (id < 0) and remove reference to it */
+ if (pkg->id != -1) headerFree(pkg->h);
+ pkg->h = 0;
+ }
+}
+
+static void
+update_provide_entry(char *name, STRLEN len, int force, URPM__Package pkg, HV *provides) {
+ SV** isv;
+
+ if (!len) len = strlen(name);
+ if ((isv = hv_fetch(provides, name, len, force))) {
+ /* check if an entry has been found or created, it should so be updated */
+ if (isv && !SvROK(*isv) || SvTYPE(SvRV(*isv)) != SVt_PVHV) {
+ SV* choice_set = (SV*)newHV();
+ if (choice_set) {
+ SvREFCNT_dec(*isv); /* drop the old as we are changing it */
+ if (!(*isv = newRV_noinc(choice_set))) {
+ SvREFCNT_dec(choice_set);
+ *isv = &PL_sv_undef;
+ }
+ }
+ }
+ if (isv && *isv != &PL_sv_undef) {
+ char id[8];
+ STRLEN id_len = snprintf(id, sizeof(id), "%d", pkg->id);
+ hv_fetch((HV*)SvRV(*isv), id, id_len, 1);
+ }
+ }
+}
+
+static void
+update_provides(URPM__Package pkg, HV *provides) {
+ if (pkg->h) {
+ /* char buff[4096];
+ int_32 *flags = NULL;
+ char **list_evr = NULL; */
+ char *p;
+ int len;
+ int_32 type, count;
+ char **list = NULL;
+ int i;
+
+ /* examine requires for files which need to be marked in provides */
+ headerGetEntry(pkg->h, RPMTAG_REQUIRENAME, &type, (void **) &list, &count);
+ if (list) {
+ for (i = 0; i < count; ++i) {
+ len = strlen(list[i]);
+ if (!strncmp(list[i], "rpmlib(", 7)) continue;
+ if (list[i][0] == '/') hv_fetch(provides, list[i], len, 1);
+ }
+ }
+
+ /* update all provides */
+ /* headerGetEntry(pkg->h, RPMTAG_PROVIDEVERSION, &type, (void **) &list_evr, &count);
+ headerGetEntry(pkg->h, RPMTAG_PROVIDEFLAGS, &type, (void **) &flags, &count); */
+ headerGetEntry(pkg->h, RPMTAG_PROVIDENAME, &type, (void **) &list, &count);
+ if (list) {
+ for (i = 0; i < count; ++i) {
+ len = strlen(list[i]);
+ if (!strncmp(list[i], "rpmlib(", 7)) continue;
+ update_provide_entry(list[i], len, 1, pkg, provides);
+ }
+ }
+ } else {
+ char *ps, *s;
+
+ if ((s = pkg->requires) != NULL && *s != 0) {
+ ps = strchr(s, '@');
+ while(ps != NULL) {
+ if (s[0] == '/') hv_fetch(provides, s, ps-s, 1);
+ s = ps + 1; ps = strchr(s, '@');
+ }
+ if (s[0] == '/') hv_fetch(provides, s, strlen(s), 1);
+ }
+
+ if ((s = pkg->provides) != NULL && *s != 0) {
+ char *es;
+
+ ps = strchr(s, '@');
+ while(ps != NULL) {
+ *ps = 0; es = strchr(s, '['); if (!es) es = strchr(s, ' '); *ps = '@';
+ update_provide_entry(s, es != NULL ? es-s : ps-s, 1, pkg, provides);
+ s = ps + 1; ps = strchr(s, '@');
+ }
+ es = strchr(s, '['); if (!es) es = strchr(s, ' ');
+ update_provide_entry(s, es != NULL ? es-s : 0, 1, pkg, provides);
+ }
+ }
+}
+
+static void
+update_provides_files(URPM__Package pkg, HV *provides) {
+ if (pkg->h) {
+ STRLEN len;
+ int_32 type, count;
+ char **list = NULL;
+ char **baseNames = NULL;
+ char **dirNames = NULL;
+ int_32 *dirIndexes = NULL;
+ int i;
+
+ headerGetEntry(pkg->h, RPMTAG_BASENAMES, &type, (void **) &baseNames, &count);
+ headerGetEntry(pkg->h, RPMTAG_DIRINDEXES, &type, (void **) &dirIndexes, NULL);
+ headerGetEntry(pkg->h, RPMTAG_DIRNAMES, &type, (void **) &dirNames, NULL);
+ if (baseNames && dirNames && dirIndexes) {
+ char buff[4096];
+ char *p;
+
+ for(i = 0; i < count; i++) {
+ SV** isv;
+
+ len = strlen(dirNames[dirIndexes[i]]);
+ if (len >= sizeof(buff)) continue;
+ memcpy(p = buff, dirNames[dirIndexes[i]], len + 1); p += len;
+ len = strlen(baseNames[i]);
+ if (p - buff + len >= sizeof(buff)) continue;
+ memcpy(p, baseNames[i], len + 1); p += len;
+
+ update_provide_entry(buff, p-buff, 0, pkg, provides);
+ }
+
+ free(baseNames);
+ free(dirNames);
+ } else {
+ headerGetEntry(pkg->h, RPMTAG_OLDFILENAMES, &type, (void **) &list, &count);
+ if (list) {
+ for (i = 0; i < count; i++) {
+ len = strlen(list[i]);
+
+ update_provide_entry(list[i], len, 0, pkg, provides);
+ }
+
+ free(list);
+ }
+ }
+ }
+}
+
+int
+open_archive(char *filename, pid_t *pid) {
+ int fd;
+ struct {
+ char header[4];
+ char toc_d_count[4];
+ char toc_l_count[4];
+ char toc_f_count[4];
+ char toc_str_size[4];
+ char uncompress[40];
+ char trailer[4];
+ } buf;
+
+ fd = open(filename, O_RDONLY);
+ if (fd >= 0) {
+ lseek(fd, -sizeof(buf), SEEK_END);
+ if (read(fd, &buf, sizeof(buf)) != sizeof(buf) || strncmp(buf.header, "cz[0", 4) || strncmp(buf.trailer, "0]cz", 4)) {
+ /* this is not an archive, open it without magic, but first rewind at begin of file */
+ lseek(fd, 0, SEEK_SET);
+ } else {
+ /* this is an archive, create a pipe and fork for reading with uncompress defined inside */
+ int fdno[2];
+
+ if (!pipe(fdno)) {
+ if ((*pid = fork()) != 0) {
+ fd_set readfds;
+ struct timeval timeout;
+
+ FD_ZERO(&readfds);
+ FD_SET(fdno[0], &readfds);
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ select(fdno[0]+1, &readfds, NULL, NULL, &timeout);
+
+ close(fd);
+ fd = fdno[0];
+ close(fdno[1]);
+ } else {
+ char *unpacker[22]; /* enough for 40 bytes in uncompress to never overbuf */
+ char *p = buf.uncompress;
+ int ip = 0;
+ char *ld_loader = getenv("LD_LOADER");
+
+ if (ld_loader && *ld_loader) {
+ unpacker[ip++] = ld_loader;
+ }
+
+ buf.trailer[0] = 0; /* make sure end-of-string is right */
+ while (*p) {
+ if (*p == ' ' || *p == '\t') *p++ = 0;
+ else {
+ unpacker[ip++] = p;
+ while (*p && *p != ' ' && *p != '\t') ++p;
+ }
+ }
+ unpacker[ip] = NULL; /* needed for execlp */
+
+ lseek(fd, 0, SEEK_SET);
+ dup2(fd, STDIN_FILENO); close(fd);
+ dup2(fdno[1], STDOUT_FILENO); close(fdno[1]);
+ fd = open("/dev/null", O_WRONLY);
+ dup2(fd, STDERR_FILENO); close(fd);
+ execvp(unpacker[0], unpacker);
+ exit(1);
+ }
+ } else {
+ close(fd);
+ fd = -1;
+ }
+ }
+ }
+ return fd;
+}
+
+static void
+parse_line(AV *depslist, HV *provides, URPM__Package pkg, char *buff) {
+ char *tag, *data;
+ int data_len;
+
+ if ((tag = strchr(buff, '@')) != NULL && (data = strchr(tag+1, '@')) != NULL) {
+ *tag++ = *data++ = 0;
+ data_len = 1+strlen(data);
+ if (!strcmp(tag, "info")) {
+ pkg->info = memcpy(malloc(data_len), data, data_len);
+ pkg->id = 1 + av_len(depslist);
+ if (provides) update_provides(pkg, provides);
+ av_push(depslist, sv_setref_pv(newSVpv("", 0), "URPM::Package",
+ memcpy(malloc(sizeof(struct s_Package)), pkg, sizeof(struct s_Package))));
+ memset(pkg, 0, sizeof(struct s_Package));
+ } else if (!strcmp(tag, "requires")) {
+ free(pkg->requires); pkg->requires = memcpy(malloc(data_len), data, data_len);
+ } else if (!strcmp(tag, "obsoletes")) {
+ free(pkg->obsoletes); pkg->obsoletes = memcpy(malloc(data_len), data, data_len);
+ } else if (!strcmp(tag, "conflicts")) {
+ free(pkg->conflicts); pkg->conflicts = memcpy(malloc(data_len), data, data_len);
+ } else if (!strcmp(tag, "provides")) {
+ free(pkg->provides); pkg->provides = memcpy(malloc(data_len), data, data_len);
+ }
+ }
+}
+
+static void
+read_config_files() {
+ static int already = 0;
+
+ if (!already) {
+ rpmReadConfigFiles(NULL, NULL);
+ already = 1;
+ }
+}
+
+static void callback_empty(void) {}
+
+MODULE = URPM PACKAGE = URPM::Package PREFIX = Pkg_
+
+void
+Pkg_DESTROY(pkg)
+ URPM::Package pkg
+ CODE:
+ free(pkg->info);
+ free(pkg->requires);
+ free(pkg->obsoletes);
+ free(pkg->conflicts);
+ free(pkg->provides);
+ free(pkg->rates);
+ if (pkg->h && pkg->id != -1) headerFree(pkg->h);
+ free(pkg);
+
+void
+Pkg_name(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *name;
+ char *version;
+
+ get_fullname_parts(pkg, &name, &version, NULL, NULL, NULL);
+ XPUSHs(sv_2mortal(newSVpv(name, version-name-1)));
+ } else if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_NAME), 0)));
+ }
+
+void
+Pkg_version(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *version;
+ char *release;
+
+ get_fullname_parts(pkg, NULL, &version, &release, NULL, NULL);
+ XPUSHs(sv_2mortal(newSVpv(version, release-version-1)));
+ } else if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_VERSION), 0)));
+ }
+
+void
+Pkg_release(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *release;
+ char *arch;
+
+ get_fullname_parts(pkg, NULL, NULL, &release, &arch, NULL);
+ XPUSHs(sv_2mortal(newSVpv(release, arch-release-1)));
+ } else if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_RELEASE), 0)));
+ }
+
+void
+Pkg_arch(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *arch;
+ char *eos;
+
+ get_fullname_parts(pkg, NULL, NULL, NULL, &arch, &eos);
+ XPUSHs(sv_2mortal(newSVpv(arch, eos-arch)));
+ } else if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(headerIsEntry(pkg->h, RPMTAG_SOURCEPACKAGE) ? "src" : get_name(pkg->h, RPMTAG_ARCH), 0)));
+ }
+
+int
+Pkg_is_arch_compat(pkg)
+ URPM::Package pkg
+ CODE:
+ read_config_files();
+ if (pkg->info) {
+ char *arch;
+ char *eos;
+
+ get_fullname_parts(pkg, NULL, NULL, NULL, &arch, &eos);
+ *eos = 0;
+ RETVAL = rpmMachineScore(RPM_MACHTABLE_INSTARCH, arch);
+ *eos = '@';
+ } else if (pkg->h && !headerIsEntry(pkg->h, RPMTAG_SOURCEPACKAGE)) {
+ RETVAL = rpmMachineScore(RPM_MACHTABLE_INSTARCH, get_name(pkg->h, RPMTAG_ARCH));
+ } else {
+ RETVAL = 0;
+ }
+
+void
+Pkg_summary(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_SUMMARY), 0)));
+ }
+
+void
+Pkg_description(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_DESCRIPTION), 0)));
+ }
+
+void
+Pkg_sourcerpm(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_SOURCERPM), 0)));
+ }
+
+void
+Pkg_fullname(pkg)
+ URPM::Package pkg
+ PREINIT:
+ I32 gimme = GIMME_V;
+ PPCODE:
+ if (pkg->info) {
+ if (gimme == G_SCALAR) {
+ char *eos;
+ if ((eos = strchr(pkg->info, '@')) != NULL) {
+ XPUSHs(sv_2mortal(newSVpv(pkg->info, eos-pkg->info)));
+ }
+ } else if (gimme == G_ARRAY) {
+ char *name, *version, *release, *arch, *eos;
+ get_fullname_parts(pkg, &name, &version, &release, &arch, &eos);
+ EXTEND(SP, 4);
+ PUSHs(sv_2mortal(newSVpv(name, version-name-1)));
+ PUSHs(sv_2mortal(newSVpv(version, release-version-1)));
+ PUSHs(sv_2mortal(newSVpv(release, arch-release-1)));
+ PUSHs(sv_2mortal(newSVpv(arch, eos-arch)));
+ }
+ } else if (pkg->h) {
+ char *name = get_name(pkg->h, RPMTAG_NAME);
+ char *version = get_name(pkg->h, RPMTAG_VERSION);
+ char *release = get_name(pkg->h, RPMTAG_RELEASE);
+ char *arch = headerIsEntry(pkg->h, RPMTAG_SOURCEPACKAGE) ? "src" : get_name(pkg->h, RPMTAG_ARCH);
+
+ if (gimme == G_SCALAR) {
+ XPUSHs(sv_2mortal(newSVpvf("%s-%s-%s.%s", name, version, release, arch)));
+ } else if (gimme == G_ARRAY) {
+ EXTEND(SP, 4);
+ PUSHs(sv_2mortal(newSVpv(name, 0)));
+ PUSHs(sv_2mortal(newSVpv(version, 0)));
+ PUSHs(sv_2mortal(newSVpv(release, 0)));
+ PUSHs(sv_2mortal(newSVpv(arch, 0)));
+ }
+ }
+
+int
+Pkg_epoch(pkg)
+ URPM::Package pkg
+ CODE:
+ if (pkg->info) {
+ char *s, *eos;
+
+ if ((s = strchr(pkg->info, '@')) != NULL) {
+ if ((eos = strchr(s+1, '@')) != NULL) *eos = 0; /* mark end of string to enable searching backwards */
+ RETVAL = atoi(s+1);
+ if (eos != NULL) *eos = '@';
+ } else {
+ RETVAL = 0;
+ }
+ } else if (pkg->h) {
+ RETVAL = get_int(pkg->h, RPMTAG_SERIAL);
+ } else RETVAL = 0;
+ OUTPUT:
+ RETVAL
+
+int
+Pkg_compare_pkg(lpkg, rpkg)
+ URPM::Package lpkg
+ URPM::Package rpkg
+ PREINIT:
+ int compare = 0;
+ int lepoch;
+ char *lversion;
+ char *lrelease;
+ char *leos;
+ int repoch;
+ char *rversion;
+ char *rrelease;
+ char *reos;
+ CODE:
+ if (lpkg->info) {
+ char *s;
+
+ if ((s = strchr(lpkg->info, '@')) != NULL) {
+ if ((leos = strchr(s+1, '@')) != NULL) *leos = 0; /* mark end of string to enable searching backwards */
+ lepoch = atoi(s+1);
+ if (leos != NULL) *leos = '@';
+ } else {
+ lepoch = 0;
+ }
+ get_fullname_parts(lpkg, NULL, &lversion, &lrelease, &leos, NULL);
+ /* temporaly mark end of each substring */
+ lrelease[-1] = 0;
+ leos[-1] = 0;
+ } else if (lpkg->h) {
+ lepoch = get_int(lpkg->h, RPMTAG_EPOCH);
+ lversion = get_name(lpkg->h, RPMTAG_VERSION);
+ lrelease = get_name(lpkg->h, RPMTAG_RELEASE);
+ } else croak("undefined package");
+ if (rpkg->info) {
+ char *s;
+
+ if ((s = strchr(rpkg->info, '@')) != NULL) {
+ if ((reos = strchr(s+1, '@')) != NULL) *reos = 0; /* mark end of string to enable searching backwards */
+ repoch = atoi(s+1);
+ if (reos != NULL) *reos = '@';
+ } else {
+ repoch = 0;
+ }
+ get_fullname_parts(rpkg, NULL, &rversion, &rrelease, &reos, NULL);
+ /* temporaly mark end of each substring */
+ rrelease[-1] = 0;
+ reos[-1] = 0;
+ } else if (rpkg->h) {
+ repoch = get_int(rpkg->h, RPMTAG_EPOCH);
+ rversion = get_name(rpkg->h, RPMTAG_VERSION);
+ rrelease = get_name(rpkg->h, RPMTAG_RELEASE);
+ } else {
+ /* restore info string modified */
+ if (lpkg->info) {
+ lrelease[-1] = '-';
+ leos[-1] = '.';
+ }
+ croak("undefined package");
+ }
+ compare = lepoch - repoch;
+ if (!compare) {
+ compare = rpmvercmp(lversion, rversion);
+ if (!compare)
+ compare = rpmvercmp(lrelease, rrelease);
+ }
+ /* restore info string modified */
+ if (lpkg->info) {
+ lrelease[-1] = '-';
+ leos[-1] = '.';
+ }
+ if (rpkg->info) {
+ rrelease[-1] = '-';
+ reos[-1] = '.';
+ }
+ RETVAL = compare;
+ OUTPUT:
+ RETVAL
+
+int
+Pkg_compare(pkg, evr)
+ URPM::Package pkg
+ char *evr
+ PREINIT:
+ int compare = 0;
+ int _epoch;
+ char *_version;
+ char *_release;
+ char *_eos;
+ CODE:
+ if (pkg->info) {
+ char *s;
+
+ if ((s = strchr(pkg->info, '@')) != NULL) {
+ if ((_eos = strchr(s+1, '@')) != NULL) *_eos = 0; /* mark end of string to enable searching backwards */
+ _epoch = atoi(s+1);
+ if (_eos != NULL) *_eos = '@';
+ } else {
+ _epoch = 0;
+ }
+ get_fullname_parts(pkg, NULL, &_version, &_release, &_eos, NULL);
+ /* temporaly mark end of each substring */
+ _release[-1] = 0;
+ _eos[-1] = 0;
+ } else if (pkg->h) {
+ _epoch = get_int(pkg->h, RPMTAG_EPOCH);
+ } else croak("undefined package");
+ if (!compare) {
+ char *epoch, *version, *release;
+
+ /* extract epoch and version from evr */
+ version = evr;
+ while (*version && isdigit(*version)) version++;
+ if (*version == ':') {
+ epoch = evr;
+ *version++ = 0;
+ if (!*epoch) epoch = "0";
+ compare = _epoch - (*epoch ? atoi(epoch) : 0);
+ version[-1] = ':'; /* restore in memory modification */
+ } else {
+ /* there is no epoch defined, so no check on epoch and assume equality */
+ version = evr;
+ }
+ if (!compare) {
+ if (!pkg->info)
+ _version = get_name(pkg->h, RPMTAG_VERSION);
+ /* continue extracting release if any */
+ if ((release = strrchr(version, '-')) != NULL) {
+ *release++ = 0;
+ compare = rpmvercmp(_version, version);
+ if (!compare) {
+ /* need to compare with release here */
+ if (!pkg->info)
+ _release = get_name(pkg->h, RPMTAG_RELEASE);
+ compare = rpmvercmp(_release, release);
+ }
+ release[-1] = '-'; /* restore in memory modification */
+ } else {
+ compare = rpmvercmp(_version, version);
+ }
+ }
+ }
+ /* restore info string modified */
+ if (pkg->info) {
+ _release[-1] = '-';
+ _eos[-1] = '.';
+ }
+ RETVAL = compare;
+ OUTPUT:
+ RETVAL
+
+int
+Pkg_size(pkg)
+ URPM::Package pkg
+ CODE:
+ if (pkg->info) {
+ char *s, *eos;
+
+ if ((s = strchr(pkg->info, '@')) != NULL && (s = strchr(s+1, '@')) != NULL) {
+ if ((eos = strchr(s+1, '@')) != NULL) *eos = 0; /* mark end of string to enable searching backwards */
+ RETVAL = atoi(s+1);
+ if (eos != NULL) *eos = '@';
+ } else {
+ RETVAL = 0;
+ }
+ } else if (pkg->h) {
+ RETVAL = get_int(pkg->h, RPMTAG_SIZE);
+ } else RETVAL = 0;
+ OUTPUT:
+ RETVAL
+
+void
+Pkg_group(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *s;
+
+ if ((s = strchr(pkg->info, '@')) != NULL && (s = strchr(s+1, '@')) != NULL && (s = strchr(s+1, '@')) != NULL) {
+ char *eos = strchr(s+1, '@');
+ XPUSHs(sv_2mortal(newSVpv(s+1, eos != NULL ? eos-s-1 : 0)));
+ }
+ } else if (pkg->h) {
+ XPUSHs(sv_2mortal(newSVpv(get_name(pkg->h, RPMTAG_GROUP), 0)));
+ }
+
+void
+Pkg_filename(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->info) {
+ char *s, *eon, *eos;
+
+ if ((eon = strchr(pkg->info, '@')) != NULL) {
+ if ((s = strchr(eon+1, '@')) != NULL && (s = strchr(s+1, '@')) != NULL && (s = strchr(s+1, '@')) != NULL) {
+ eos = strchr(s+1, '@');
+ XPUSHs(sv_2mortal(newSVpv(s+1, eos != NULL ? eos-s-1 : 0)));
+ } else {
+ char savbuf[4];
+ memcpy(savbuf, eon, 4); /* there should be at least epoch and size described so (@0@0 minimum) */
+ memcpy(eon, ".rpm", 4);
+ XPUSHs(sv_2mortal(newSVpv(pkg->info, eon-pkg->info+4)));
+ memcpy(eon, savbuf, 4);
+ }
+ }
+ } else if (pkg->h) {
+ char *filename = get_name(pkg->h, FILENAME_TAG);
+
+ if (filename != NULL)
+ XPUSHs(sv_2mortal(newSVpv(filename, 0)));
+ }
+
+void
+Pkg_id(pkg)
+ URPM::Package pkg
+ PPCODE:
+ if (pkg->id >= 0) {
+ XPUSHs(sv_2mortal(newSViv(pkg->id)));
+ }
+
+void
+Pkg_set_id(pkg, id=-1)
+ URPM::Package pkg
+ int id
+ PPCODE:
+ if (pkg->id >= 0) {
+ XPUSHs(sv_2mortal(newSViv(pkg->id)));
+ }
+ pkg->id = id >= 0 ? id : -2; /* -1 should not be used since it marks non freeable header */
+
+void
+Pkg_requires(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->requires, pkg->h, RPMTAG_REQUIRENAME, RPMTAG_REQUIREFLAGS, RPMTAG_REQUIREVERSION);
+
+void
+Pkg_requires_nosense(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->requires, pkg->h, RPMTAG_REQUIRENAME, 0, 0);
+
+void
+Pkg_obsoletes(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->obsoletes, pkg->h, RPMTAG_OBSOLETENAME, RPMTAG_OBSOLETEFLAGS, RPMTAG_OBSOLETEVERSION);
+
+void
+Pkg_obsoletes_nosense(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->obsoletes, pkg->h, RPMTAG_OBSOLETENAME, 0, 0);
+
+void
+Pkg_conflicts(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->conflicts, pkg->h, RPMTAG_CONFLICTNAME, RPMTAG_CONFLICTFLAGS, RPMTAG_CONFLICTVERSION);
+
+void
+Pkg_conflicts_nosense(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->conflicts, pkg->h, RPMTAG_CONFLICTNAME, 0, 0);
+
+void
+Pkg_provides(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->provides, pkg->h, RPMTAG_PROVIDENAME, RPMTAG_PROVIDEFLAGS, RPMTAG_PROVIDEVERSION);
+
+void
+Pkg_provides_nosense(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, pkg->provides, pkg->h, RPMTAG_PROVIDENAME, 0, 0);
+
+void
+Pkg_files(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_files(SP, pkg->h, 0);
+
+void
+Pkg_files_md5sum(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, NULL, pkg->h, RPMTAG_FILEMD5S, 0, 0);
+
+void
+Pkg_files_owner(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, NULL, pkg->h, RPMTAG_FILEUSERNAME, 0, 0);
+
+void
+Pkg_files_group(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_str(SP, NULL, pkg->h, RPMTAG_FILEGROUPNAME, 0, 0);
+
+void
+Pkg_files_mtime(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_int_32(SP, pkg->h, RPMTAG_FILEMTIMES);
+
+void
+Pkg_files_size(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_int_32(SP, pkg->h, RPMTAG_FILESIZES);
+
+void
+Pkg_files_uid(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_int_32(SP, pkg->h, RPMTAG_FILEUIDS);
+
+void
+Pkg_files_gid(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_int_32(SP, pkg->h, RPMTAG_FILEGIDS);
+
+void
+Pkg_files_mode(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_list_uint_16(SP, pkg->h, RPMTAG_FILEMODES);
+
+void
+Pkg_conf_files(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_files(SP, pkg->h, FILTER_MODE_CONF_FILES);
+
+void
+Pkg_upgrade_files(pkg)
+ URPM::Package pkg
+ PPCODE:
+ SP = xreturn_files(SP, pkg->h, FILTER_MODE_UPGRADE_FILES);
+
+void
+Pkg_pack_header(pkg)
+ URPM::Package pkg
+ CODE:
+ pack_header(pkg);
+
+void
+Pkg_build_info(pkg, fileno, provides_files=NULL)
+ URPM::Package pkg
+ int fileno
+ char *provides_files
+ CODE:
+ if (pkg->info) {
+ char buff[65536];
+ int size;
+
+ /* info line should be the last to be written */
+ if (pkg->provides && *pkg->provides) {
+ size = snprintf(buff, sizeof(buff), "@provides@%s\n", pkg->provides);
+ if (size < sizeof(buff)) {
+ if (provides_files && *provides_files) {
+ --size;
+ size += snprintf(buff+size, sizeof(buff)-size, "@%s\n", provides_files);
+ }
+ write(fileno, buff, size);
+ }
+ }
+ if (pkg->conflicts && *pkg->conflicts) {
+ size = snprintf(buff, sizeof(buff), "@conflicts@%s\n", pkg->conflicts);
+ if (size < sizeof(buff)) write(fileno, buff, size);
+ }
+ if (pkg->obsoletes && *pkg->obsoletes) {
+ size = snprintf(buff, sizeof(buff), "@obsoletes@%s\n", pkg->obsoletes);
+ if (size < sizeof(buff)) write(fileno, buff, size);
+ }
+ if (pkg->requires && *pkg->requires) {
+ size = snprintf(buff, sizeof(buff), "@requires@%s\n", pkg->requires);
+ if (size < sizeof(buff)) write(fileno, buff, size);
+ }
+ size = snprintf(buff, sizeof(buff), "@info@%s\n", pkg->info);
+ write(fileno, buff, size);
+ } else croak("no info available for package");
+
+void
+Pkg_build_header(pkg, fileno)
+ URPM::Package pkg
+ int fileno
+ CODE:
+ if (pkg->h) {
+ FD_t fd;
+
+ fd = fdDup(fileno);
+ headerWrite(fd, pkg->h, HEADER_MAGIC_YES);
+ fdClose(fd);
+ } else croak("no header available for package");
+
+int
+Pkg_flag_base(pkg)
+ URPM::Package pkg
+ CODE:
+ RETVAL = pkg->flag & FLAG_BASE;
+ OUTPUT:
+ RETVAL
+
+int
+Pkg_set_flag_base(pkg, value=1)
+ URPM::Package pkg
+ int value
+ CODE:
+ RETVAL = pkg->flag & FLAG_BASE;
+ if (value) pkg->flag |= FLAG_BASE;
+ else pkg->flag &= ~FLAG_BASE;
+ OUTPUT:
+ RETVAL
+
+
+MODULE = URPM PACKAGE = URPM::DB PREFIX = Db_
+
+URPM::DB
+Db_open(prefix="/")
+ char *prefix
+ PREINIT:
+ rpmdb db;
+ rpmErrorCallBackType old_cb;
+ CODE:
+ read_config_files();
+ old_cb = rpmErrorSetCallback(callback_empty);
+ rpmSetVerbosity(RPMMESS_FATALERROR);
+ RETVAL = rpmdbOpen(prefix, &db, O_RDONLY, 0644) == 0 ? db : NULL;
+ rpmErrorSetCallback(old_cb);
+ rpmSetVerbosity(RPMMESS_NORMAL);
+ OUTPUT:
+ RETVAL
+
+URPM::DB
+Db_open_rw(prefix="/")
+ char *prefix
+ PREINIT:
+ rpmdb db;
+ rpmErrorCallBackType old_cb;
+ CODE:
+ read_config_files();
+ old_cb = rpmErrorSetCallback(callback_empty);
+ rpmSetVerbosity(RPMMESS_FATALERROR);
+ RETVAL = rpmdbOpen(prefix, &db, O_RDWR | O_CREAT, 0644) == 0 ? db : NULL;
+ rpmErrorSetCallback(old_cb);
+ rpmSetVerbosity(RPMMESS_NORMAL);
+ OUTPUT:
+ RETVAL
+
+void
+Db_DESTROY(db)
+ URPM::DB db
+ CODE:
+ rpmdbClose(db);
+
+int
+Db_traverse(db,callback)
+ URPM::DB db
+ SV *callback
+ PREINIT:
+ Header header;
+ rpmdbMatchIterator mi;
+ int count = 0;
+ CODE:
+ mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, NULL, 0);
+ while (header = rpmdbNextIterator(mi)) {
+ if (SvROK(callback)) {
+ dSP;
+ URPM__Package pkg = calloc(1, sizeof(struct s_Package));
+
+ pkg->id = -1; /* this is not a real package where header should not be freed */
+ pkg->h = header;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(sv_setref_pv(newSVpv("", 0), "URPM::Package", pkg)));
+ PUTBACK;
+
+ call_sv(callback, G_DISCARD | G_SCALAR);
+ pkg->h = 0; /* avoid using it anymore, in case it has been copied inside callback */
+
+ FREETMPS;
+ LEAVE;
+ }
+ ++count;
+ }
+ rpmdbFreeIterator(mi);
+ RETVAL = count;
+ OUTPUT:
+ RETVAL
+
+int
+Db_traverse_tag(db,tag,names,callback)
+ URPM::DB db
+ char *tag
+ SV *names
+ SV *callback
+ PREINIT:
+ Header header;
+ rpmdbMatchIterator mi;
+ int count = 0;
+ CODE:
+ if (SvROK(names) && SvTYPE(SvRV(names)) == SVt_PVAV) {
+ AV* names_av = (AV*)SvRV(names);
+ int len = av_len(names_av);
+ SV** isv;
+ int i, rpmtag;
+
+ if (!strcmp(tag, "name"))
+ rpmtag = RPMTAG_NAME;
+ else if (!strcmp(tag, "whatprovides"))
+ rpmtag = RPMTAG_PROVIDENAME;
+ else if (!strcmp(tag, "whatrequires"))
+ rpmtag = RPMTAG_REQUIRENAME;
+ else if (!strcmp(tag, "group"))
+ rpmtag = RPMTAG_GROUP;
+ else if (!strcmp(tag, "triggeredby"))
+ rpmtag = RPMTAG_BASENAMES;
+ else if (!strcmp(tag, "path"))
+ rpmtag = RPMTAG_BASENAMES;
+ else croak("unknown tag");
+
+ for (i = 0; i <= len; ++i) {
+ STRLEN str_len;
+ SV **isv = av_fetch(names_av, i, 0);
+ char *name = SvPV(*isv, str_len);
+
+ mi = rpmdbInitIterator((rpmdb)db, rpmtag, name, str_len);
+ while (header = rpmdbNextIterator(mi)) {
+ if (SvROK(callback)) {
+ dSP;
+ URPM__Package pkg = calloc(1, sizeof(struct s_Package));
+
+ pkg->id = -1; /* this is not a real package where header should not be freed */
+ pkg->h = header;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(sv_setref_pv(newSVpv("", 0), "URPM::Package", pkg)));
+ PUTBACK;
+
+ call_sv(callback, G_DISCARD | G_SCALAR);
+ pkg->h = 0; /* avoid using it anymore, in case it has been copied inside callback */
+
+ FREETMPS;
+ LEAVE;
+ }
+ ++count;
+ }
+ rpmdbFreeIterator(mi);
+ }
+ } else croak("bad arguments list");
+ RETVAL = count;
+ OUTPUT:
+ RETVAL
+
+MODULE = URPM PACKAGE = URPM PREFIX = Urpm_
+
+void
+Urpm_parse_synthesis(urpm, filename)
+ SV *urpm
+ char *filename
+ PPCODE:
+ if (SvROK(urpm) && SvTYPE(SvRV(urpm)) == SVt_PVHV) {
+ SV **fdepslist = hv_fetch((HV*)SvRV(urpm), "depslist", 8, 0);
+ AV *depslist = fdepslist && SvROK(*fdepslist) && SvTYPE(SvRV(*fdepslist)) == SVt_PVAV ? (AV*)SvRV(*fdepslist) : NULL;
+ SV **fprovides = hv_fetch((HV*)SvRV(urpm), "provides", 8, 0);
+ HV *provides = fprovides && SvROK(*fprovides) && SvTYPE(SvRV(*fprovides)) == SVt_PVHV ? (HV*)SvRV(*fprovides) : NULL;
+
+ if (depslist != NULL) {
+ char buff[65536];
+ char *p, *eol;
+ int buff_len;
+ struct s_Package pkg;
+ gzFile f;
+ int start_id = 1 + av_len(depslist);
+ int count = 1;
+
+ if ((f = gzopen(filename, "rb")) != NULL) {
+ memset(&pkg, 0, sizeof(struct s_Package));
+ buff[sizeof(buff)-1] = 0;
+ p = buff;
+ while ((buff_len = gzread(f, p, sizeof(buff)-1-(p-buff)) + (p-buff)) != 0) {
+ p = buff;
+ if ((eol = strchr(p, '\n')) != NULL) {
+ do {
+ *eol++ = 0;
+ parse_line(depslist, provides, &pkg, p);
+ p = eol;
+ } while ((eol = strchr(p, '\n')) != NULL);
+ } else {
+ /* a line larger than sizeof(buff) has been encountered, bad file problably */
+ break;
+ }
+ if (gzeof(f)) {
+ parse_line(depslist, provides, &pkg, p);
+ break;
+ } else {
+ memmove(buff, p, buff_len-(p-buff));
+ p = &buff[buff_len-(p-buff)];
+ }
+ }
+ gzclose(f);
+ if (av_len(depslist) >= start_id) {
+ XPUSHs(sv_2mortal(newSViv(start_id)));
+ XPUSHs(sv_2mortal(newSViv(av_len(depslist))));
+ }
+ } else croak("unable to uncompress synthesis file");
+ } else croak("first argument should contains a depslist ARRAY reference");
+ } else croak("first argument should be a reference to HASH");
+
+void
+Urpm_parse_hdlist(urpm, filename, packing=0)
+ SV *urpm
+ char *filename
+ int packing
+ PPCODE:
+ if (SvROK(urpm) && SvTYPE(SvRV(urpm)) == SVt_PVHV) {
+ SV **fdepslist = hv_fetch((HV*)SvRV(urpm), "depslist", 8, 0);
+ AV *depslist = fdepslist && SvROK(*fdepslist) && SvTYPE(SvRV(*fdepslist)) == SVt_PVAV ? (AV*)SvRV(*fdepslist) : NULL;
+ SV **fprovides = hv_fetch((HV*)SvRV(urpm), "provides", 8, 0);
+ HV *provides = fprovides && SvROK(*fprovides) && SvTYPE(SvRV(*fprovides)) == SVt_PVHV ? (HV*)SvRV(*fprovides) : NULL;
+
+ if (depslist != NULL) {
+ pid_t pid;
+ int d;
+ FD_t fd;
+
+ d = open_archive(filename, &pid);
+ fd = fdDup(d);
+ close(d);
+
+ if (fdFileno(fd) >= 0) {
+ Header header;
+ int count;
+ int start_id = 1 + av_len(depslist);
+
+ for (count = 0; count < 20 && (header=headerRead(fd, HEADER_MAGIC_YES)) == 0; ++count) {
+ struct timeval timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 10000;
+ select(0, NULL, NULL, NULL, &timeout);
+ }
+ while (header != 0) {
+ struct s_Package pkg;
+
+ memset(&pkg, 0, sizeof(struct s_Package));
+ pkg.id = 1 + av_len(depslist);
+ pkg.h = header;
+ if (provides) {
+ update_provides(&pkg, provides);
+ update_provides_files(&pkg, provides);
+ }
+ if (packing) pack_header(&pkg);
+ av_push(depslist, sv_setref_pv(newSVpv("", 0), "URPM::Package",
+ memcpy(malloc(sizeof(struct s_Package)), &pkg, sizeof(struct s_Package))));
+
+ header=headerRead(fd, HEADER_MAGIC_YES);
+ }
+ fdClose(fd);
+ if (pid) {
+ kill(pid, SIGTERM);
+ waitpid(pid, NULL, 0);
+ pid = 0;
+ }
+ if (av_len(depslist) >= start_id) {
+ XPUSHs(sv_2mortal(newSViv(start_id)));
+ XPUSHs(sv_2mortal(newSViv(av_len(depslist))));
+ }
+ } else croak("cannot open hdlist file");
+ } else croak("first argument should contains a depslist ARRAY reference");
+ } else croak("first argument should be a reference to HASH");
+
+void
+Urpm_parse_rpm(urpm, filename, packing=0)
+ SV *urpm
+ char *filename
+ int packing
+ PPCODE:
+ if (SvROK(urpm) && SvTYPE(SvRV(urpm)) == SVt_PVHV) {
+ SV **fdepslist = hv_fetch((HV*)SvRV(urpm), "depslist", 8, 0);
+ AV *depslist = fdepslist && SvROK(*fdepslist) && SvTYPE(SvRV(*fdepslist)) == SVt_PVAV ? (AV*)SvRV(*fdepslist) : NULL;
+ SV **fprovides = hv_fetch((HV*)SvRV(urpm), "provides", 8, 0);
+ HV *provides = fprovides && SvROK(*fprovides) && SvTYPE(SvRV(*fprovides)) == SVt_PVHV ? (HV*)SvRV(*fprovides) : NULL;
+
+ if (depslist != NULL) {
+ FD_t fd = fdOpen(filename, O_RDONLY, 0666);
+ Header header;
+ int isSource;
+
+ if (fdFileno(fd) >= 0) {
+ if (rpmReadPackageHeader(fd, &header, &isSource, NULL, NULL) == 0) {
+ struct s_Package pkg;
+ struct stat sb;
+ char *basename;
+ int_32 size;
+
+ basename = strrchr(filename, '/');
+ fstat(fdFileno(fd), &sb);
+ size = sb.st_size;
+ headerAddEntry(header, FILENAME_TAG, RPM_STRING_TYPE, basename != NULL ? basename + 1 : filename, 1);
+ headerAddEntry(header, FILESIZE_TAG, RPM_INT32_TYPE, &size, 1);
+
+ memset(&pkg, 0, sizeof(struct s_Package));
+ pkg.id = 1 + av_len(depslist);
+ pkg.h = header;
+ if (provides) {
+ update_provides(&pkg, provides);
+ update_provides_files(&pkg, provides);
+ }
+ if (packing) pack_header(&pkg);
+ else {
+ headerRemoveEntry(pkg.h, RPMTAG_POSTIN);
+ headerRemoveEntry(pkg.h, RPMTAG_POSTUN);
+ headerRemoveEntry(pkg.h, RPMTAG_PREIN);
+ headerRemoveEntry(pkg.h, RPMTAG_PREUN);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEUSERNAME);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEGROUPNAME);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEVERIFYFLAGS);
+ headerRemoveEntry(pkg.h, RPMTAG_FILERDEVS);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEMTIMES);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEDEVICES);
+ headerRemoveEntry(pkg.h, RPMTAG_FILEINODES);
+ headerRemoveEntry(pkg.h, RPMTAG_TRIGGERSCRIPTS);
+ headerRemoveEntry(pkg.h, RPMTAG_TRIGGERVERSION);
+ headerRemoveEntry(pkg.h, RPMTAG_TRIGGERFLAGS);
+ headerRemoveEntry(pkg.h, RPMTAG_TRIGGERNAME);
+ headerRemoveEntry(pkg.h, RPMTAG_CHANGELOGTIME);
+ headerRemoveEntry(pkg.h, RPMTAG_CHANGELOGNAME);
+ headerRemoveEntry(pkg.h, RPMTAG_CHANGELOGTEXT);
+ headerRemoveEntry(pkg.h, RPMTAG_ICON);
+ headerRemoveEntry(pkg.h, RPMTAG_GIF);
+ headerRemoveEntry(pkg.h, RPMTAG_VENDOR);
+ headerRemoveEntry(pkg.h, RPMTAG_EXCLUDE);
+ headerRemoveEntry(pkg.h, RPMTAG_EXCLUSIVE);
+ headerRemoveEntry(pkg.h, RPMTAG_DISTRIBUTION);
+ headerRemoveEntry(pkg.h, RPMTAG_VERIFYSCRIPT);
+ }
+ av_push(depslist, sv_setref_pv(newSVpv("", 0), "URPM::Package",
+ memcpy(malloc(sizeof(struct s_Package)), &pkg, sizeof(struct s_Package))));
+
+ /* only one element read */
+ XPUSHs(sv_2mortal(newSViv(av_len(depslist))));
+ XPUSHs(sv_2mortal(newSViv(av_len(depslist))));
+ }
+ }
+ fdClose(fd);
+ } else croak("first argument should contains a depslist ARRAY reference");
+ } else croak("first argument should be a reference to HASH");
+
diff --git a/URPM/Build.pm b/URPM/Build.pm
new file mode 100644
index 0000000..6ebbfd0
--- /dev/null
+++ b/URPM/Build.pm
@@ -0,0 +1,383 @@
+package URPM;
+
+use strict;
+
+#- prepare build of an hdlist from a list of files.
+#- it can be used to start computing depslist.
+sub parse_rpms_build_headers {
+ my ($urpm, $dir, @rpms) = @_;
+ my (%cache, @headers, %names);
+
+ #- build a working directory which will hold rpm headers.
+ $dir ||= '.';
+ -d $dir or mkdir $dir, 0755 or die "cannot create directory $dir\n";
+
+ #- examine cache if it contains any headers which will be much faster to read
+ #- than parsing rpm file directly.
+ local *DIR;
+ opendir DIR, $dir;
+ while (my $file = readdir DIR) {
+ $file =~ /(.+?-[^:\-]+-[^:\-]+\.[^:\-\.]+)(?::(\S+))?$/ or next;
+ $cache{$2 || $1} = $file;
+ }
+ closedir DIR;
+
+ foreach (@rpms) {
+ my ($key) = /([^\/]*)\.rpm$/ or next; #- get rpm filename.
+ my ($id, $filename);
+
+ if ($cache{$key} && -s "$dir/$cache{$key}") {
+ ($id, undef) = $urpm->parse_hdlist("$dir/$cache{$key}", 1);
+ defined $id or die "bad header $dir/$cache{$key}\n";
+
+ $filename = $cache{$key};
+ } else {
+ ($id, undef) = $urpm->parse_rpm($_);
+ defined $id or die "bad rpm $_\n";
+
+ my $pkg = $urpm->{depslist}[$id];
+
+ $filename = $pkg->fullname;
+ "$filename.rpm" eq $pkg->filename or $filename .= ":$key";
+
+ print STDERR "$dir/$filename\n";
+ unless (-s "$dir/$filename") {
+ local *F;
+ open F, ">$dir/$filename";
+ $pkg->build_header(fileno *F);
+ close F;
+ }
+ -s "$dir/$filename" or unlink("$dir/$filename"), die "can create header $dir/$filename\n";
+
+ #- make smart use of memory (no need to keep header in memory now).
+ $pkg->pack_header;
+ }
+
+ #- keep track of header associated (to avoid rereading rpm filename directly
+ #- if rereading has been made neccessary).
+ push @headers, $filename;
+ }
+ @headers;
+}
+
+#- check if rereading of hdlist is neccessary.
+sub unresolved_provides_clean {
+ my ($urpm) = @_;
+ my @unresolved = grep { ! defined $urpm->{provides}{$_} } keys %{$urpm->{provides} || {}};
+
+ #- names can be safely removed in any cases.
+ delete $urpm->{names};
+
+ #- remove
+ @{$urpm}{qw(depslist provides)} = ([], {});
+ @{$urpm->{provides}}{@unresolved} = ();
+
+ @unresolved;
+}
+
+#- read a list of headers (typically when building an hdlist when provides have
+#- been cleaned.
+sub parse_headers {
+ my ($urpm, $dir, @headers) = @_;
+ my ($start, $id);
+
+ $dir ||= '.';
+ -d $dir or die "no directory $dir\n";
+
+ $start = @{$urpm->{depslist} || []};
+ foreach (@headers) {
+ #- make smart use of memory (no need to keep header in memory now).
+ ($id, undef) = $urpm->parse_hdlist("$dir/$_", 1);
+ defined $id or die "bad header $dir/$_\n";
+ }
+ defined $id ? ($start, $id) : ();
+}
+
+#- compute dependencies, result in stored in info values of urpm.
+#- operations are incremental, it is possible to read just one hdlist, compute
+#- dependencies and read another hdlist, and again.
+sub compute_deps {
+ my ($urpm) = @_;
+
+ #- avoid recomputing already present infos, take care not to modify
+ #- existing entries, as the array here is used instead of values of infos.
+ my $start = @{$urpm->{deps} ||= []};
+ my $end = $#{$urpm->{depslist} || []};
+
+ #- check if something has to be done.
+ $start > $end and return;
+
+ #- take into account in which hdlist a package has been found.
+ #- this can be done by an incremental take into account generation
+ #- of depslist.ordered part corresponding to the hdlist.
+ #- compute closed requires, do not take into account choices.
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ my %required_packages;
+ my @required_packages;
+ my %requires; @requires{$pkg->requires_nosense} = ();
+ my @requires = keys %requires;
+
+ while (my $req = shift @requires) {
+ $req =~ /^basesystem/ and next; #- never need to requires basesystem directly as always required! what a speed up!
+ $req = ($req =~ /^[0-9]+$/ && [ $req ] ||
+ $urpm->{provides}{$req} && [ keys %{$urpm->{provides}{$req}} ] ||
+ [ ($req !~ /NOTFOUND_/ && "NOTFOUND_") . $req ]);
+ if (@$req > 1) {
+ #- this is a choice, no closure need to be done here.
+ push @required_packages, $req;
+ } else {
+ #- this could be nothing if the provides is a file not found.
+ #- and this has been fixed above.
+ foreach (@$req) {
+ my $pkg_ = /^[0-9]+$/ && $urpm->{depslist}[$_];
+ exists $required_packages{$_} and next;
+ $required_packages{$_} = undef; $pkg_ or next;
+ foreach ($pkg_->requires_nosense) {
+ unless (exists $requires{$_}) {
+ $requires{$_} = undef;
+ push @requires, $_;
+ }
+ }
+ }
+ }
+ }
+ #- examine choice to remove those which are not mandatory.
+ foreach (@required_packages) {
+ unless (grep { exists $required_packages{$_} } @$_) {
+ $required_packages{join '|', sort { $a <=> $b } @$_} = undef;
+ }
+ }
+
+ #- store a short representation of requires.
+ $urpm->{requires}[$_] = join ' ', keys %required_packages;
+ }
+
+ #- expand choices and closure again.
+ my %ordered;
+ foreach ($start .. $end) {
+ my %requires;
+ my @requires = ($_);
+ while (my $dep = shift @requires) {
+ exists $requires{$dep} || /^[^0-9\|]*$/ and next;
+ foreach ($dep, split ' ', $urpm->{requires}[$dep]) {
+ if (/\|/) {
+ push @requires, split /\|/, $_;
+ } else {
+ /^[0-9]+$/ and $requires{$_} = undef;
+ }
+ }
+ }
+
+ my $pkg = $urpm->{depslist}[$_];
+ my $delta = 1 + ($pkg->name eq 'basesystem' ? 10000 : 0) + ($pkg->name eq 'msec' ? 20000 : 0);
+ foreach (keys %requires) {
+ $ordered{$_} += $delta;
+ }
+ }
+
+ #- some package should be sorted at the beginning.
+ my $fixed_weight = 10000;
+ foreach (qw(basesystem msec * locales filesystem setup glibc sash bash libtermcap2 termcap readline ldconfig)) {
+ foreach (keys %{$urpm->{provides}{$_} || {}}) {
+ /^[0-9]+$/ and $ordered{$_} = $fixed_weight;
+ }
+ $fixed_weight += 10000;
+ }
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ $pkg->name =~ /locales-[a-zA-Z]/ and $ordered{$_} = 35000;
+ }
+
+ #- compute base flag, consists of packages which are required without
+ #- choices of basesystem and are ALWAYS installed. these packages can
+ #- safely be removed from requires of others packages.
+ foreach (qw(basesystem glibc kernel)) {
+ foreach (keys %{$urpm->{provides}{$_} || {}}) {
+ foreach ($_, split ' ', $urpm->{requires}[$_]) {
+ /^[0-9]+$/ and $urpm->{depslist}[$_] and $urpm->{depslist}[$_]->set_flag_base(1);
+ }
+ }
+ }
+
+ #- give an id to each packages, start from number of package already
+ #- registered in depslist.
+ my %remap_ids; @remap_ids{sort {
+ $ordered{$b} <=> $ordered{$a} or do {
+ my ($na, $nb) = map { $urpm->{depslist}[$_]->name } ($a, $b);
+ my ($sa, $sb) = map { /^lib(.*)/ and $1 } ($na, $nb);
+ $sa && $sb ? $sa cmp $sb : $sa ? -1 : $sb ? +1 : $na cmp $nb;
+ }} ($start .. $end)} = ($start .. $end);
+
+ #- recompute requires to use packages id, drop any base packages or
+ #- reference of a package to itself.
+ my @depslist;
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ #- set new id.
+ $pkg->set_id($remap_ids{$_});
+
+ my ($id, $base, %requires_id);
+ foreach (split ' ', $urpm->{requires}[$_]) {
+ if (/\|/) {
+ #- all choices are grouped together at the end of requires,
+ #- this allow computation of dropable choices.
+ my ($to_drop, @choices_base_id, @choices_id);
+ foreach (split /\|/, $_) {
+ my ($id, $base) = /^[0-9]+$/ ? (exists $remap_ids{$_} ? $remap_ids{$_} : $_,
+ $urpm->{depslist}[$_]->flag_base) : ($_, 0);
+ $base and push @choices_base_id, $id;
+ $base &&= ! $pkg->flag_base;
+ $to_drop ||= $id == $pkg->id || exists $requires_id{$id} || $base;
+ push @choices_id, $id;
+ }
+
+ #- package can safely be dropped as it will be selected in requires directly.
+ $to_drop and next;
+
+ #- if a base package is in a list, keep it instead of the choice.
+ if (@choices_base_id) {
+ @choices_id = @choices_base_id;
+ $base = 1;
+ }
+ if (@choices_id == 1) {
+ $id = $choices_id[0];
+ } else {
+ my $choices_key = join '|', sort { $a <=> $b } @choices_id;
+ $requires_id{$choices_key} = undef;
+ next;
+ }
+ } else {
+ ($id, $base) = /^[0-9]+$/ ? (exists $remap_ids{$_} ? $remap_ids{$_} : $_,
+ $urpm->{depslist}[$_]->flag_base) : ($_, 0);
+ }
+
+ #- select individual package.
+ $base &&= ! $pkg->flag_base;
+ $id == $pkg->id || $base or $requires_id{$id} = undef;
+ }
+ #- be smart with memory usage.
+ delete $urpm->{requires}[$_];
+ $urpm->{deps}[$remap_ids{$_}] = join(' ', sort { $a <=> $b } map { join '|', sort { $a <=> $b } @{ref $_ ? $_ : [$_]} } keys %requires_id);
+ $depslist[$remap_ids{$_}-$start] = $pkg;
+ }
+
+ #- remap all provides ids for new package position and update depslist.
+ @{$urpm->{depslist}}[$start .. $end] = @depslist;
+ foreach my $h (values %{$urpm->{provides}}) {
+ my %provided;
+ foreach (keys %{$h || {}}) {
+ $provided{exists $remap_ids{$_} ? $remap_ids{$_} : $_} = delete $h->{$_};
+ }
+ $h = \%provided;
+ }
+ delete $urpm->{requires};
+
+ ($start, $end);
+}
+
+#- build an hdlist from existing depslist, from start to end inclusive.
+sub build_hdlist {
+ my ($urpm, $start, $end, $dir, $hdlist, $ratio, $split_ratio) = @_;
+
+ #- compression ratio are not very high, sample for cooker
+ #- gives the following (main only and cache fed up):
+ #- ratio compression_time size
+ #- 9 21.5 sec 8.10Mb -> good for installation CD
+ #- 6 10.7 sec 8.15Mb
+ #- 5 9.5 sec 8.20Mb
+ #- 4 8.6 sec 8.30Mb -> good for urpmi
+ #- 3 7.6 sec 8.60Mb
+ $ratio ||= 4;
+ $split_ratio ||= 400000;
+
+ open B, "| $ENV{LD_LOADER} packdrake -b${ratio}ds '$hdlist' '$dir' $split_ratio";
+ foreach (@{$urpm->{depslist}}[$start .. $end]) {
+ my $filename = $_->fullname;
+ "$filename.rpm" ne $_->filename && $_->filename =~ /([^\/]*)\.rpm$/ and $filename .= ":$1";
+ -s "$dir/$filename" or die "bad header $dir/$filename\n";
+ print B "$filename\n";
+ }
+ close B or die "packdrake failed\n";
+}
+
+#- build synthesis file.
+sub build_synthesis {
+ my ($urpm, $start, $end, $synthesis) = @_;
+
+ $start > $end and return;
+
+ #- first pass: traverse provides to find files provided.
+ my %provided_files;
+ foreach (keys %{$urpm->{provides}}) {
+ /^\// or next;
+ foreach my $id (keys %{$urpm->{provides}{$_} || {}}) {
+ push @{$provided_files{$id} ||= []}, $_;
+ }
+ }
+
+ local *F;
+ open F, "| $ENV{LD_LOADER} gzip -9 >'$synthesis'";
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+ my %files;
+
+ if ($provided_files{$_}) {
+ @files{@{$provided_files{$_}}} = undef;
+ delete @files{$pkg->provides_nosense};
+ }
+
+ $pkg->build_info(fileno *F, join('@', keys %files));
+ }
+ close F;
+}
+
+#- write depslist.ordered file according to info in params.
+sub build_base_files {
+ my ($urpm, $depslist, $provides, $compss) = @_;
+ local *F;
+
+ if ($depslist) {
+ open F, ">$depslist";
+ for (0 .. $#{$urpm->{depslist}}) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ printf F ("%s-%s-%s.%s%s %s %s\n", $pkg->fullname,
+ ($pkg->epoch ? ':' . $pkg->epoch : ''), $pkg->size || 0, $urpm->{deps}[$_]);
+ }
+ close F;
+ }
+
+ if ($provides) {
+ open F, ">$provides";
+ while (my ($k, $v) = each %{$urpm->{provides}}) {
+ printf F "%s\n", join '@', $k, map { scalar $urpm->{depslist}[$_]->fullname } keys %{$v || {}};
+ }
+ close F;
+ }
+
+ if ($compss) {
+ my %p;
+
+ open F, ">$compss";
+ foreach (@{$urpm->{depslist}}) {
+ $_->group or next;
+ push @{$p{$_->group} ||= []}, $_->name;
+ }
+ foreach (sort keys %p) {
+ print F $_, "\n";
+ foreach (@{$p{$_}}) {
+ print F "\t", $_, "\n";
+ }
+ print F "\n";
+ }
+ close F;
+ }
+
+ 1;
+}
+
+1;
diff --git a/t/rpmdb.t b/t/rpmdb.t
new file mode 100644
index 0000000..c6a1040
--- /dev/null
+++ b/t/rpmdb.t
@@ -0,0 +1,39 @@
+
+use strict ;
+use warnings ;
+
+sub ok {
+ my ($no, $ok) = @_ ;
+
+ print "ok $no\n" if $ok ;
+ print "not ok $no\n" unless $ok ;
+ printf "# Failed test at line %d\n", (caller)[2] unless $ok ;
+}
+
+use URPM;
+
+print "1..5\n";
+
+my $db;
+ok(1, $db = URPM::DB::open);
+
+my @all_pkgs_extern = sort { $a cmp $b } split '\n', `rpm -qa`;
+ok(2, @all_pkgs_extern > 0);
+
+my @all_pkgs;
+my $count = $db->traverse(sub {
+ my ($pkg) = @_;
+ my ($name, $version, $release, $arch) = $pkg->fullname;
+ $arch or return;
+ push @all_pkgs, "$name-$version-$release";
+ });
+ok(3, $count == @all_pkgs_extern);
+ok(4, $count == @all_pkgs);
+
+my @all_pkgs_sorted = sort { $a cmp $b } @all_pkgs;
+my $bad_pkgs = 0;
+foreach (0..$#all_pkgs_sorted) {
+ $all_pkgs_sorted[$_] eq $all_pkgs_extern[$_] or ++$bad_pkgs;
+}
+ok(5, $bad_pkgs == 0);
+
diff --git a/t/synthesis.t b/t/synthesis.t
new file mode 100644
index 0000000..24c0719
--- /dev/null
+++ b/t/synthesis.t
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+use strict ;
+use warnings ;
+
+sub ok {
+ my ($no, $ok) = @_ ;
+
+ print "ok $no\n" if $ok ;
+ print "not ok $no\n" unless $ok ;
+ printf "# Failed test at line %d\n", (caller)[2] unless $ok ;
+}
+
+use URPM;
+
+my $file1 = 'synthesis.sample.cz';
+
+local *F;
+open F, "| gzip -9 >$file1";
+print F q{
+glibc-devel@provides@glibc-devel == 6:2.2.4-25mdk
+glibc-devel@requires@/sbin/install-info@glibc == 2.2.4@kernel-headers@kernel-headers >= 2.2.1@/bin/sh@/bin/sh@/bin/sh@rpmlib(PayloadFilesHavePrefix) <= 4.0-1@rpmlib(CompressedFileNames) <= 3.0.4-1
+glibc-devel@conflicts@texinfo < 3.11@gcc < 2.96-0.50mdk
+glibc-devel@obsoletes@libc-debug@libc-headers@libc-devel@linuxthreads-devel@glibc-debug
+glibc-devel@info@glibc-devel-2.2.4-25mdk.i586@6@45692097@Development/C
+};
+close F;
+
+print "1..80\n";
+
+my $a = new URPM;
+ok(1, $a);
+
+my ($first, $end) = $a->parse_synthesis($file1);
+ok(2, $first == 0 && $end == 0);
+ok(3, @{$a->{depslist}} == 1);
+ok(4, keys(%{$a->{provides}}) == 3);
+ok(5, defined $a->{provides}{'glibc-devel'});
+ok(6, exists $a->{provides}{'/bin/sh'});
+ok(7, ! defined $a->{provides}{'/bin/sh'});
+ok(8, exists $a->{provides}{'/sbin/install-info'});
+ok(9, ! defined $a->{provides}{'/sbin/install-info'});
+
+my $pkg = $a->{depslist}[0];
+ok(10, $pkg);
+ok(11, $pkg->name eq 'glibc-devel');
+ok(12, $pkg->version eq '2.2.4');
+ok(13, $pkg->release eq '25mdk');
+ok(14, $pkg->arch eq 'i586');
+ok(15, $pkg->fullname eq 'glibc-devel-2.2.4-25mdk.i586');
+
+my ($name, $version, $release, $arch, @l) = $pkg->fullname;
+ok(16, @l == 0);
+ok(17, $name eq 'glibc-devel');
+ok(18, $version eq '2.2.4');
+ok(19, $release eq '25mdk');
+ok(20, $arch eq 'i586');
+
+ok(21, $pkg->epoch == 6);
+ok(22, $pkg->size == 45692097);
+ok(23, $pkg->group eq 'Development/C');
+ok(24, $pkg->filename eq 'glibc-devel-2.2.4-25mdk.i586.rpm');
+ok(25, defined $pkg->id);
+ok(26, $pkg->id == 0);
+ok(27, $pkg->set_id(6) == 0);
+ok(28, $pkg->id == 6);
+ok(29, $pkg->set_id == 6);
+ok(30, ! defined $pkg->id);
+ok(31, ! defined $pkg->set_id(0));
+ok(32, defined $pkg->id);
+ok(33, $pkg->id == 0);
+
+my @obsoletes = $pkg->obsoletes;
+ok(34, @obsoletes == 5);
+ok(35, $obsoletes[0] eq 'libc-debug');
+ok(36, $obsoletes[4] eq 'glibc-debug');
+
+my @conflicts = $pkg->conflicts;
+ok(37, @conflicts == 2);
+ok(38, $conflicts[0] eq 'texinfo < 3.11');
+ok(39, $conflicts[1] eq 'gcc < 2.96-0.50mdk');
+
+my @requires = $pkg->requires;
+ok(40, @requires == 9);
+ok(41, $requires[0] eq '/sbin/install-info');
+ok(42, $requires[8] eq 'rpmlib(CompressedFileNames) <= 3.0.4-1');
+
+my @provides = $pkg->provides;
+ok(43, @provides == 1);
+ok(44, $provides[0] eq 'glibc-devel == 6:2.2.4-25mdk');
+
+my @files = $pkg->files;
+ok(45, @files == 0);
+
+ok(46, $pkg->compare("6:2.2.4-25mdk") == 0);
+ok(47, $pkg->compare("2.2.4-25mdk") == 0);
+ok(48, $pkg->compare("2.2.4") == 0);
+ok(49, $pkg->compare("2.2.3") > 0);
+ok(50, $pkg->compare("2.2") > 0);
+ok(51, $pkg->compare("2") > 0);
+ok(52, $pkg->compare("2.2.4.0") < 0);
+ok(53, $pkg->compare("2.2.5") < 0);
+ok(54, $pkg->compare("2.1.7") > 0);
+ok(55, $pkg->compare("2.3.1") < 0);
+ok(56, $pkg->compare("2.2.31") < 0);
+ok(57, $pkg->compare("2.2.4-25") > 0);
+ok(58, $pkg->compare("2.2.4-25.1mdk") < 0);
+ok(59, $pkg->compare("2.2.4-24mdk") > 0);
+ok(60, $pkg->compare("2.2.4-26mdk") < 0);
+ok(61, $pkg->compare("6:2.2.4-26mdk") < 0);
+ok(62, $pkg->compare("7:2.2.4-26mdk") < 0);
+ok(63, $pkg->compare("7:2.2.4-24mdk") < 0);
+
+ok(64, $a->traverse() == 1);
+
+my $test = 0;
+ok(65, $a->traverse(sub { my ($pkg) = @_; $test = $pkg->name eq 'glibc-devel' }) == 1);
+ok(66, $test);
+ok(67, $a->traverse_tag('name', [ 'glibc-devel' ]) == 1);
+ok(68, $a->traverse_tag('name', [ 'glibc' ]) == 0);
+
+$test = 0;
+ok(69, $a->traverse_tag('name', [ 'glibc-devel' ], sub { my ($pkg) = @_; $test = $pkg->name eq 'glibc-devel' }) == 1);
+ok(70, $test);
+
+@conflicts = $pkg->conflicts_nosense;
+ok(71, @conflicts == 2);
+ok(72, $conflicts[0] eq 'texinfo');
+ok(73, $conflicts[1] eq 'gcc');
+
+@requires = $pkg->requires_nosense;
+ok(74, @requires == 9);
+ok(75, $requires[0] eq '/sbin/install-info');
+ok(76, $requires[1] eq 'glibc');
+ok(77, $requires[3] eq 'kernel-headers');
+ok(78, $requires[8] eq 'rpmlib(CompressedFileNames)');
+
+@provides = $pkg->provides_nosense;
+ok(79, @provides == 1);
+ok(80, $provides[0] eq 'glibc-devel');
+
diff --git a/typemap b/typemap
new file mode 100644
index 0000000..d6a74d8
--- /dev/null
+++ b/typemap
@@ -0,0 +1,2 @@
+URPM::DB T_PTROBJ
+URPM::Package T_PTROBJ