Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1299 lines (1240 sloc) 37.9 KB
################################################################
#
# Copyright (c) 1995-2014 SUSE Linux Products GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see the file COPYING); if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
################################################################
package Build::Rpm;
our $unfilteredprereqs = 0;
our $conflictdeps = 0;
use strict;
use Digest::MD5;
sub expr {
my $expr = shift;
my $lev = shift;
$lev ||= 0;
my ($v, $v2);
$expr =~ s/^\s+//;
my $t = substr($expr, 0, 1);
if ($t eq '(') {
($v, $expr) = expr(substr($expr, 1), 0);
return undef unless defined $v;
return undef unless $expr =~ s/^\)//;
} elsif ($t eq '!') {
($v, $expr) = expr(substr($expr, 1), 5);
return undef unless defined $v;
$v = 0 if $v && $v eq '\"\"';
$v =~ s/^0+/0/ if $v;
$v = !$v;
} elsif ($t eq '-') {
($v, $expr) = expr(substr($expr, 1), 5);
return undef unless defined $v;
$v = -$v;
} elsif ($expr =~ /^([0-9]+)(.*?)$/) {
$v = $1;
$expr = $2;
} elsif ($expr =~ /^([a-zA-Z_0-9]+)(.*)$/) {
$v = "\"$1\"";
$expr = $2;
} elsif ($expr =~ /^(\".*?\")(.*)$/) {
$v = $1;
$expr = $2;
} else {
return;
}
return ($v, $expr) if $lev >= 5;
while (1) {
$expr =~ s/^\s+//;
if ($expr =~ /^&&/) {
return ($v, $expr) if $lev > 1;
($v2, $expr) = expr(substr($expr, 2), 1);
return undef unless defined $v2;
$v = 0 if $v && $v eq '\"\"';
$v =~ s/^0+/0/;
$v2 = 0 if $v2 && $v2 eq '\"\"';
$v2 =~ s/^0+/0/;
$v &&= $v2;
} elsif ($expr =~ /^\|\|/) {
return ($v, $expr) if $lev > 1;
($v2, $expr) = expr(substr($expr, 2), 1);
return undef unless defined $v2;
$v = 0 if $v && $v eq '\"\"';
$v =~ s/^0+/0/;
$v2 = 0 if $v2 && $v2 eq '\"\"';
$v2 =~ s/^0+/0/;
$v ||= $v2;
} elsif ($expr =~ /^>=/) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 2), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v ge $v2 : $v >= $v2) ? 1 : 0;
} elsif ($expr =~ /^>/) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 1), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v gt $v2 : $v > $v2) ? 1 : 0;
} elsif ($expr =~ /^<=/) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 2), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v le $v2 : $v <= $v2) ? 1 : 0;
} elsif ($expr =~ /^</) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 1), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v lt $v2 : $v < $v2) ? 1 : 0;
} elsif ($expr =~ /^==/) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 2), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v eq $v2 : $v == $v2) ? 1 : 0;
} elsif ($expr =~ /^!=/) {
return ($v, $expr) if $lev > 2;
($v2, $expr) = expr(substr($expr, 2), 2);
return undef unless defined $v2;
$v = (($v =~ /^\"/) ? $v ne $v2 : $v != $v2) ? 1 : 0;
} elsif ($expr =~ /^\+/) {
return ($v, $expr) if $lev > 3;
($v2, $expr) = expr(substr($expr, 1), 3);
return undef unless defined $v2;
$v += $v2;
} elsif ($expr =~ /^-/) {
return ($v, $expr) if $lev > 3;
($v2, $expr) = expr(substr($expr, 1), 3);
return undef unless defined $v2;
$v -= $v2;
} elsif ($expr =~ /^\*/) {
($v2, $expr) = expr(substr($expr, 1), 4);
return undef unless defined $v2;
$v *= $v2;
} elsif ($expr =~ /^\//) {
($v2, $expr) = expr(substr($expr, 1), 4);
return undef unless defined $v2 && 0 + $v2;
$v /= $v2;
} elsif ($expr =~ /^([=&|])/) {
warn("syntax error while parsing $1$1\n");
return ($v, $expr);
} else {
return ($v, $expr);
}
}
}
sub adaptmacros {
my ($macros, $optold, $optnew) = @_;
for (keys %$optold) {
delete $macros->{$_};
}
for (keys %$optnew) {
$macros->{$_} = $optnew->{$_};
}
return $optnew;
}
sub grabargs {
my ($macname, $getopt, @args) = @_;
my %m;
$m{'0'} = $macname;
$m{'**'} = join(' ', @args);
my %go;
%go = ($getopt =~ /(.)(:*)/sg) if defined $getopt;
while (@args && $args[0] =~ s/^-//) {
my $o = shift @args;
last if $o eq '-';
while ($o =~ /^(.)(.*)$/) {
if ($go{$1}) {
my $arg = $2;
$arg = shift(@args) if @args && $arg eq '';
$m{"-$1"} = "-$1 $arg";
$m{"-$1*"} = $arg;
last;
}
$m{"-$1"} = "-$1";
$o = $2;
}
}
$m{'#'} = scalar(@args);
my $i = 1;
for (@args) {
$m{$i} = $_;
$i++;
}
$m{'*'} = join(' ', @args);
return \%m;
}
# xspec may be passed as array ref to return the parsed spec files
# an entry in the returned array can be
# - a string: verbatim line from the original file
# - a two element array ref:
# - [0] original line
# - [1] undef: line unused due to %if
# - [1] scalar: line after macro expansion. Only set if it's a build deps
# line and build deps got modified or 'save_expanded' is set in
# config
sub parse {
my ($config, $specfile, $xspec) = @_;
my $packname;
my $exclarch;
my $badarch;
my @subpacks;
my @packdeps;
my @prereqs;
my $hasnfb;
my $nfbline;
my %macros;
my %macros_args;
my $ret = {};
my $ifdeps;
my $specdata;
local *SPEC;
if (ref($specfile) eq 'GLOB') {
*SPEC = *$specfile;
} elsif (ref($specfile) eq 'ARRAY') {
$specdata = [ @$specfile ];
} elsif (!open(SPEC, '<', $specfile)) {
warn("$specfile: $!\n");
$ret->{'error'} = "open $specfile: $!";
return $ret;
}
my @macros = @{$config->{'macros'}};
my $skip = 0;
my $main_preamble = 1;
my $preamble = 1;
my $inspec = 0;
my $hasif = 0;
my $lineno = 0;
my $obspackage = defined($config->{'obspackage'}) ? $config->{'obspackage'} : '@OBS_PACKAGE@';
my $buildflavor = defined($config->{'buildflavor'}) ? $config->{'buildflavor'} : '';
while (1) {
my $line;
if (@macros) {
$line = shift @macros;
$hasif = 0 unless @macros;
} elsif ($specdata) {
$inspec = 1;
last unless @$specdata;
$line = shift @$specdata;
++$lineno;
if (ref $line) {
$line = $line->[0]; # verbatim line
push @$xspec, $line if $xspec;
$xspec->[-1] = [ $line, undef ] if $xspec && $skip;
next;
}
} else {
$inspec = 1;
$line = <SPEC>;
last unless defined $line;
chomp $line;
++$lineno;
}
push @$xspec, $line if $inspec && $xspec;
if ($line =~ /^#\s*neededforbuild\s*(\S.*)$/) {
if (defined $hasnfb) {
$xspec->[-1] = [ $xspec->[-1], undef ] if $inspec && $xspec;
next;
}
$hasnfb = $1;
$nfbline = \$xspec->[-1] if $inspec && $xspec;
next;
}
if ($line =~ /^\s*#/) {
next unless $line =~ /^#!Build(?:Ignore|Conflicts)\s*:/i;
}
my $expandedline = '';
if (!$skip && ($line =~ /%/)) {
my $tries = 0;
my @expandstack;
my $optmacros = {};
# newer perls: \{((?:(?>[^{}]+)|(?2))*)\}
reexpand:
while ($line =~ /^(.*?)%(\{([^\}]+)\}|[\?\!]*[0-9a-zA-Z_]+|%|\*\*?|#|\()(.*?)$/) {
if ($tries++ > 1000) {
print STDERR "Warning: spec file parser ",($lineno?" line $lineno":''),": macro too deeply nested\n" if $config->{'warnings'};
$line = 'MACRO';
last;
}
$expandedline .= $1;
$line = $4;
my $macname = defined($3) ? $3 : $2;
my $macorig = $2;
my $macdata;
my $macalt;
if (defined($3)) {
if ($macname =~ /{/) { # {
while (($macname =~ y/{/{/) > ($macname =~ y/}/}/)) {
last unless $line =~ /^([^}]*)}(.*)$/;
$macname .= "}$1";
$macorig .= "$1}";
$line = $2;
}
}
$macdata = '';
if ($macname =~ /^([^\s:]+)([\s:])(.*)$/) {
$macname = $1;
if ($2 eq ':') {
$macalt = $3;
} else {
$macdata = $3;
}
}
}
my $mactest = 0;
if ($macname =~ /^\!\?/ || $macname =~ /^\?\!/) {
$mactest = -1;
} elsif ($macname =~ /^\?/) {
$mactest = 1;
}
$macname =~ s/^[\!\?]+//;
if ($macname eq '%') {
$expandedline .= '%';
next;
} elsif ($macname eq '(') {
print STDERR "Warning: spec file parser",($lineno?" line $lineno":''),": can't expand %(...)\n" if $config->{'warnings'};
$line = 'MACRO';
last;
} elsif ($macname eq 'define' || $macname eq 'global') {
if ($line =~ /^\s*([0-9a-zA-Z_]+)(?:\(([^\)]*)\))?\s*(.*?)$/) {
my $macname = $1;
my $macargs = $2;
my $macbody = $3;
if (defined $macargs) {
$macros_args{$macname} = $macargs;
} else {
delete $macros_args{$macname};
}
$macros{$macname} = $macbody;
}
$line = '';
last;
} elsif ($macname eq 'defined' || $macname eq 'with' || $macname eq 'undefined' || $macname eq 'without' || $macname eq 'bcond_with' || $macname eq 'bcond_without') {
my @args;
if ($macorig =~ /^\{(.*)\}$/) {
@args = split(' ', $1);
shift @args;
} else {
@args = split(' ', $line);
$line = '';
}
next unless @args;
if ($macname eq 'bcond_with') {
$macros{"with_$args[0]"} = 1 if exists $macros{"_with_$args[0]"};
next;
}
if ($macname eq 'bcond_without') {
$macros{"with_$args[0]"} = 1 unless exists $macros{"_without_$args[0]"};
next;
}
$args[0] = "with_$args[0]" if $macname eq 'with' || $macname eq 'without';
$line = ((exists($macros{$args[0]}) ? 1 : 0) ^ ($macname eq 'undefined' || $macname eq 'without' ? 1 : 0)).$line;
} elsif ($macname eq 'expand') {
$macalt = $macros{$macname} unless defined $macalt;
$macalt = '' if $mactest == -1;
push @expandstack, ($expandedline, $line, undef);
$line = $macalt;
$expandedline = '';
} elsif (exists($macros{$macname})) {
if (!defined($macros{$macname})) {
print STDERR "Warning: spec file parser",($lineno?" line $lineno":''),": can't expand '$macname'\n" if $config->{'warnings'};
$line = 'MACRO';
last;
}
if (defined($macros_args{$macname})) {
# macro with args!
if (!defined($macdata)) {
$line =~ /^\s*([^\n]*).*$/;
$macdata = $1;
$line = '';
}
push @expandstack, ($expandedline, $line, $optmacros);
$optmacros = adaptmacros(\%macros, $optmacros, grabargs($macname, $macros_args{$macname}, split(' ', $macdata)));
$line = $macros{$macname};
$expandedline = '';
next;
}
$macalt = $macros{$macname} unless defined $macalt;
$macalt = '' if $mactest == -1;
if ($macalt =~ /%/) {
push @expandstack, ('', $line, 1) if $line ne '';
$line = $macalt;
} else {
$expandedline .= $macalt;
}
} elsif ($mactest) {
$macalt = '' if !defined($macalt) || $mactest == 1;
if ($macalt =~ /%/) {
push @expandstack, ('', $line, 1) if $line ne '';
$line = $macalt;
} else {
$expandedline .= $macalt;
}
} else {
$expandedline .= "%$macorig" unless $macname =~ /^-/;
}
}
$line = $expandedline . $line;
if (@expandstack) {
my $m = pop(@expandstack);
if ($m) {
$optmacros = adaptmacros(\%macros, $optmacros, $m) if ref $m;
$expandstack[-2] .= $line;
$line = pop(@expandstack);
$expandedline = pop(@expandstack);
} else {
my $todo = pop(@expandstack);
$expandedline = pop(@expandstack);
push @expandstack, ('', $todo, 1) if $todo ne '';
}
goto reexpand;
}
}
if ($line =~ /^\s*%else\b/) {
$skip = 1 - $skip if $skip < 2;
next;
}
if ($line =~ /^\s*%endif\b/) {
$skip-- if $skip;
next;
}
$skip++ if $skip && $line =~ /^\s*%if/;
if ($skip) {
$xspec->[-1] = [ $xspec->[-1], undef ] if $xspec;
$ifdeps = 1 if $line =~ /^(BuildRequires|BuildPrereq|BuildConflicts|\#\!BuildIgnore|\#\!BuildConflicts)\s*:\s*(\S.*)$/i;
next;
}
if ($line =~ /\@/) {
$line =~ s/\@BUILD_FLAVOR\@/$buildflavor/g;
$line =~ s/\@OBS_PACKAGE\@/$obspackage/g;
}
if ($line =~ /^\s*%ifarch(.*)$/) {
my $arch = $macros{'_target_cpu'} || 'unknown';
my @archs = grep {$_ eq $arch} split(/\s+/, $1);
$skip = 1 if !@archs;
$hasif = 1;
next;
}
if ($line =~ /^\s*%ifnarch(.*)$/) {
my $arch = $macros{'_target_cpu'} || 'unknown';
my @archs = grep {$_ eq $arch} split(/\s+/, $1);
$skip = 1 if @archs;
$hasif = 1;
next;
}
if ($line =~ /^\s*%ifos(.*)$/) {
my $os = $macros{'_target_os'} || 'unknown';
my @oss = grep {$_ eq $os} split(/\s+/, $1);
$skip = 1 if !@oss;
$hasif = 1;
next;
}
if ($line =~ /^\s*%ifnos(.*)$/) {
my $os = $macros{'_target_os'} || 'unknown';
my @oss = grep {$_ eq $os} split(/\s+/, $1);
$skip = 1 if @oss;
$hasif = 1;
next;
}
if ($line =~ /^\s*%if(.*)$/) {
my ($v, $r) = expr($1);
$v = 0 if $v && $v eq '\"\"';
$v =~ s/^0+/0/ if $v;
$skip = 1 unless $v;
$hasif = 1;
next;
}
if ($main_preamble) {
if ($line =~ /^(Name|Version|Disttag|Release)\s*:\s*(\S+)/i) {
$ret->{lc $1} = $2;
$macros{lc $1} = $2;
} elsif ($line =~ /^ExclusiveArch\s*:\s*(.*)/i) {
$exclarch ||= [];
push @$exclarch, split(' ', $1);
} elsif ($line =~ /^ExcludeArch\s*:\s*(.*)/i) {
$badarch ||= [];
push @$badarch, split(' ', $1);
}
}
if (@subpacks && $preamble && exists($ret->{'version'}) && $line =~ /^Version\s*:\s*(\S+)/i) {
$ret->{'multiversion'} = 1 if $ret->{'version'} ne $1;
}
if ($line =~ /^(?:Requires\(pre\)|Requires\(post\)|PreReq)\s*:\s*(\S.*)$/i) {
my $deps = $1;
my @deps = $deps =~ /([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*/g;
while (@deps) {
my ($pack, $vers, $qual) = splice(@deps, 0, 3);
if (!$unfilteredprereqs && $pack =~ /^\//) {
$ifdeps = 1;
next unless $config->{'fileprovides'}->{$pack};
}
push @prereqs, $pack unless grep {$_ eq $pack} @prereqs;
}
next;
}
if ($preamble && ($line =~ /^(BuildRequires|BuildPrereq|BuildConflicts|\#\!BuildIgnore|\#\!BuildConflicts)\s*:\s*(\S.*)$/i)) {
my $what = $1;
my $deps = $2;
$ifdeps = 1 if $hasif;
# XXX: weird syntax addition. can append arch or project to dependency
# BuildRequire: foo > 17 [i586,x86_64]
# BuildRequire: foo [home:bar]
# BuildRequire: foo [!home:bar]
my @deps;
if (" $deps" =~ /[\s,]\(/) {
# we need to be careful, there could be a rich dep
my $d = $deps;
while ($d ne '') {
if ($d =~ /^\(/) {
my @s = split(' ', $d);
push @deps, shiftrich(\@s), undef, undef;
$d = join(' ', @s);
} else {
last unless $d =~ s/([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*//;
push @deps, $1, $2, $3;
}
}
} else {
@deps = $deps =~ /([^\s\[,]+)(\s+[<=>]+\s+[^\s\[,]+)?(\s+\[[^\]]+\])?[\s,]*/g;
}
my $replace = 0;
my @ndeps = ();
while (@deps) {
my ($pack, $vers, $qual) = splice(@deps, 0, 3);
if (defined($qual)) {
$replace = 1;
my $arch = $macros{'_target_cpu'} || '';
my $proj = $macros{'_target_project'} || '';
$qual =~ s/^\s*\[//;
$qual =~ s/\]$//;
my $isneg = 0;
my $bad;
for my $q (split('[\s,]', $qual)) {
$isneg = 1 if $q =~ s/^\!//;
$bad = 1 if !defined($bad) && !$isneg;
if ($isneg) {
if ($q eq $arch || $q eq $proj) {
$bad = 1;
last;
}
} elsif ($q eq $arch || $q eq $proj) {
$bad = 0;
}
}
next if $bad;
}
$vers = '' unless defined $vers;
$vers =~ s/=(>|<)/$1=/;
push @ndeps, "$pack$vers";
}
$replace = 1 if grep {/^-/} @ndeps;
if (lc($what) ne 'buildrequires' && lc($what) ne 'buildprereq') {
if ($conflictdeps && $what =~ /conflict/i) {
push @packdeps, map {"!$_"} @ndeps;
next;
}
push @packdeps, map {"-$_"} @ndeps;
next;
}
if (defined($hasnfb)) {
if ((grep {$_ eq 'glibc' || $_ eq 'rpm' || $_ eq 'gcc' || $_ eq 'bash'} @ndeps) > 2) {
# ignore old generated BuildRequire lines.
$xspec->[-1] = [ $xspec->[-1], undef ] if $xspec;
next;
}
}
push @packdeps, @ndeps;
next unless $xspec && $inspec;
if ($replace) {
my @cndeps = grep {!/^-/} @ndeps;
if (@cndeps) {
$xspec->[-1] = [ $xspec->[-1], "$what: ".join(' ', @cndeps) ];
} else {
$xspec->[-1] = [ $xspec->[-1], ''];
}
}
next;
} elsif ($preamble && $line =~ /^(Source\d*|Patch\d*|Url|Icon)\s*:\s*(\S+)/i) {
my ($tag, $val) = (lc($1), $2);
# associate url and icon tags with the corresponding subpackage
$tag .= scalar @subpacks if ($tag eq 'url' || $tag eq 'icon') && @subpacks;
if ($tag =~ /icon/) {
# there can be a gif and xpm icon
push @{$ret->{$tag}}, $val;
} else {
$ret->{$tag} = $val;
}
if ($tag =~ /^(source|patch)(\d+)?$/) {
my $num = defined($2) ? $2 : ($1 eq 'source' ? 0 : -1);
$macros{uc($1) . "URL$num"} = $val if $num >= 0;
}
}
if ($line =~ /^\s*%package\s+(-n\s+)?(\S+)/) {
if ($1) {
push @subpacks, $2;
} else {
push @subpacks, $ret->{'name'}.'-'.$2 if defined $ret->{'name'};
}
$preamble = 1;
$main_preamble = 0;
}
if ($line =~ /^\s*%(prep|build|install|check|clean|preun|postun|pretrans|posttrans|pre|post|files|changelog|description|triggerpostun|triggerun|triggerin|trigger|verifyscript)/) {
$main_preamble = 0;
$preamble = 0;
}
# do this always?
if ($xspec && @$xspec && $config->{'save_expanded'}) {
$xspec->[-1] = [ $xspec->[-1], $line ];
}
}
close SPEC unless ref $specfile;
if (defined($hasnfb)) {
if (!@packdeps) {
@packdeps = split(' ', $hasnfb);
} elsif ($nfbline) {
$$nfbline = [$$nfbline, undef ];
}
}
unshift @subpacks, $ret->{'name'} if defined $ret->{'name'};
$ret->{'subpacks'} = \@subpacks;
$ret->{'exclarch'} = $exclarch if defined $exclarch;
$ret->{'badarch'} = $badarch if defined $badarch;
$ret->{'deps'} = \@packdeps;
$ret->{'prereqs'} = \@prereqs if @prereqs;
$ret->{'configdependent'} = 1 if $ifdeps;
return $ret;
}
###########################################################################
my %rpmstag = (
"SIGTAG_SIZE" => 1000, # Header+Payload size in bytes. */
"SIGTAG_PGP" => 1002, # RSA signature over Header+Payload
"SIGTAG_MD5" => 1004, # MD5 hash over Header+Payload
"SIGTAG_GPG" => 1005, # DSA signature over Header+Payload
"NAME" => 1000,
"VERSION" => 1001,
"RELEASE" => 1002,
"EPOCH" => 1003,
"SUMMARY" => 1004,
"DESCRIPTION" => 1005,
"BUILDTIME" => 1006,
"ARCH" => 1022,
"OLDFILENAMES" => 1027,
"SOURCERPM" => 1044,
"PROVIDENAME" => 1047,
"REQUIREFLAGS" => 1048,
"REQUIRENAME" => 1049,
"REQUIREVERSION" => 1050,
"NOSOURCE" => 1051,
"NOPATCH" => 1052,
"SOURCEPACKAGE" => 1106,
"PROVIDEFLAGS" => 1112,
"PROVIDEVERSION" => 1113,
"DIRINDEXES" => 1116,
"BASENAMES" => 1117,
"DIRNAMES" => 1118,
"DISTURL" => 1123,
"CONFLICTFLAGS" => 1053,
"CONFLICTNAME" => 1054,
"CONFLICTVERSION" => 1055,
"OBSOLETENAME" => 1090,
"OBSOLETEFLAGS" => 1114,
"OBSOLETEVERSION" => 1115,
"OLDSUGGESTSNAME" => 1156,
"OLDSUGGESTSVERSION" => 1157,
"OLDSUGGESTSFLAGS" => 1158,
"OLDENHANCESNAME" => 1159,
"OLDENHANCESVERSION" => 1160,
"OLDENHANCESFLAGS" => 1161,
"RECOMMENDNAME" => 5046,
"RECOMMENDVERSION" => 5047,
"RECOMMENDFLAGS" => 5048,
"SUGGESTNAME" => 5049,
"SUGGESTVERSION" => 5050,
"SUGGESTFLAGS" => 5051,
"SUPPLEMENTNAME" => 5052,
"SUPPLEMENTVERSION" => 5053,
"SUPPLEMENTFLAGS" => 5054,
"ENHANCENAME" => 5055,
"ENHANCEVERSION" => 5056,
"ENHANCEFLAGS" => 5057,
);
sub rpmq {
my ($rpm, @stags) = @_;
my @sigtags = grep {/^SIGTAG_/} @stags;
@stags = grep {!/^SIGTAG_/} @stags;
my $dosigs = @sigtags && !@stags;
@stags = @sigtags if $dosigs;
my $need_filenames = grep { $_ eq 'FILENAMES' } @stags;
push @stags, 'BASENAMES', 'DIRNAMES', 'DIRINDEXES', 'OLDFILENAMES' if $need_filenames;
@stags = grep { $_ ne 'FILENAMES' } @stags if $need_filenames;
my %stags = map {0 + ($rpmstag{$_} || $_) => $_} @stags;
my ($magic, $sigtype, $headmagic, $cnt, $cntdata, $lead, $head, $index, $data, $tag, $type, $offset, $count);
local *RPM;
my $forcebinary;
if (ref($rpm) eq 'ARRAY') {
($headmagic, $cnt, $cntdata) = unpack('N@8NN', $rpm->[0]);
if ($headmagic != 0x8eade801) {
warn("Bad rpm\n");
return ();
}
if (length($rpm->[0]) < 16 + $cnt * 16 + $cntdata) {
warn("Bad rpm\n");
return ();
}
$index = substr($rpm->[0], 16, $cnt * 16);
$data = substr($rpm->[0], 16 + $cnt * 16, $cntdata);
} else {
if (ref($rpm) eq 'GLOB') {
*RPM = *$rpm;
} elsif (!open(RPM, '<', $rpm)) {
warn("$rpm: $!\n");
return ();
}
if (read(RPM, $lead, 96) != 96) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
($magic, $sigtype) = unpack('N@78n', $lead);
if ($magic != 0xedabeedb || $sigtype != 5) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
$forcebinary = 1 if unpack('@6n', $lead) != 1;
if (read(RPM, $head, 16) != 16) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head);
if ($headmagic != 0x8eade801) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
if (read(RPM, $index, $cnt * 16) != $cnt * 16) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
$cntdata = ($cntdata + 7) & ~7;
if (read(RPM, $data, $cntdata) != $cntdata) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
}
my %res = ();
if (@sigtags && !$dosigs) {
%res = &rpmq(["$head$index$data"], @sigtags);
}
if (ref($rpm) eq 'ARRAY' && !$dosigs && @$rpm > 1) {
my %res2 = &rpmq([ $rpm->[1] ], @stags);
%res = (%res, %res2);
return %res;
}
if (ref($rpm) ne 'ARRAY' && !$dosigs) {
if (read(RPM, $head, 16) != 16) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head);
if ($headmagic != 0x8eade801) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
if (read(RPM, $index, $cnt * 16) != $cnt * 16) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
if (read(RPM, $data, $cntdata) != $cntdata) {
warn("Bad rpm $rpm\n");
close RPM unless ref($rpm);
return ();
}
}
close RPM unless ref($rpm);
# return %res unless @stags;
while($cnt-- > 0) {
($tag, $type, $offset, $count, $index) = unpack('N4a*', $index);
$tag = 0+$tag;
if ($stags{$tag} || !@stags) {
eval {
my $otag = $stags{$tag} || $tag;
if ($type == 0) {
$res{$otag} = [ '' ];
} elsif ($type == 1) {
$res{$otag} = [ unpack("\@${offset}c$count", $data) ];
} elsif ($type == 2) {
$res{$otag} = [ unpack("\@${offset}c$count", $data) ];
} elsif ($type == 3) {
$res{$otag} = [ unpack("\@${offset}n$count", $data) ];
} elsif ($type == 4) {
$res{$otag} = [ unpack("\@${offset}N$count", $data) ];
} elsif ($type == 5) {
$res{$otag} = [ undef ];
} elsif ($type == 6) {
$res{$otag} = [ unpack("\@${offset}Z*", $data) ];
} elsif ($type == 7) {
$res{$otag} = [ unpack("\@${offset}a$count", $data) ];
} elsif ($type == 8 || $type == 9) {
my $d = unpack("\@${offset}a*", $data);
my @res = split("\0", $d, $count + 1);
$res{$otag} = [ splice @res, 0, $count ];
} else {
$res{$otag} = [ undef ];
}
};
if ($@) {
warn("Bad rpm $rpm: $@\n");
return ();
}
}
}
if ($forcebinary && $stags{1044} && !$res{$stags{1044}} && !($stags{1106} && $res{$stags{1106}})) {
$res{$stags{1044}} = [ '(none)' ]; # like rpm does...
}
if ($need_filenames) {
if ($res{'OLDFILENAMES'}) {
$res{'FILENAMES'} = [ @{$res{'OLDFILENAMES'}} ];
} else {
my $i = 0;
$res{'FILENAMES'} = [ map {"$res{'DIRNAMES'}->[$res{'DIRINDEXES'}->[$i++]]$_"} @{$res{'BASENAMES'}} ];
}
}
return %res;
}
sub add_flagsvers {
my ($res, $name, $flags, $vers) = @_;
return unless $res && $res->{$name};
my @flags = @{$res->{$flags} || []};
my @vers = @{$res->{$vers} || []};
for (@{$res->{$name}}) {
if (@flags && ($flags[0] & 0xe) && @vers) {
$_ .= ' ';
$_ .= '<' if $flags[0] & 2;
$_ .= '>' if $flags[0] & 4;
$_ .= '=' if $flags[0] & 8;
$_ .= " $vers[0]";
}
shift @flags;
shift @vers;
}
}
sub filteroldweak {
my ($res, $name, $flags, $data, $strong, $weak) = @_;
return unless $res && $res->{$name};
my @flags = @{$res->{$flags} || []};
my @strong;
my @weak;
for (@{$res->{$name}}) {
if (@flags && ($flags[0] & 0x8000000)) {
push @strong, $_;
} else {
push @weak, $_;
}
shift @flags;
}
$data->{$strong} = \@strong if @strong;
$data->{$weak} = \@weak if @weak;
}
sub verscmp_part {
my ($s1, $s2) = @_;
if (!defined($s1)) {
return defined($s2) ? -1 : 0;
}
return 1 if !defined $s2;
return 0 if $s1 eq $s2;
while (1) {
$s1 =~ s/^[^a-zA-Z0-9~]+//;
$s2 =~ s/^[^a-zA-Z0-9~]+//;
if ($s1 =~ s/^~//) {
next if $s2 =~ s/^~//;
return -1;
}
return 1 if $s2 =~ /^~/;
if ($s1 eq '') {
return $s2 eq '' ? 0 : -1;
}
return 1 if $s2 eq '';
my ($x1, $x2, $r);
if ($s1 =~ /^([0-9]+)(.*?)$/) {
$x1 = $1;
$s1 = $2;
$s2 =~ /^([0-9]*)(.*?)$/;
$x2 = $1;
$s2 = $2;
return 1 if $x2 eq '';
$x1 =~ s/^0+//;
$x2 =~ s/^0+//;
$r = length($x1) - length($x2) || $x1 cmp $x2;
} elsif ($s1 ne '' && $s2 ne '') {
$s1 =~ /^([a-zA-Z]*)(.*?)$/;
$x1 = $1;
$s1 = $2;
$s2 =~ /^([a-zA-Z]*)(.*?)$/;
$x2 = $1;
$s2 = $2;
return -1 if $x1 eq '' || $x2 eq '';
$r = $x1 cmp $x2;
}
return $r > 0 ? 1 : -1 if $r;
}
}
sub verscmp {
my ($s1, $s2, $dtest) = @_;
return 0 if $s1 eq $s2;
my ($e1, $v1, $r1) = $s1 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s;
$e1 = 0 unless defined $e1;
my ($e2, $v2, $r2) = $s2 =~ /^(?:(\d+):)?(.*?)(?:-([^-]*))?$/s;
$e2 = 0 unless defined $e2;
if ($e1 ne $e2) {
my $r = verscmp_part($e1, $e2);
return $r if $r;
}
return 0 if $dtest && ($v1 eq '' || $v2 eq '');
if ($v1 ne $v2) {
my $r = verscmp_part($v1, $v2);
return $r if $r;
}
$r1 = '' unless defined $r1;
$r2 = '' unless defined $r2;
return 0 if $dtest && ($r1 eq '' || $r2 eq '');
if ($r1 ne $r2) {
return verscmp_part($r1, $r2);
}
return 0;
}
sub query {
my ($handle, %opts) = @_;
my @tags = qw{NAME SOURCERPM NOSOURCE NOPATCH SIGTAG_MD5 PROVIDENAME PROVIDEFLAGS PROVIDEVERSION REQUIRENAME REQUIREFLAGS REQUIREVERSION SOURCEPACKAGE};
push @tags, qw{EPOCH VERSION RELEASE ARCH};
push @tags, qw{FILENAMES} if $opts{'filelist'};
push @tags, qw{SUMMARY DESCRIPTION} if $opts{'description'};
push @tags, qw{DISTURL} if $opts{'disturl'};
push @tags, qw{BUILDTIME} if $opts{'buildtime'};
push @tags, qw{CONFLICTNAME CONFLICTVERSION CONFLICTFLAGS OBSOLETENAME OBSOLETEVERSION OBSOLETEFLAGS} if $opts{'conflicts'};
push @tags, qw{RECOMMENDNAME RECOMMENDVERSION RECOMMENDFLAGS SUGGESTNAME SUGGESTVERSION SUGGESTFLAGS SUPPLEMENTNAME SUPPLEMENTVERSION SUPPLEMENTFLAGS ENHANCENAME ENHANCEVERSION ENHANCEFLAGS OLDSUGGESTSNAME OLDSUGGESTSVERSION OLDSUGGESTSFLAGS OLDENHANCESNAME OLDENHANCESVERSION OLDENHANCESFLAGS} if $opts{'weakdeps'};
my %res = rpmq($handle, @tags);
return undef unless %res;
my $src = $res{'SOURCERPM'}->[0];
$src = '' unless defined $src;
$src =~ s/-[^-]*-[^-]*\.[^\.]*\.rpm//;
add_flagsvers(\%res, 'PROVIDENAME', 'PROVIDEFLAGS', 'PROVIDEVERSION');
add_flagsvers(\%res, 'REQUIRENAME', 'REQUIREFLAGS', 'REQUIREVERSION');
my $data = {
name => $res{'NAME'}->[0],
hdrmd5 => unpack('H32', $res{'SIGTAG_MD5'}->[0]),
};
if ($opts{'alldeps'}) {
$data->{'provides'} = [ @{$res{'PROVIDENAME'} || []} ];
$data->{'requires'} = [ @{$res{'REQUIRENAME'} || []} ];
} else {
$data->{'provides'} = [ grep {!/^rpmlib\(/ && !/^\//} @{$res{'PROVIDENAME'} || []} ];
$data->{'requires'} = [ grep {!/^rpmlib\(/ && !/^\//} @{$res{'REQUIRENAME'} || []} ];
}
if ($opts{'conflicts'}) {
add_flagsvers(\%res, 'CONFLICTNAME', 'CONFLICTFLAGS', 'CONFLICTVERSION');
add_flagsvers(\%res, 'OBSOLETENAME', 'OBSOLETEFLAGS', 'OBSOLETEVERSION');
$data->{'conflicts'} = [ @{$res{'CONFLICTNAME'}} ] if $res{'CONFLICTNAME'};
$data->{'obsoletes'} = [ @{$res{'OBSOLETENAME'}} ] if $res{'OBSOLETENAME'};
}
if ($opts{'weakdeps'}) {
for (qw{RECOMMEND SUGGEST SUPPLEMENT ENHANCE}) {
next unless $res{"${_}NAME"};
add_flagsvers(\%res, "${_}NAME", "${_}FLAGS", "${_}VERSION");
$data->{lc($_)."s"} = [ @{$res{"${_}NAME"}} ];
}
if ($res{'OLDSUGGESTSNAME'}) {
add_flagsvers(\%res, 'OLDSUGGESTSNAME', 'OLDSUGGESTSFLAGS', 'OLDSUGGESTSVERSION');
filteroldweak(\%res, 'OLDSUGGESTSNAME', 'OLDSUGGESTSFLAGS', $data, 'recommends', 'suggests');
}
if ($res{'OLDENHANCESNAME'}) {
add_flagsvers(\%res, 'OLDENHANCESNAME', 'OLDENHANCESFLAGS', 'OLDENHANCESVERSION');
filteroldweak(\%res, 'OLDENHANCESNAME', 'OLDENHANCESFLAGS', $data, 'supplements', 'enhances');
}
}
# rpm3 compatibility: retrofit missing self provides
if ($src ne '') {
my $haveselfprovides;
if (@{$data->{'provides'}}) {
if ($data->{'provides'}->[-1] =~ /^\Q$res{'NAME'}->[0]\E =/) {
$haveselfprovides = 1;
} elsif (@{$data->{'provides'}} > 1 && $data->{'provides'}->[-2] =~ /^\Q$res{'NAME'}->[0]\E =/) {
$haveselfprovides = 1;
}
}
if (!$haveselfprovides) {
my $evr = "$res{'VERSION'}->[0]-$res{'RELEASE'}->[0]";
$evr = "$res{'EPOCH'}->[0]:$evr" if $res{'EPOCH'} && $res{'EPOCH'}->[0];
push @{$data->{'provides'}}, "$res{'NAME'}->[0] = $evr";
}
}
$data->{'source'} = $src eq '(none)' ? $data->{'name'} : $src if $src ne '';
if ($opts{'evra'}) {
my $arch = $res{'ARCH'}->[0];
$arch = $res{'NOSOURCE'} || $res{'NOPATCH'} ? 'nosrc' : 'src' unless $src ne '';
$data->{'version'} = $res{'VERSION'}->[0];
$data->{'release'} = $res{'RELEASE'}->[0];
$data->{'arch'} = $arch;
$data->{'epoch'} = $res{'EPOCH'}->[0] if exists $res{'EPOCH'};
}
if ($opts{'filelist'}) {
$data->{'filelist'} = $res{'FILENAMES'};
}
if ($opts{'description'}) {
$data->{'summary'} = $res{'SUMMARY'}->[0];
$data->{'description'} = $res{'DESCRIPTION'}->[0];
}
$data->{'buildtime'} = $res{'BUILDTIME'}->[0] if $opts{'buildtime'};
$data->{'disturl'} = $res{'DISTURL'}->[0] if $opts{'disturl'} && $res{'DISTURL'};
return $data;
}
sub queryhdrmd5 {
my ($bin, $leadsigp) = @_;
local *F;
open(F, '<', $bin) || die("$bin: $!\n");
my $buf = '';
my $l;
while (length($buf) < 96 + 16) {
$l = sysread(F, $buf, 4096, length($buf));
if (!$l) {
warn("$bin: read error\n");
close(F);
return undef;
}
}
my ($magic, $sigtype) = unpack('N@78n', $buf);
if ($magic != 0xedabeedb || $sigtype != 5) {
warn("$bin: not a rpm (bad magic of header type)\n");
close(F);
return undef;
}
my ($headmagic, $cnt, $cntdata) = unpack('@96N@104NN', $buf);
if ($headmagic != 0x8eade801) {
warn("$bin: not a rpm (bad sig header magic)\n");
close(F);
return undef;
}
my $hlen = 96 + 16 + $cnt * 16 + $cntdata;
$hlen = ($hlen + 7) & ~7;
while (length($buf) < $hlen) {
$l = sysread(F, $buf, 4096, length($buf));
if (!$l) {
warn("$bin: read error\n");
close(F);
return undef;
}
}
close F;
$$leadsigp = Digest::MD5::md5_hex(substr($buf, 0, $hlen)) if $leadsigp;
my $idxarea = substr($buf, 96 + 16, $cnt * 16);
if ($idxarea !~ /\A(?:.{16})*\000\000\003\354\000\000\000\007(....)\000\000\000\020/s) {
warn("$bin: no md5 signature header\n");
return undef;
}
my $md5off = unpack('N', $1);
if ($md5off >= $cntdata) {
warn("$bin: bad md5 offset\n");
return undef;
}
$md5off += 96 + 16 + $cnt * 16;
return unpack("\@${md5off}H32", $buf);
}
sub queryinstalled {
my ($root, %opts) = @_;
$root = '' if !defined($root) || $root eq '/';
local *F;
my $dochroot = $root ne '' && !$opts{'nochroot'} && !$< && (-x "$root/usr/bin/rpm" || -x "$root/bin/rpm") ? 1 : 0;
my $pid = open(F, '-|');
die("fork: $!\n") unless defined $pid;
if (!$pid) {
if ($dochroot && chroot($root)) {
chdir('/') || die("chdir: $!\n");
$root = '';
}
my @args;
unshift @args, '--nodigest', '--nosignature' if -e "$root/usr/bin/rpmquery ";
unshift @args, '--dbpath', "$root/var/lib/rpm" if $root ne '';
push @args, '--qf', '%{NAME}/%{ARCH}/%|EPOCH?{%{EPOCH}}:{0}|/%{VERSION}/%{RELEASE}/%{BUILDTIME}\n';
if (-x "$root/usr/bin/rpm") {
exec("$root/usr/bin/rpm", '-qa', @args);
die("$root/usr/bin/rpm: $!\n");
}
if (-x "$root/bin/rpm") {
exec("$root/bin/rpm", '-qa', @args);
die("$root/bin/rpm: $!\n");
}
die("rpm: command not found\n");
}
my @pkgs;
while (<F>) {
chomp;
my @s = split('/', $_);
next unless @s >= 5;
my $q = {'name' => $s[0], 'arch' => $s[1], 'version' => $s[3], 'release' => $s[4]};
$q->{'epoch'} = $s[2] if $s[2];
$q->{'buildtime'} = $s[5] if $s[5];
push @pkgs, $q;
}
if (!close(F)) {
return queryinstalled($root, %opts, 'nochroot' => 1) if !@pkgs && $dochroot;
die("rpm: exit status $?\n");
}
return \@pkgs;
}
# return (lead, sighdr, hdr [, hdrmd5]) of a rpm
sub getrpmheaders {
my ($path, $withhdrmd5) = @_;
my $hdrmd5;
local *F;
open(F, '<', $path) || die("$path: $!\n");
my $buf = '';
my $l;
while (length($buf) < 96 + 16) {
$l = sysread(F, $buf, 4096, length($buf));
die("$path: read error\n") unless $l;
}
die("$path: not a rpm\n") unless unpack('N', $buf) == 0xedabeedb && unpack('@78n', $buf) == 5;
my ($headmagic, $cnt, $cntdata) = unpack('@96N@104NN', $buf);
die("$path: not a rpm (bad sig header)\n") unless $headmagic == 0x8eade801 && $cnt < 16384 && $cntdata < 1048576;
my $hlen = 96 + 16 + $cnt * 16 + $cntdata;
$hlen = ($hlen + 7) & ~7;
while (length($buf) < $hlen + 16) {
$l = sysread(F, $buf, 4096, length($buf));
die("$path: read error\n") unless $l;
}
if ($withhdrmd5) {
my $idxarea = substr($buf, 96 + 16, $cnt * 16);
die("$path: no md5 signature header\n") unless $idxarea =~ /\A(?:.{16})*\000\000\003\354\000\000\000\007(....)\000\000\000\020/s;
my $md5off = unpack('N', $1);
die("$path: bad md5 offset\n") unless $md5off;
$md5off += 96 + 16 + $cnt * 16;
$hdrmd5 = unpack("\@${md5off}H32", $buf);
}
($headmagic, $cnt, $cntdata) = unpack('N@8NN', substr($buf, $hlen));
die("$path: not a rpm (bad header)\n") unless $headmagic == 0x8eade801 && $cnt < 1048576 && $cntdata < 33554432;
my $hlen2 = $hlen + 16 + $cnt * 16 + $cntdata;
while (length($buf) < $hlen2) {
$l = sysread(F, $buf, 4096, length($buf));
die("$path: read error\n") unless $l;
}
close F;
return (substr($buf, 0, 96), substr($buf, 96, $hlen - 96), substr($buf, $hlen, $hlen2 - $hlen), $hdrmd5);
}
sub getnevr_rich {
my ($d) = @_;
my $n = '';
my $bl = 0;
while ($d =~ /^([^ ,\(\)]*)/) {
$n .= $1;
$d = substr($d, length($1));
last unless $d =~ /^([\(\)])/;
$bl += $1 eq '(' ? 1 : -1;
last if $bl < 0;
$n .= $1;
$d = substr($d, 1);
}
return $n;
}
my %richops = (
'and' => 1,
'or' => 2,
'if' => 3,
'unless' => 4,
'else' => 5,
'with' => 6,
'without' => 7,
);
sub parse_rich_rec {
my ($dep, $chainop) = @_;
my $d = $dep;
$chainop ||= 0;
return ($d, undef) unless $d =~ s/^\(\s*//;
my ($r, $r2);
if ($d =~ /^\(/) {
($d, $r) = parse_rich_rec($d);
return ($d, undef) unless $r;
} else {
return ($d, undef) if $d =~ /^\)/;
my $n = getnevr_rich($d);
$d = substr($d, length($n));
$d =~ s/^ +//;
if ($d =~ /^([<=>]+)/) {
$n .= " $1 ";
$d =~ s/^[<=>]+ +//;
my $evr = getnevr_rich($d);
$d = substr($d, length($evr));
$n .= $evr;
}
$r = [0, $n];
}
$d =~ s/^\s+//;
return ($d, undef) unless $d ne '';
return ($d, $r) if $d =~ s/^\)//;
return ($d, undef) unless $d =~ s/([a-z]+)\s+//;
my $op = $richops {$1};
return ($d, undef) unless $op;
return ($d, undef) if $op == 5 && $chainop != 3 && $chainop != 4;
$chainop = 0 if $op == 5;
return ($d, undef) if $chainop && (($chainop != 1 && $chainop != 2 && $chainop != 6) || $op != $chainop);
($d, $r2) = parse_rich_rec("($d", $op);
return ($d, undef) unless $r2;
if (($op == 3 || $op == 4) && $r2->[0] == 5) {
$r = [$op, $r, $r2->[1], $r2->[2]];
} else {
$r = [$op, $r, $r2];
}
return ($d, $r);
}
sub parse_rich_dep {
my ($dep) = @_;
my ($d, $r) = parse_rich_rec($dep);
return undef if !$r || $d ne '';
return $r;
}
my @testcaseops = ('', '&', '|', '<IF>', '<UNLESS>', '<ELSE>', '+', '-');
sub testcaseformat_rec {
my ($r, $addparens) = @_;
my $op = $r->[0];
return $r->[1] unless $op;
my $top = $testcaseops[$op];
my $r1 = testcaseformat_rec($r->[1], 1);
if (($op == 3 || $op == 4) && @$r == 4) {
$r1 = "$r1 $top " . testcaseformat_rec($r->[2], 1);
$top = '<ELSE>';
}
my $addparens2 = 1;
$addparens2 = 0 if $r->[2]->[0] == $op && ($op == 1 || $op == 2 || $op == 6);
my $r2 = testcaseformat_rec($r->[-1], $addparens2);
return $addparens ? "($r1 $top $r2)" : "$r1 $top $r2";
}
sub testcaseformat {
my ($dep) = @_;
my $r = parse_rich_dep($dep);
return $dep unless $r;
return testcaseformat_rec($r);
}
sub shiftrich {
my ($s) = @_;
# FIXME: do this right!
my $dep = shift @$s;
while (@$s && ($dep =~ y/\(/\(/) > ($dep =~ y/\)/\)/)) {
$dep .= ' ' . shift(@$s);
}
return $dep;
}
1;