You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
996 lines
25 KiB
996 lines
25 KiB
## |
|
## openpkg-index.pl -- OpenPKG Maintenance Tool (backend for indexing) |
|
## Copyright (c) 2000-2003 The OpenPKG Project <http://www.openpkg.org/> |
|
## Copyright (c) 2000-2003 Ralf S. Engelschall <rse@engelschall.com> |
|
## Copyright (c) 2000-2003 Cable & Wireless <http://www.cw.com/> |
|
## |
|
## Permission to use, copy, modify, and distribute this software for |
|
## any purpose with or without fee is hereby granted, provided that |
|
## the above copyright notice and this permission notice appear in all |
|
## copies. |
|
## |
|
## 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. |
|
## |
|
|
|
require 5; |
|
|
|
use strict; |
|
|
|
use Getopt::Std; |
|
getopts('r:p:C:o:ci'); |
|
use vars qw/$opt_r $opt_p $opt_C $opt_o $opt_c $opt_i/; |
|
|
|
use FileHandle; |
|
use DirHandle; |
|
|
|
my $l_prefix = '@l_prefix@'; |
|
my $RPM = "$l_prefix/bin/rpm"; |
|
my $R2C = "$l_prefix/bin/rpm2cpio"; |
|
my $BZ = "$l_prefix/lib/openpkg/bzip2 -9"; |
|
|
|
######################################################################### |
|
|
|
# |
|
# escape XML special characters for output in RDF file |
|
# |
|
# remove trailing whitespace |
|
# remove common leading whitespace |
|
# |
|
sub e ($) { |
|
my($s) = @_; |
|
my($i); |
|
|
|
$s =~ s/\n+$//sg; |
|
$s =~ s/[^\S\n]+$//mg; |
|
|
|
$i = undef; |
|
while ($s =~ /^([^\S\n]+)/mg) { |
|
$i = $1 if !defined $i || length($1) < length($i); |
|
} |
|
|
|
$s =~ s/^\Q$i\E//mg if defined $i; |
|
$s =~ s/&/&/sg; |
|
$s =~ s/</</sg; |
|
$s =~ s/>/>/sg; |
|
|
|
return $s; |
|
} |
|
|
|
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; |
|
|
|
sub make_resource ($) { |
|
my($s) = @_; |
|
|
|
if ($s =~ /(\S+)\s*($opreg)\s*(.*?)\s*$/o) { |
|
return { |
|
resource => $1, |
|
attrname => $attrname{$2}, |
|
attrval => $3 |
|
} |
|
} |
|
|
|
return { |
|
resource => $s |
|
} |
|
} |
|
|
|
sub commasep ($$) { |
|
my($k,$v) = @_; |
|
|
|
if ($k =~ /^(NoSource)$/) { |
|
return split(/\s*,\s*/, $v); |
|
} elsif ($k =~ /^(PreReq|BuildPreReq|Provides|Conflicts)$/) { |
|
return map { make_resource($_) } |
|
split(/\s*,\s*/, $v); |
|
} |
|
|
|
return $v; |
|
} |
|
|
|
sub optesc ($) { |
|
my($s) = @_; |
|
|
|
$s =~ s/([\x00-\x1f\x80-\xbf\s\%])/sprintf("%%%02x",ord($1))/eg; |
|
|
|
return $s; |
|
} |
|
|
|
sub vsub ($$) { |
|
my($var,$v) = @_; |
|
|
|
$v =~ s/\%\{([^}]+)\}/ |
|
exists $var->{$1} ? $var->{$1} : '%{'.$1.'}'/emg; |
|
|
|
return $v; |
|
} |
|
|
|
sub upn ($) { |
|
my($t) = @_; |
|
my(@tok) = $t =~ /(\(|\)|\&\&|\|\||\!|\S+)/g; |
|
my(@out,$op,$o); |
|
my(@save); |
|
|
|
$op = []; |
|
foreach (@tok) { |
|
if ($_ eq '(') { |
|
push @save, $op; |
|
$op = []; |
|
} elsif ($_ eq ')') { |
|
die "FATAL: unresolved operators in: @tok\n" if @$op; |
|
$op = pop @save |
|
or die "FATAL: parenthesis stack underflow 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 (/^\%\{(\S*?)\}$/) { |
|
push @out, $1; |
|
while ($o = pop @$op) { |
|
push @out, $o->[0]; |
|
last if $o->[1]; # binop |
|
} |
|
} |
|
} |
|
|
|
return join (' ',@out); |
|
} |
|
|
|
# |
|
# deduce external variables from description |
|
# |
|
# 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 #if/#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 |
|
# |
|
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 |
|
$s =~ s/\\\n/ /sg; |
|
|
|
# |
|
# map 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 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 interpolation |
|
# |
|
%evar = %$ovar; |
|
|
|
# |
|
# guess more external parameters by scanning for "default" sections. |
|
# |
|
$re = '^\#ifndef\s+[\w\_]+\s*\n((?:\#define\s+[\w\_]+\s.*\n)+)\#endif\n'; |
|
@defs = $s =~ /$re/gm; |
|
foreach (@defs) { |
|
while (/^\#define\s+([\w\_]+)\s(.*?)\s*$/mg) { |
|
$ovar->{$1} = $2; |
|
$evar{$1} = '%{'.$1.'}'; |
|
} |
|
} |
|
$s =~ s/$re//gm; |
|
|
|
# |
|
# add everything looking like a with_ variable |
|
# |
|
$re = '%{(with\_[\w\_]+)}'; |
|
@defs = $s =~ /$re/gm; |
|
foreach (@defs) { |
|
next if exists $ovar->{$1}; |
|
$ovar->{$1} = '%{'.$1.'}'; |
|
$evar{$1} = '%{'.$1.'}'; |
|
} |
|
|
|
# |
|
# extract all conditional sections |
|
# |
|
@term = (); |
|
%var = (); |
|
$cond = ''; |
|
foreach $l (split(/\n/, $s)) { |
|
$v = vsub(\%avar, vsub(\%var, $l)); |
|
|
|
if (($p) = $v =~ /^\#if\s+(.*?)\s*$/) { |
|
# |
|
# normalize #if expressions |
|
# "%{variable}" == "yes" |
|
# "%{variable}" == "no" |
|
# operators ! && || |
|
# |
|
$term = ''; |
|
while ($p =~ /(!=)|(\!|\|\||\&\&|\(|\))|"\%\{([^}]+)\}"\s*==\s*"(yes|no)"|(\S+)/g) { |
|
if (defined $1) { |
|
warn "WARNING: unknown token '$1':\n< $l\n> $v\n"; |
|
} elsif (defined $5) { |
|
warn "WARNING: unknown token '$5':\n< $l\n> $v\n"; |
|
} elsif (defined $2) { |
|
$term .= " $2 "; |
|
} elsif (exists $evar{$3}) { |
|
$term .= ($4 eq 'no' ? '! ' : '').vsub(\%evar,'%{'.$3.'}'); |
|
} else { |
|
warn "WARNING: unknown conditional '$3':\n< $l\n> $v\n"; |
|
} |
|
} |
|
|
|
# |
|
# 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 "FATAL: else without if\n"; |
|
} |
|
} 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 "WARNING: logic too complex for '$1':\n< $l\n> $v\n"; |
|
} |
|
} else { |
|
$var{$1} = $2; |
|
} |
|
} elsif ($v =~ /^\#(?:undefine)\s*(\S+)\s*$/) { |
|
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 "ERROR: no package name set for option $1 = $2\n"; |
|
} |
|
|
|
} 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 for current condition |
|
# |
|
push @{$attr{$1}->{$cond}}, commasep($1,$2); |
|
$avar{lc($1)} = $2 if $cond eq ''; |
|
} |
|
} |
|
|
|
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. |
|
# |
|
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(/^(?=%\w+\s*\n)/m, $s)) { |
|
if (/^%(\w+)\s*\n/) { |
|
$map{$1} .= $'; |
|
} else { |
|
$map{'*'} .= $_; |
|
} |
|
} |
|
|
|
if (exists $map{'description'}) { |
|
$o = find_options($map{'description'}); |
|
$a = package2data($map{'*'}, $o ); |
|
$a->{'Description'} = { '' => [ $map{'description'} ] }; |
|
} else { |
|
$a = package2data($map{'*'}, {}); |
|
} |
|
|
|
return $a; |
|
} |
|
|
|
########################################################################## |
|
|
|
# |
|
# start of XML file |
|
# |
|
sub xml_head ($$) { |
|
my($fh,$res) = @_; |
|
print $fh <<EOFEOF; |
|
<?xml version="1.0" encoding="iso-8859-1"?> |
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
|
xmlns="http://www.openpkg.org/xml-rdf-index/0.9"> |
|
<Repository rdf:resource="$res"> |
|
EOFEOF |
|
} |
|
|
|
# |
|
# end of XML file, corresponds with start tags |
|
# |
|
sub xml_foot ($) { |
|
my($fh) = @_; |
|
print $fh <<EOFEOF; |
|
</Repository> |
|
</rdf:RDF> |
|
EOFEOF |
|
} |
|
|
|
sub n($$) { |
|
my($a,$k) = @_; |
|
return unless $a->{$k}; |
|
return unless $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 "" unless exists $a->{$k}; |
|
$tag = $k unless 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 "" unless exists $a->{$k}; |
|
$tag = $k unless 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 "" unless exists $a->{$k}; |
|
$tag = $k unless defined $tag; |
|
$out = ''; |
|
$i = ' ' x $i; |
|
|
|
foreach $cond (sort keys %{$a->{$k}}) { |
|
next unless @{$a->{$k}->{$cond}}; |
|
$upn = e(upn($cond)); |
|
$out .= $i. |
|
($cond ne '' ? "<$tag cond=\"$upn\">\n" : "<$tag>\n"). |
|
"$i <rdf:bag>\n". |
|
join("", |
|
map { |
|
ref $_ |
|
? "$i <resource". |
|
( exists $_->{attrname} |
|
? " $_->{attrname}=\"".e($_->{attrval})."\"" |
|
: "" |
|
). |
|
">".e($_->{resource})."</resource>\n" |
|
: "$i <rdf:li>".e($_)."</rdf:li>\n" |
|
} |
|
@{$a->{$k}->{$cond}}). |
|
"$i </rdf:bag>\n". |
|
"$i</$tag>\n"; |
|
} |
|
|
|
return $out; |
|
} |
|
|
|
# |
|
# send out reference to another RDF |
|
# |
|
sub xml_reference ($$$) { |
|
my($fh, $res, $href) = @_; |
|
|
|
print $fh <<EOFEOF; |
|
<Repository rdf:resource="$res" href="$href"/> |
|
EOFEOF |
|
} |
|
|
|
# |
|
# translate attributes from %$a as generated by package2data |
|
# into XML and write to file $fh |
|
# |
|
sub xml_record ($$$) { |
|
my($fh, $a, $href) = @_; |
|
my($maj,$min,$rel,$about); |
|
|
|
$about = |
|
n($a,'Name').'-'. |
|
n($a,'Version').'-'. |
|
n($a,'Release'); |
|
|
|
unless (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') =~ /^(\d+)\.(\d+)\.(\d+)/; |
|
|
|
if (defined $min) { |
|
if ($maj > 1 || ($maj == 1 && $min > 0)) { |
|
# 1.1 or later |
|
if (n($a,'Distribution') =~ /\[PLUS\]/) { |
|
$href = 'PLUS/'.$href; |
|
} |
|
} |
|
if ($maj > 1 || ($maj == 1 && $min >= 0)) { |
|
# 1.0 or later |
|
if ($rel > 0) { |
|
$href = 'UPD/'.$href; |
|
} |
|
} |
|
} else { |
|
# current |
|
} |
|
|
|
} |
|
|
|
print $fh <<EOFEOF; |
|
<rdf:Description about="$about" href="$href"> |
|
EOFEOF |
|
|
|
# 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; |
|
#s/.*\///; |
|
$_; |
|
} |
|
map { |
|
$a->{$_}->{''} ? @{$a->{$_}->{''}} : () |
|
} |
|
sort { |
|
my($x) = $a =~ /^(\d*)$/; |
|
my($y) = $b =~ /^(\d*)$/; |
|
return $x <=> $y; |
|
} |
|
grep { |
|
/^Source\d*$/ |
|
} keys %$a |
|
]}; |
|
delete $a->{'Source'} unless @{$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, '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 <<EOFEOF; |
|
</rdf:Description> |
|
EOFEOF |
|
} |
|
|
|
##################################################################### |
|
|
|
sub rpm2spec ($) { |
|
my($fn) = @_; |
|
local($SIG{'PIPE'}) = 'IGNORE'; |
|
my($pipe) = new FileHandle "$R2C '$fn' |" |
|
or die "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 unless 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 !~ /.spec$/) { |
|
while ($m > 0) { |
|
$step = $m > 8192 ? 8192 : $m; |
|
last unless read($pipe,$buf,$step); |
|
$m -= length($buf); |
|
} |
|
} else { |
|
if (read($pipe,$buf,$n) == $n) { |
|
$spec = $buf; |
|
} |
|
last; |
|
} |
|
} |
|
$pipe->close; |
|
|
|
return $spec; |
|
} |
|
|
|
##################################################################### |
|
|
|
sub rpm2data ($$) { |
|
my($fn,$platform) = @_; |
|
my($q,$pipe,%a); |
|
my($t,$v); |
|
|
|
unless (defined $platform) { |
|
die "FATAL: indexing binary package '$fn' requires -p option\n"; |
|
} |
|
|
|
$q = <<EOFEOF; |
|
Name %{Name} |
|
Version %{Version} |
|
Release %{Release} |
|
URL %{URL} |
|
Summary %{Summary} |
|
Copyright %{Copyright} |
|
License %{License} |
|
Distribution %{Distribution} |
|
Vendor %{Vendor} |
|
Group %{Group} |
|
Packager %{Packager} |
|
Prefixes %{Prefixes} |
|
BuildHost %{BuildHost} |
|
BuildTime %{BuildTime} |
|
Arch %{Arch} |
|
Os %{Os} |
|
Size %{Size} |
|
SigSize %{SigSize} |
|
SigMD5 %{SigMD5} |
|
SigPGP %{SigPGP} |
|
SigGPG %{SigGPG} |
|
SourceRPM %{SourceRPM} |
|
[Patch %{Patch} |
|
] |
|
[Source %{Source} |
|
] |
|
[Filenames %{Filenames} |
|
] |
|
[Conflicts %{CONFLICTNAME} %|CONFLICTFLAGS?{%{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}}:{}| |
|
] |
|
[PreReq %{REQUIRENAME} %|REQUIREFLAGS?{%{REQUIREFLAGS:depflags} %{REQUIREVERSION}}:{}| |
|
] |
|
[Provides %{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}| |
|
] |
|
Description %{Description} |
|
EOFEOF |
|
|
|
$pipe = new FileHandle "$RPM -qp --qf '$q' '$fn' |" |
|
or die "FATAL: cannot read '$fn' ($!)\n"; |
|
while (<$pipe>) { |
|
if (/^(\S+)\s+(.*?)\s*$/) { |
|
$t = $1; |
|
$v = $2; |
|
} elsif (/^(\s+.+?)\s*$/) { |
|
next unless defined $t; |
|
$v = $1; |
|
} else { |
|
$t = undef; |
|
next; |
|
} |
|
|
|
if (exists $a{$t}) { |
|
$a{$t} .= "\n$v"; |
|
} else { |
|
$a{$t} = $v; |
|
} |
|
} |
|
$pipe->close; |
|
|
|
%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 }; |
|
} |
|
|
|
##################################################################### |
|
|
|
sub getindex ($) { |
|
my($dir) = @_; |
|
my(@idx) = sort { -M $a <=> -M $b; } |
|
grep { -f $_ } |
|
( <$dir/00INDEX.rdf>, <$dir/00INDEX.rdf.*> ); |
|
|
|
return unless @idx; |
|
return $idx[0]; |
|
} |
|
|
|
sub list_specdir ($) { |
|
my($dir) = @_; |
|
my($dh,$d,$path); |
|
my(@list); |
|
|
|
$dh = new DirHandle($dir); |
|
while ($d = $dh->read) { |
|
next if $d =~ /^\./; |
|
$path = "$dir/$d/$d.spec"; |
|
push @list, $path if -f $path; |
|
} |
|
|
|
return \@list; |
|
} |
|
|
|
sub list_rpmdir ($) { |
|
my($dir) = @_; |
|
my($dh,$d,$path); |
|
my(@list,$idx,$sub); |
|
|
|
$dh = new DirHandle($dir); |
|
while ($d = $dh->read) { |
|
next if $d =~ /^\./; |
|
$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 unless $d =~ /\.rpm$/ && -f $path; |
|
push @list, $path; |
|
} |
|
} |
|
|
|
return \@list; |
|
} |
|
|
|
##################################################################### |
|
|
|
sub readfile ($) { |
|
my($fn) = @_; |
|
my($fh) = new FileHandle "< $fn" |
|
or die "FATAL: cannot read '$fn' ($!)\n"; |
|
my(@l) = <$fh>; |
|
$fh->close; |
|
return join('',@l); |
|
} |
|
|
|
sub relpath ($$) { |
|
my($prefix,$path) = @_; |
|
$path =~ s/^\Q$prefix\E\///s; |
|
return $path; |
|
} |
|
|
|
sub dirname ($) { |
|
my($path) = @_; |
|
$path =~ s/\/[^\/]*$//s; |
|
return $path.'/'; |
|
} |
|
|
|
sub getresource ($) { |
|
my($fn) = @_; |
|
my($fh, $buf); |
|
|
|
if ($fn =~ /\.bz2$/) { |
|
$fh = new FileHandle "$BZ -dc $fn |" |
|
or die "FATAL: cannot read '$fn' ($!)\n"; |
|
} else { |
|
$fh = new FileHandle "< $fn" |
|
or die "FATAL: cannot read '$fn' ($!)\n"; |
|
} |
|
$fh->read($buf, 1024); |
|
$fh->close; |
|
|
|
if ($buf =~ /<Repository.*?rdf:resource="([^"]+)"/) { |
|
return $1; |
|
} |
|
|
|
return undef; |
|
} |
|
|
|
##################################################################### |
|
|
|
sub write_index ($$$$$$) { |
|
my($fh,$prefix,$resource,$platform,$list,$cache) = @_; |
|
my($a,$h,$r,$spec); |
|
my($mtime); |
|
|
|
foreach (@$list) { |
|
$a = undef; |
|
$h = undef; |
|
$r = undef; |
|
if (/\.spec$/) { |
|
$spec = readfile($_); |
|
$a = spec2data($spec); |
|
} elsif (/([^\/]+\.(?:no)?src\.rpm)$/) { |
|
$h = relpath($prefix, $_); |
|
if ($cache) { |
|
$mtime = (stat $_)[9]; |
|
if (exists $cache->{"M$_"} && |
|
$cache->{"M$_"} == $mtime) { |
|
$spec = $cache->{"S$_"}; |
|
} else { |
|
$spec = rpm2spec($_); |
|
$cache->{"S$_"} = $spec; |
|
$cache->{"M$_"} = $mtime; |
|
} |
|
} else { |
|
$spec = rpm2spec($_); |
|
} |
|
$a = spec2data($spec); |
|
} elsif (/([^\/]+\.rpm)$/) { |
|
$h = relpath($prefix, $_); |
|
$a = rpm2data($_, $platform); |
|
} elsif (/([^\/]+\.rdf[^\/]*)$/) { |
|
$h = relpath($prefix, $_); |
|
$r = getresource($_) || $resource.dirname($h); |
|
} |
|
|
|
if ($a) { |
|
xml_record($fh, $a, $h); |
|
} elsif ($r) { |
|
xml_reference($fh, $r, $h); |
|
} else { |
|
warn "ERROR: cannot process $_\n"; |
|
} |
|
} |
|
} |
|
|
|
##################################################################### |
|
|
|
my($prefix,$list,$fh,%cache,$tmpo); |
|
|
|
if ($#ARGV < 0) { |
|
print "openpkg:index:USAGE: $0 [-r resource] [-p platform] [-C cache.db] [-o index.rdf] [-c] [-i] dir ...\n"; |
|
die "\n"; |
|
} |
|
|
|
if ($opt_C) { |
|
eval { |
|
require DB_File; |
|
}; |
|
if ($@) { |
|
die "Sorry. The -C option requires an installed DB_File perl module.\n"; |
|
} |
|
tie %cache, 'DB_File', $opt_C, O_CREAT|O_RDWR, 0666, $DB_File::DB_HASH |
|
or die "FATAL: cannot tie cache '$opt_C' ($!)\n"; |
|
} |
|
|
|
$opt_r = 'OpenPKG-CURRENT/Source/' unless defined $opt_r; |
|
|
|
if (defined $opt_o) { |
|
$tmpo = $opt_o . '.tmp'; |
|
if ($opt_c) { |
|
$fh = new FileHandle "| $BZ -c > '$tmpo'" |
|
or die "FATAL: cannot write '$tmpo' ($!)\n"; |
|
} else { |
|
$fh = new FileHandle "> $tmpo" |
|
or die "FATAL: cannot write '$tmpo' ($!)\n"; |
|
} |
|
} else { |
|
if ($opt_c) { |
|
$fh = new FileHandle "| $BZ -c" |
|
or die "FATAL: cannot write to stdout ($!)\n"; |
|
} else { |
|
$fh = new FileHandle ">&=1" |
|
or die "FATAL: cannot write to stdout ($!)\n"; |
|
} |
|
} |
|
|
|
xml_head($fh, $opt_r); |
|
foreach $prefix (@ARGV) { |
|
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); |
|
|
|
$fh->close |
|
or die "FATAL: write error on output ($!)\n"; |
|
|
|
if (defined $tmpo) { |
|
rename $tmpo,$opt_o |
|
or die "FATAL: cannot rename $tmpo to $opt_o ($!)\n"; |
|
} |
|
|
|
|