diff -r 71503088f51b -r f880f219c566 openpkg/index.pl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/openpkg/index.pl Tue Jul 31 12:23:42 2012 +0200
@@ -0,0 +1,1173 @@
+##
+## index.pl -- OpenPKG Maintenance Tool (backend for indexing)
+## Copyright (c) 2000-2012 OpenPKG GmbH
+##
+## This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
+## All rights reserved. Licenses which grant limited permission to use,
+## copy, modify and distribute this software are available from the
+## OpenPKG GmbH.
+##
+## THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
+## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+## SUCH DAMAGE.
+##
+
+#############################################################################
+##
+## MAIN PROCEDURE
+##
+#############################################################################
+
+require 5;
+#use strict;
+
+# determine OpenPKG instance prefix via
+# 1. the environment of the "openpkg build" framework
+# 2. the installation path of the script
+# 3. the installation path of the Perl interpreter
+# 4. the path of the "openpkg" command in $PATH
+my $l_prefix = $ENV{'OPENPKG_PREFIX'};
+if (not $l_prefix) {
+ ($l_prefix) = ($0 =~ m/^(.+)\/lib(exec)?\/openpkg(-tools)?\/build(\.pl)?$/);
+}
+if (not $l_prefix) {
+ ($l_prefix) = ($^X =~ m/^(.+)\/bin\/perl.*$/);
+}
+if (not $l_prefix) {
+ $l_prefix = (`(which openpkg) 2>/dev/null` =~ m/^(.+)\/bin\/openpkg$/);
+}
+if (not -x "$l_prefix/bin/openpkg") {
+ die "openpkg:index:FATAL: cannot determine OpenPKG instance prefix";
+}
+
+# determine tools
+my $RPM = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm") ?
+ "$l_prefix/bin/openpkg rpm" : "$l_prefix/bin/rpm");
+my $R2C = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm2cpio") ?
+ "$l_prefix/bin/openpkg rpm2cpio" : "$l_prefix/bin/rpm2cpio");
+my $BZ = "$l_prefix/lib/openpkg/bzip2 -9";
+my $OPENSSL = "$l_prefix/lib/openpkg/openssl";
+
+# parse command line
+our ($opt_r, $opt_p, $opt_S, $opt_T, $opt_D, $opt_C, $opt_o, $opt_c, $opt_i);
+my $getopts = 'r:p:STDC:o:ci';
+getopts($getopts);
+if ($#ARGV < 0) {
+ print "openpkg:index:USAGE: openpkg index [-r resource] [-p platform] [-S] [-T] [-D] [-C cache.db] [-o index.rdf] [-c] [-i] dir ...\n";
+ exit(1);
+}
+
+# optionally open cache file
+my %cache;
+if ($opt_C) {
+ eval {
+ require DB_File;
+ };
+ if ($@) {
+ die "openpkg:index:FATAL: The -C option requires an installed DB_File perl module.";
+ }
+ eval {
+ require Fcntl;
+ };
+ if ($@) {
+ die "openpkg:index:FATAL: The -C option requires an installed Fcntl perl module.";
+ }
+ tie %cache, 'DB_File', $opt_C, Fcntl::O_CREAT()|Fcntl::O_RDWR(), 0666, $DB_File::DB_HASH
+ or die "openpkg:index:FATAL: cannot tie cache '$opt_C' ($!)";
+}
+
+# provide default for repository path
+$opt_r = 'OpenPKG-CURRENT/Source/' if (not defined($opt_r));
+
+# create make_resource function closure
+my $make_resource = gen_make_resource();
+
+# determine output channel
+my $fh;
+my $tmpo;
+if (defined($opt_o)) {
+ $tmpo = $opt_o . '.tmp';
+ if ($opt_c) {
+ open(FH, "| $BZ -c > '$tmpo'")
+ or die "openpkg:index:FATAL: cannot write '$tmpo' ($!)";
+ } else {
+ open(FH, "> $tmpo")
+ or die "openpkg:index:FATAL: cannot write '$tmpo' ($!)";
+ }
+} else {
+ if ($opt_c) {
+ open(FH, "| $BZ -c")
+ or die "openpkg:index:FATAL: cannot write to stdout ($!)";
+ } else {
+ open(FH, ">&=1")
+ or die "openpkg:index:FATAL: cannot write to stdout ($!)";
+ }
+}
+$fh = \*FH;
+
+# generate XML/RDF output
+xml_head($fh, $opt_r);
+foreach my $prefix (@ARGV) {
+ my $list;
+ if (-d $prefix) {
+ if ($opt_i) {
+ $list = list_rpmdir($prefix);
+ } else {
+ $list = list_specdir($prefix);
+ }
+ } else {
+ $list = [ $prefix ];
+ $prefix = dirname($prefix);
+ }
+ write_index($fh, $prefix, $opt_r, $opt_p, $list, $opt_C ? \%cache : undef);
+}
+xml_foot($fh);
+
+# close output channel
+close($fh)
+ or die "openpkg:index:FATAL: write error on output ($!)";
+
+# post-process output
+if (defined($tmpo)) {
+ rename($tmpo, $opt_o)
+ or die "openpkg:index:FATAL: cannot rename $tmpo to $opt_o ($!)";
+}
+
+# die gracefully
+exit(0);
+
+#############################################################################
+##
+## FUNCTIONS: PARSING
+##
+#############################################################################
+
+# home-brewn getopt(3) style option parser
+sub getopts ($) {
+ my ($opts) = @_;
+ my (%optf) = map { m/(\w)/; $1 => $_ } $opts =~ m/(\w:|\w)/g;
+ my (%opts, @argv, $optarg);
+
+ foreach (@ARGV) {
+ if (@argv) {
+ push @argv, $_;
+ } elsif (defined $optarg) {
+ if (exists $opts{$optarg}) {
+ $opts{$optarg} .= " $_";
+ } else {
+ $opts{$optarg} = $_;
+ }
+ $optarg = undef;
+ } elsif (!/^[-]/) {
+ push @argv, $_;
+ } else {
+ while (/^\-(\w)(.*)/) {
+ if (exists $optf{$1}) {
+ if (length($optf{$1}) > 1) {
+ if ($2 ne '') {
+ if (exists $opts{$1}) {
+ $opts{$1} .= " $2";
+ } else {
+ $opts{$1} = $2;
+ }
+ } else {
+ $optarg = $1;
+ }
+ last;
+ } else {
+ $opts{$1} = 1;
+ }
+ } else {
+ warn "openpkg:index:WARNING: unknown option $_\n";
+ }
+ $_ = "-$2";
+ }
+ }
+ }
+ if (defined $optarg) {
+ warn "openpkg:index:WARNING: option $optarg requires an argument\n";
+ }
+ foreach (keys %opts) {
+ eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";';
+ }
+ @ARGV = @argv;
+}
+
+#############################################################################
+##
+## OpenPKG RPM Package Specification Parsing
+##
+#############################################################################
+
+# escape XML for output in RDF file
+sub e ($) {
+ my ($s) = @_;
+ my ($i);
+
+ # remove trailing whitespace
+ $s =~ s/\n+$//sg;
+ $s =~ s/[^\S\n]+$//mg;
+
+ # remove common leading whitespace
+ $i = undef;
+ while ($s =~ m/^([^\S\n]+)/mg) {
+ $i = $1 if (!defined($i) || (length($1) < length($i)));
+ }
+ $s =~ s/^\Q$i\E//mg if (defined($i));
+
+ # escape XML special characters
+ $s =~ s/&/&/sg;
+ $s =~ s/</sg;
+ $s =~ s/>/>/sg;
+
+ return $s;
+}
+
+# make_resource closure
+sub gen_make_resource {
+ # generate operator regular expression
+ # (used in make_resource below)
+ my %attrname = (
+ '==' => 'equ',
+ '=' => 'equ',
+ '>=' => 'geq',
+ '=>' => 'geq',
+ '<=' => 'leq',
+ '=<' => 'leq',
+ '>' => 'gt',
+ '<' => 'lt'
+ );
+ my ($opreg) =
+ join '|',
+ map {
+ "\Q$_\E"
+ } sort {
+ length($b) <=> length($a)
+ || $b cmp $a
+ } keys(%attrname);
+
+ # return function which makes resource object
+ return sub ($) {
+ my ($s) = @_;
+
+ if ($s =~ m/(\S+)\s*($opreg)\s*(.*?)\s*$/o) {
+ return {
+ resource => $1,
+ attrname => $attrname{$2},
+ attrval => $3
+ }
+ }
+ else {
+ return {
+ resource => $s
+ }
+ }
+ };
+};
+
+# split string into array at comma seperator
+# (optioanlly map resource operators into resource objects)
+sub commasep ($$) {
+ my ($k, $v) = @_;
+
+ if ($k =~ m/^(NoSource)$/) {
+ return split(/\s*,\s*/, $v);
+ } elsif ($k =~ /^(PreReq|BuildPreReq|Provides|Conflicts)$/) {
+ return map { &$make_resource($_) }
+ split(/\s*,\s*/, $v);
+ }
+ return $v;
+}
+
+# escape option string
+sub optesc ($) {
+ my ($s) = @_;
+
+ $s =~ s/([\x00-\x1f\x80-\xbf\s\%])/sprintf("%%%02x", ord($1))/eg;
+ return $s;
+}
+
+# variable substitution
+sub vsub ($$) {
+ my ($var, $v) = @_;
+
+ $v =~ s/\%\{([^}]+)\}/
+ exists $var->{$1} ? $var->{$1} : '%{'.$1.'}'/emg;
+ return $v;
+}
+
+# Umgekehrte Polnische Notation (UPN)
+# Reverse Polish Notation (RPN)
+# << ( %{foo} == "yes" ) && ( ! %{bar} == "no" ) || ( %{baz} == "yes" )
+# >> %{foo} %{bar} ! && %{baz} ||
+sub upn ($) {
+ my ($t) = @_;
+ my (@tok);
+ my (@out, $op, $o);
+ my (@save);
+
+ # split string into tokens
+ @tok = ($t =~ m/(\(|\)|\&\&|\|\||\!|\S+)/g);
+
+ # iterate over all tokens
+ $op = [];
+ foreach (@tok) {
+ if ($_ eq '(') {
+ push(@save, $op);
+ $op = [];
+ } elsif ($_ eq ')') {
+ die "openpkg:index:FATAL: unresolved operators in: @tok\n" if (@$op);
+ $op = pop(@save)
+ or die "openpkg:index:FATAL: unmatched closing parenthesis in: @tok\n";
+ while ($o = pop(@$op)) {
+ push(@out, $o->[0]);
+ last if ($o->[1]);
+ }
+ } elsif ($_ eq '&&') {
+ push(@$op, [ '+', 1 ]);
+ } elsif ($_ eq '||') {
+ push(@$op, [ '|', 1 ]);
+ } elsif ($_ eq '!') {
+ push(@$op, [ '!', 0 ]);
+ } elsif (m/^\%\{(\S*?)\}$/) {
+ push(@out, $1);
+ while ($o = pop(@$op)) {
+ push(@out, $o->[0]);
+ last if ($o->[1]); # binary operator
+ }
+ }
+ }
+ return join (' ', @out);
+}
+
+# deduce external variables from description
+# (backward compatibility for times before openpkg-20021230)
+sub find_options ($) {
+ my ($descr) = @_;
+ my $evar = {};
+ $descr =~ s/--define\s*'(\S+)\s*\%\{\1\}'/$evar->{$1} = '%{'.$1.'}', ''/sge;
+ return $evar;
+}
+
+# translate default section from spec-file into a hash
+# - %if/%ifdef/%define... are translated to #/#ifdef/#define
+# - #defines are interpolated (correct ?)
+# - #if/#ifdef/... sections are stripped
+# result is the same as if all conditions evaluate false (!)
+# - all attributes are of the form key: value
+# - repeated attributes are coalesced into a list
+# hint: evar = expansion variables, ovar = option variables
+sub package2data ($$) {
+ my ($s, $ovar) = @_;
+ my (%evar, %var);
+ my (@term, $term);
+ my (%attr, %avar);
+ my ($l, $v, $cond, $d, $p);
+ my ($re, @defs);
+
+ # combine multilines (line continuation!)
+ $s =~ s/\\\n/ /sg;
+
+ # map syntax of conditional variable macros
+ $s =~ s/^#\{\!\?([^:]*):\s*%(.*?)\s*\}\s*$/#ifndef $1\n#$2\n#endif/mg;
+ $s =~ s/^#\{\!\?([^:]*):\s*(.*?)\s*\}\s*$/#ifndef $1\n$2\n#endif/mg;
+
+ # map syntax of option macro
+ $s =~ s/^#option\s+(\S+)\s*(.*?)\s*$/#ifndef $1\n#define $1 $2\n#endif\n#provides $1 $2/mg;
+
+ # use option variables for expansion
+ %evar = %$ovar;
+
+ # guess more external parameters by scanning for (and removing) "set option default" sections
+ $re = '^\#ifndef\s+[\w\_]+\s*\n((?:\#define\s+[\w\_]+\s.*\n)+)\#endif\n';
+ @defs = ($s =~ m/$re/gm);
+ foreach (@defs) {
+ while (m/^\#define\s+([\w\_]+)\s(.*?)\s*$/mg) {
+ $ovar->{$1} = $2;
+ $evar{$1} = '%{'.$1.'}';
+ }
+ }
+ $s =~ s/$re//gm;
+
+ # add everything looking like a "with_xxx" variable
+ $re = '%{(with\_[\w\_]+)}';
+ @defs = ($s =~ /$re/gm);
+ foreach (@defs) {
+ next if (exists($ovar->{$1}));
+ $ovar->{$1} = '%{'.$1.'}'; # unexpanded
+ $evar{$1} = '%{'.$1.'}';
+ }
+
+ # extract all conditional sections (#if/#else/#endif)
+ @term = ();
+ %var = ();
+ $cond = '';
+ foreach $l (split(/\n/, $s)) {
+ # expand variables
+ $v = vsub(\%avar, vsub(\%var, $l));
+
+ if (($p) = ($v =~ /^\#if\s+(.*?)\s*$/)) {
+ # normalize "%{variable}" != ..."
+ $p =~ s/("\%\{[^}]+\}")\s*!=\s*"(yes|no)"/$1 . " == \"" . ($2 ne "yes" ? "yes" : "no") . "\""/sge;
+
+ # normalize #if expressions
+ # - "%{variable}" == "yes"
+ # - "%{variable}" == "no"
+ # - operators ! && ||
+ # warn on:
+ # - operator !=
+ # - any other word
+ $term = '';
+ while ($p =~ m/(!=)|(\!|\|\||\&\&|\(|\))|"\%\{([^}]+)\}"\s*==\s*"(yes|no)"|(\S+)/g) {
+ if (defined($1)) {
+ warn "openpkg:index:WARNING: unknown token '$1':\n< $l\n> $v";
+ } elsif (defined($5)) {
+ warn "openpkg:index:WARNING: unknown token '$5':\n< $l\n> $v";
+ } elsif (defined($2)) {
+ $term .= " $2 ";
+ } elsif (exists($evar{$3})) {
+ $term .= ($4 eq 'no' ? '! ' : '').vsub(\%evar, '%{'.$3.'}');
+ } else {
+ warn "openpkg:index:WARNING: unknown condition variable '$3':\n< $l\n> $v";
+ }
+ }
+
+ # join with previous conditions for this #if/#endif block
+ if ($term ne '') {
+ push(@term, "( $term )");
+ $cond = join(' && ', grep { $_ ne '' } @term) . '';
+ } else {
+ push(@term, '');
+ }
+ }
+ elsif ($v =~ /^\#else\s*$/) {
+ # reverse last condition
+ if (@term) {
+ $term[-1] = ' ! '. $term[-1];
+ $cond = join(' && ', grep { $_ ne '' } @term) . '';
+ } else {
+ die "openpkg:index:FATAL: \"else\" without \"if\"";
+ }
+ }
+ elsif ($v =~ /^\#endif\s*$/) {
+ # unwind last #if expression
+ pop(@term);
+ $cond = join(' && ', grep { $_ ne '' } @term) . '';
+ }
+ elsif ($v =~ /^\#(?:define)\s*(\S+)\s*(.*?)\s*$/) {
+ # define conditional variables
+ # - truth-value becomes current condition
+ # define internal variables
+ # - store for subsequent substitution
+ if (exists($evar{$1})) {
+ if ($2 eq 'yes') {
+ if ($cond eq '') {
+ $evar{$1} = "( \%\{$1\} )";
+ } else {
+ $evar{$1} = "( \%\{$1\} || ( $cond ) )";
+ }
+ } elsif ($2 eq 'no') {
+ if ($cond eq '') {
+ $evar{$1} = "( \%\{$1\} )";
+ } else {
+ $evar{$1} = "( %\{$1\} && ! ( $cond ) )";
+ }
+ } else {
+ warn "openpkg:index:WARNING: logic too complex for '$1' (boolean expressions allowed only):\n< $l\n> $v";
+ }
+ } else {
+ $var{$1} = $2;
+ }
+ }
+ elsif ($v =~ /^\#(?:undefine)\s*(\S+)\s*$/) {
+ # undefine conditional variables
+ # undefine internal variables
+ if (exists($evar{$1})) {
+ $evar{$1} = "\%\{$1\}";
+ } else {
+ delete($var{$1});
+ }
+ }
+ elsif ($v =~ /^\#(?:provides)\s*(\S+)\s*(.*?)\s*$/) {
+ # store option for current condition
+ if (exists($attr{'Name'}->{''})) {
+ push(@{$attr{'Provides'}->{$cond}}, {
+ resource => $attr{'Name'}->{''}->[0] . '::' . $1,
+ attrname => 'equ',
+ attrval => optesc($2)
+ });
+ } else {
+ warn "openpkg:index:ERROR: no package name set for option \"$1 = $2\"";
+ }
+ }
+ elsif ($v =~ /^\#NoSource\s*(.*?)\s*$/) {
+ # store conditional NoSource attribute
+ push(@{$attr{'NoSource'}->{$cond}}, commasep('NoSource', $1));
+
+ }
+ elsif ($v =~ /^\s*([^\#]\S*)\s*:\s*(.*?)\s*$/) {
+ # store "attribute: value" headers for current condition
+ push(@{$attr{$1}->{$cond}}, commasep($1,$2));
+ $avar{lc($1)} = $2 if ($cond eq '');
+ }
+ }
+
+ # return all header "attributes"
+ return \%attr;
+}
+
+# split spec file into sections starting with a %word
+# - concatenate extended lines
+# - strip comment lines
+# - map %command to #command
+# - split sections
+# - return package2data() from default section (before first %xxx section)
+sub spec2data ($) {
+ my ($s) = @_;
+ my (%map);
+ my ($a, $o);
+ my $spec = $s;
+
+ # remove comments
+ $s =~ s/^\s*#.*?\n//mg;
+
+ # map commands
+ $s =~ s/^%(ifdef|ifndef|if|NoSource|option|undefine|define|else|endif|\{)/#$1/mg;
+
+ # split sections
+ foreach (split(m/^(?=%\w+\s*\n)/m, $s)) {
+ if (m/^%(\w+)\s*\n/) {
+ $map{$1} .= $';
+ } else {
+ $map{'*'} .= $_;
+ }
+ }
+
+ # translate package information into "data"
+ if (exists($map{'description'})) {
+ # backward compatibility (options are in description)
+ $o = find_options($map{'description'});
+ $a = package2data($map{'*'}, $o );
+ $a->{'Description'} = { '' => [ $map{'description'} ] };
+ } else {
+ # standard case
+ $a = package2data($map{'*'}, {});
+ }
+ return $a;
+}
+
+#############################################################################
+##
+## XML/RDF Generation
+##
+#############################################################################
+
+# start of XML file
+sub xml_head ($$) {
+ my ($fh, $res) = @_;
+ print $fh <
+
+
+EOFEOF
+}
+
+# end of XML file, corresponds with start tags
+sub xml_foot ($) {
+ my($fh) = @_;
+ print $fh <
+
+EOFEOF
+}
+
+# conditional expansion of attribute
+sub n ($$) {
+ my ($a, $k) = @_;
+ return if (not $a->{$k});
+ return if (not $a->{$k}->{''});
+ return ($a->{$k}->{''}->[0]);
+}
+
+# send out $a->{$k} as text-style tag
+sub xml_text ($$$;$) {
+ my ($i, $a, $k, $tag) = @_;
+ my ($out);
+ return "" if (not exists($a->{$k}));
+ $tag = $k if (not defined($tag));
+ $i = ' ' x $i;
+ $out = e(n($a, $k));
+ return if $out eq '';
+ return "$i<$tag>\n$out\n$i$tag>\n";
+}
+
+# send out @{$a->{$k}} as body of an XML tag
+# $k is the name of the tag unless overridden by $tag
+# $i denotes the depth of indentation to form nicely
+# looking files.
+# all data from the list is flattened into a single
+# body, separated by LF and escaped for XML metachars.
+sub xml_tag ($$$;$) {
+ my ($i, $a, $k, $tag) = @_;
+ my ($out, $cond, $upn);
+ return "" if (not exists($a->{$k}));
+ $tag = $k if (not defined($tag));
+ $out = '';
+ $i = ' ' x $i;
+ foreach $cond (sort keys(%{$a->{$k}})) {
+ $upn = e(upn($cond));
+ $out .= $i .
+ ($cond ne '' ? "<$tag cond=\"$upn\">" : "<$tag>") .
+ join("\n", map { e($_) } @{$a->{$k}->{$cond}}) .
+ "$tag>\n";
+ }
+ return $out;
+}
+
+# send out @{$a->{$k}} as a rdf:bag
+# $k is the name of the outer tag unless overriden by $tag
+# $i denotes the depth of indentation, inner tags are indented
+# 2 or 4 more character positions.
+# each element of the bag is listed
+sub xml_bag ($$$;$) {
+ my ($i, $a, $k, $tag) = @_;
+ my ($out, $cond, $upn);
+ return "" if (not exists($a->{$k}));
+ $tag = $k if (not defined($tag));
+ $out = '';
+ $i = ' ' x $i;
+ foreach $cond (sort keys %{$a->{$k}}) {
+ next if (not @{$a->{$k}->{$cond}});
+ $upn = e(upn($cond));
+ $out .= $i .
+ ($cond ne '' ? "<$tag cond=\"$upn\">\n" : "<$tag>\n") .
+ "$i \n" .
+ join("",
+ map {
+ ref $_
+ ? "$i {attrname}
+ ? " $_->{attrname}=\"".e($_->{attrval})."\""
+ : ""
+ ) .
+ ">".e($_->{resource})."\n"
+ : "$i ".e($_)."\n"
+ }
+ @{$a->{$k}->{$cond}}) .
+ "$i \n" .
+ "$i$tag>\n";
+ }
+ return $out;
+}
+
+# send out reference to another RDF
+sub xml_reference ($$$$$$) {
+ my($fh, $res, $href, $S, $T, $D) = @_;
+ print $fh " \n";
+}
+
+# send out reference to other resources
+sub xml_other ($$$$$$) {
+ my($fh, $about, $href, $S, $T, $D) = @_;
+ print $fh " \n";
+ print $fh " \n";
+}
+
+# translate attributes from %$a as generated by package2data
+# into XML and write to file $fh
+sub xml_record ($$$$$$) {
+ my ($fh, $a, $href, $S, $T, $D) = @_;
+ my ($maj, $min, $rel, $about);
+
+ $about =
+ n($a, 'Name') . '-' .
+ n($a, 'Version') . '-' .
+ n($a, 'Release');
+ if (not defined($href)) {
+ # guess location from Information in Specfile
+ if (exists($a->{'NoSource'})) {
+ $href = "$about.nosrc.rpm";
+ } else {
+ $href = "$about.src.rpm";
+ }
+ ($maj, $min, $rel) = (n($a, 'Release') =~ m/^(\d+)\.(\d+)\.(\d+)/);
+ if (defined($min)) {
+ if ($maj > 1 || ($maj == 1 && $min > 0)) {
+ # OpenPKG-1.1-RELEASE or later
+ if (n($a, 'Distribution') =~ /\[PLUS\]/) {
+ $href = 'PLUS/'.$href;
+ }
+ }
+ if ($maj > 1 || ($maj == 1 && $min >= 0)) {
+ # OpenPKG-1.0-RELEASE or later
+ if ($rel > 0) {
+ $href = 'UPD/'.$href;
+ }
+ }
+ } else {
+ # OpenPKG-CURRENT
+ }
+ }
+
+ print $fh " \n";
+
+ # fake Source attribute from Source\d attribtutes
+ # XXX only default conditional
+ $a->{'Source'} = { '' => [
+ map {
+ s/\Q%{name}\E/n($a, 'Name')/esg;
+ s/\Q%{version}\E/n($a, 'Version')/esg;
+ s/\Q%{release}\E/n($a, 'Release')/esg;
+ $_;
+ }
+ map {
+ $a->{$_}->{''} ? @{$a->{$_}->{''}} : ()
+ }
+ sort {
+ my ($x) = ($a =~ /^(\d*)$/);
+ my ($y) = ($b =~ /^(\d*)$/);
+ return $x <=> $y;
+ }
+ grep {
+ /^Source\d*$/
+ } keys(%$a)
+ ]};
+ delete($a->{'Source'}) if (not @{$a->{'Source'}->{''}});
+
+ print $fh
+ xml_tag(6, $a, 'Name'),
+ xml_tag(6, $a, 'Version'),
+ xml_tag(6, $a, 'Release'),
+ xml_tag(6, $a, 'Distribution'),
+ xml_tag(6, $a, 'Class'),
+ xml_tag(6, $a, 'Group'),
+ xml_tag(6, $a, 'License'),
+ xml_tag(6, $a, 'Packager'),
+ xml_tag(6, $a, 'Summary'),
+ xml_tag(6, $a, 'URL'),
+ xml_tag(6, $a, 'Vendor'),
+ xml_tag(6, $a, 'SourceRPM'),
+ xml_tag(6, $a, 'Arch'),
+ xml_tag(6, $a, 'Os'),
+ xml_tag(6, $a, 'BuildHost'),
+ xml_tag(6, $a, 'BuildSystem'),
+ xml_tag(6, $a, 'BuildTime'),
+ xml_tag(6, $a, 'Relocations'),
+ xml_tag(6, $a, 'Size'),
+ xml_tag(6, $a, 'Prefixes'),
+ xml_tag(6, $a, 'Platform'),
+ xml_tag(6, $a, 'SigSize'),
+ xml_tag(6, $a, 'SigMD5'),
+ xml_tag(6, $a, 'SigPGP'),
+ xml_tag(6, $a, 'SigGPG'),
+ xml_bag(6, $a, 'BuildPreReq'),
+ xml_bag(6, $a, 'PreReq'),
+ xml_bag(6, $a, 'Provides'),
+ xml_bag(6, $a, 'Conflicts'),
+ xml_bag(6, $a, 'Source'),
+ xml_bag(6, $a, 'NoSource'),
+ xml_bag(6, $a, 'Filenames'),
+ xml_text(6, $a, 'Description');
+
+ print $fh " \n";
+}
+
+#############################################################################
+##
+## OpenPKG RPM Package Payload Extraction
+##
+#############################################################################
+
+# extract foo.spec from foo-V-R.src.rpm file
+sub rpm2spec ($) {
+ my ($fn) = @_;
+ local($SIG{'PIPE'}) = 'IGNORE';
+ local(*PIPE);
+ open(PIPE, "$R2C '$fn' |")
+ or die "openpkg:index:FATAL: cannot read '$fn' ($!)\n";
+ my ($buf, @hdr, $n, $m, $name, $step);
+ my ($spec);
+ while (read(PIPE, $buf, 110) == 110) {
+ @hdr = unpack('a6a8a8a8a8a8a8a8a8a8a8a8a8a8', $buf);
+ $n = hex($hdr[12]); # filename length
+ $m = int(($n+5)/4)*4-2; # filename size (padded)
+ last if (not (read(PIPE,$buf,$m) == $m));
+ $name = substr($buf, 0, $n-1);
+ $n = hex($hdr[7]); # file length
+ $m = int(($n+3)/4)*4; # file size (padded)
+ if ($name !~ m/.spec$/) {
+ while ($m > 0) {
+ $step = $m > 8192 ? 8192 : $m;
+ last if (not read(PIPE,$buf,$step));
+ $m -= length($buf);
+ }
+ } else {
+ if (read(PIPE,$buf,$n) == $n) {
+ $spec = $buf;
+ }
+ last;
+ }
+ }
+ close(PIPE);
+ return $spec;
+}
+
+#############################################################################
+##
+## OpenPKG RPM Package Header Extraction
+##
+#############################################################################
+
+# extract header information from foo-V-R.src.rpm
+sub rpm2data ($$) {
+ my ($fn, $platform) = @_;
+ my ($q, %a);
+ my ($t, $v);
+ local(*PIPE);
+
+ if (not defined($platform)) {
+ die "openpkg:index:FATAL: indexing binary package '$fn' requires -p option\n";
+ }
+
+ $q = <) {
+ if (/^(\S+)\s+(.*?)\s*$/) {
+ $t = $1;
+ $v = $2;
+ } elsif (/^(\s+.+?)\s*$/) {
+ next if (not defined($t));
+ $v = $1;
+ } else {
+ $t = undef;
+ next;
+ }
+ if (exists($a{$t})) {
+ $a{$t} .= "\n$v";
+ } else {
+ $a{$t} = $v;
+ }
+ }
+ close(PIPE);
+
+ %a = map { $_ => $a{$_} }
+ grep { $a{$_} ne '(none)' }
+ keys(%a);
+ if ($a{'Relocations'} eq '(non relocatable)') {
+ delete($a{'Relocations'});
+ }
+ if ($a{'SigMD5'} eq '(unknown type)') {
+ delete($a{'SigMD5'});
+ }
+ if (defined($platform)) {
+ $a{'Platform'} = $platform;
+ }
+ $a{'Description'} = [ $a{'Description'} ];
+
+ foreach ('Conflicts', 'PreReq', 'Provides') {
+ $a{$_} = [
+ map { &$make_resource($_) }
+ grep { !/^rpmlib\(/ }
+ split(/\n+/, $a{$_})
+ ];
+ }
+
+ return { map {
+ $_ => { '' => (ref $a{$_} ? $a{$_} : [ split(/\n+/, $a{$_}) ]) }
+ } keys(%a) };
+}
+
+#############################################################################
+##
+## OpenPKG XML/RDF Index Locating
+##
+#############################################################################
+
+# detect index files
+sub getindex ($) {
+ my ($dir) = @_;
+ my (@idx) =
+ sort { -M $a <=> -M $b; }
+ grep { -f $_ }
+ ( <$dir/00INDEX.rdf>, <$dir/00INDEX.rdf.*> );
+ return if (not @idx);
+ return $idx[0];
+}
+
+# list RPM directories of unpacked .spec files
+sub list_specdir ($) {
+ my ($dir) = @_;
+ my ($d, $path);
+ my (@list);
+
+ local(*DH);
+ opendir(DH, $dir);
+ while ($d = readdir(DH)) {
+ next if ($d =~ m/^\./);
+ $path = "$dir/$d/$d.spec";
+ push(@list, $path) if (-f $path);
+ }
+ closedir(DH);
+ return \@list;
+}
+
+# list index or RPM file directories
+sub list_rpmdir ($) {
+ my ($dir) = @_;
+ my ($d, $path);
+ my (@list, $idx, $sub);
+
+ local(*DH);
+ opendir(DH, $dir);
+ while ($d = readdir(DH)) {
+ next if ($d =~ m/^\./);
+ $path = "$dir/$d";
+ if (-d $path) {
+ $idx = getindex($path);
+ if (defined($idx)) {
+ push(@list, $idx);
+ } else {
+ $sub = list_rpmdir($path);
+ push(@list, @$sub);
+ undef $sub;
+ }
+ } else {
+ next if (not ($d =~ m/\.(?:rpm|sh)$/ && -f $path));
+ push(@list, $path);
+ }
+ }
+ closedir(DH);
+ return \@list;
+}
+
+#############################################################################
+##
+## OpenPKG XML/RDF Index Reading
+##
+#############################################################################
+
+# fetch a whole file
+sub readfile ($) {
+ my ($fn) = @_;
+ local(*FH);
+ open(FH, "< $fn")
+ or die "FATAL: cannot read '$fn' ($!)\n";
+ my $l; { local $/; $l = ; }
+ close(FH);
+ return $l;
+}
+
+# create relative path by stripping instance prefix
+sub relpath ($$) {
+ my ($prefix, $path) = @_;
+ $path =~ s/^\Q$prefix\E\///s;
+ return $path;
+}
+
+# create directory path with trailing slash
+sub dirname ($) {
+ my ($path) = @_;
+ $path =~ s/\/[^\/]*$//s;
+ return $path.'/';
+}
+
+# peek at resource identifier of index file
+sub getresource ($) {
+ my ($fn) = @_;
+ my ($fh, $buf);
+
+ local(*FH);
+ if ($fn =~ /\.bz2$/) {
+ open(FH, "$BZ -dc $fn |")
+ or die "FATAL: cannot read '$fn' ($!)\n";
+ } else {
+ open(FH, "< $fn")
+ or die "FATAL: cannot read '$fn' ($!)\n";
+ }
+ read(FH, $buf, 1024);
+ close(FH);
+ if ($buf =~ //dev/null || true`;
+ if ($output =~ m/MD5\(.+?\)=\s*([0-9a-fA-F]+)/s) {
+ $digest = uc($1);
+ }
+ return $digest;
+}
+
+sub write_index ($$$$$$) {
+ my ($fh, $prefix, $resource, $platform, $list, $cache) = @_;
+ my ($a, $h, $r, $spec, $T, $D);
+
+ foreach (@$list) {
+ $a = undef;
+ $h = undef;
+ $r = undef;
+ $S = undef;
+ $T = undef;
+ $D = undef;
+ $o = undef;
+
+ # determine information
+ if (m/\.spec$/) {
+ $spec = readfile($_);
+ $a = spec2data($spec);
+ ($S, $T) = sizetime($_) if ($opt_S or $opt_T);
+ $D = digest($_) if ($opt_D);
+ } elsif (m/([^\/]+\.(?:no)?src\.rpm)$/) {
+ $h = relpath($prefix, $_);
+ ($S, $T) = sizetime($_) if ($opt_S or $opt_T or $cache);
+ if ($cache) {
+ if ( exists($cache->{"T$_"})
+ && $cache->{"T$_"} == $T
+ && exists($cache->{"s$_"})
+ && $cache->{"s$_"} ne ""
+ && (!$opt_S || ($opt_S && exists($cache->{"S$_"}) && $cache->{"S$_"} ne ""))
+ && (!$opt_D || ($opt_D && exists($cache->{"D$_"}) && $cache->{"D$_"} ne ""))) {
+ # found in cache
+ $spec = $cache->{"s$_"};
+ $S = $cache->{"S$_"} if ($opt_S);
+ $D = $cache->{"D$_"} if ($opt_D);
+ } else {
+ # not found in cache
+ $spec = rpm2spec($_);
+ $cache->{"s$_"} = $spec;
+ $cache->{"S$_"} = $S;
+ $cache->{"T$_"} = $T;
+ $D = digest($_) if ($opt_D);
+ $cache->{"D$_"} = $D if ($opt_D);
+ }
+ } else {
+ # no cache at all
+ $spec = rpm2spec($_);
+ $D = digest($_) if ($opt_D);
+ }
+ $a = spec2data($spec);
+ } elsif (m/([^\/]+\.rpm)$/) {
+ $h = relpath($prefix, $_);
+ $a = rpm2data($_, $platform);
+ ($S, $T) = sizetime($_) if ($opt_S or $opt_T);
+ $D = digest($_) if ($opt_D);
+ } elsif (m/(([^\/]+)\.(?:no)?src\.sh)$/) {
+ $h = relpath($prefix, $_);
+ ($S, $T) = sizetime($_) if ($opt_S or $opt_T);
+ $D = digest($_) if ($opt_D);
+ $o = "$2:bootstrap";
+ } elsif (m/([^\/]+\.rdf[^\/]*)$/) {
+ $h = relpath($prefix, $_);
+ $r = getresource($_) || $resource.dirname($h);
+ ($S, $T) = sizetime($_) if ($opt_S or $opt_T);
+ $D = digest($_) if ($opt_D);
+ }
+
+ # process information
+ if ($a) {
+ xml_record($fh, $a, $h, $S, $T, $D);
+ } elsif ($o) {
+ xml_other($fh, $o, $h, $S, $T, $D);
+ } elsif ($r) {
+ xml_reference($fh, $r, $h, $S, $T, $D);
+ } else {
+ warn "openpkg:index:ERROR: cannot process $_";
+ }
+ }
+}
+