Thu, 04 Oct 2012 20:30:05 +0200
Correct out of date build configuration, porting to Solaris 11 network
link infrastructure and new libpcap logic. This additionally allows for
device drivers in subdirectories of /dev. Correct packaged nmap
personalities and signatures to work out of the box. Finally, hack
arpd logic to properly close sockets and quit on TERM by repeating
signaling in the run command script. Sadly, all this fails to correct
the run time behaviour of honeyd which fails to bind to the IP layer.
michael@428 | 1 | ## |
michael@428 | 2 | ## index.pl -- OpenPKG Maintenance Tool (backend for indexing) |
michael@428 | 3 | ## Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/> |
michael@428 | 4 | ## |
michael@428 | 5 | ## This software is property of the OpenPKG GmbH, DE MUC HRB 160208. |
michael@428 | 6 | ## All rights reserved. Licenses which grant limited permission to use, |
michael@428 | 7 | ## copy, modify and distribute this software are available from the |
michael@428 | 8 | ## OpenPKG GmbH. |
michael@428 | 9 | ## |
michael@428 | 10 | ## THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED |
michael@428 | 11 | ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
michael@428 | 12 | ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
michael@428 | 13 | ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR |
michael@428 | 14 | ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
michael@428 | 15 | ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
michael@428 | 16 | ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
michael@428 | 17 | ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
michael@428 | 18 | ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
michael@428 | 19 | ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
michael@428 | 20 | ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
michael@428 | 21 | ## SUCH DAMAGE. |
michael@428 | 22 | ## |
michael@428 | 23 | |
michael@428 | 24 | ############################################################################# |
michael@428 | 25 | ## |
michael@428 | 26 | ## MAIN PROCEDURE |
michael@428 | 27 | ## |
michael@428 | 28 | ############################################################################# |
michael@428 | 29 | |
michael@428 | 30 | require 5; |
michael@428 | 31 | #use strict; |
michael@428 | 32 | |
michael@428 | 33 | # determine OpenPKG instance prefix via |
michael@428 | 34 | # 1. the environment of the "openpkg build" framework |
michael@428 | 35 | # 2. the installation path of the script |
michael@428 | 36 | # 3. the installation path of the Perl interpreter |
michael@428 | 37 | # 4. the path of the "openpkg" command in $PATH |
michael@428 | 38 | my $l_prefix = $ENV{'OPENPKG_PREFIX'}; |
michael@428 | 39 | if (not $l_prefix) { |
michael@428 | 40 | ($l_prefix) = ($0 =~ m/^(.+)\/lib(exec)?\/openpkg(-tools)?\/build(\.pl)?$/); |
michael@428 | 41 | } |
michael@428 | 42 | if (not $l_prefix) { |
michael@428 | 43 | ($l_prefix) = ($^X =~ m/^(.+)\/bin\/perl.*$/); |
michael@428 | 44 | } |
michael@428 | 45 | if (not $l_prefix) { |
michael@428 | 46 | $l_prefix = (`(which openpkg) 2>/dev/null` =~ m/^(.+)\/bin\/openpkg$/); |
michael@428 | 47 | } |
michael@428 | 48 | if (not -x "$l_prefix/bin/openpkg") { |
michael@428 | 49 | die "openpkg:index:FATAL: cannot determine OpenPKG instance prefix"; |
michael@428 | 50 | } |
michael@428 | 51 | |
michael@428 | 52 | # determine tools |
michael@428 | 53 | my $RPM = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm") ? |
michael@428 | 54 | "$l_prefix/bin/openpkg rpm" : "$l_prefix/bin/rpm"); |
michael@428 | 55 | my $R2C = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm2cpio") ? |
michael@428 | 56 | "$l_prefix/bin/openpkg rpm2cpio" : "$l_prefix/bin/rpm2cpio"); |
michael@428 | 57 | my $BZ = "$l_prefix/lib/openpkg/bzip2 -9"; |
michael@428 | 58 | my $OPENSSL = "$l_prefix/lib/openpkg/openssl"; |
michael@428 | 59 | |
michael@428 | 60 | # parse command line |
michael@428 | 61 | our ($opt_r, $opt_p, $opt_S, $opt_T, $opt_D, $opt_C, $opt_o, $opt_c, $opt_i); |
michael@428 | 62 | my $getopts = 'r:p:STDC:o:ci'; |
michael@428 | 63 | getopts($getopts); |
michael@428 | 64 | if ($#ARGV < 0) { |
michael@428 | 65 | print "openpkg:index:USAGE: openpkg index [-r resource] [-p platform] [-S] [-T] [-D] [-C cache.db] [-o index.rdf] [-c] [-i] dir ...\n"; |
michael@428 | 66 | exit(1); |
michael@428 | 67 | } |
michael@428 | 68 | |
michael@428 | 69 | # optionally open cache file |
michael@428 | 70 | my %cache; |
michael@428 | 71 | if ($opt_C) { |
michael@428 | 72 | eval { |
michael@428 | 73 | require DB_File; |
michael@428 | 74 | }; |
michael@428 | 75 | if ($@) { |
michael@428 | 76 | die "openpkg:index:FATAL: The -C option requires an installed DB_File perl module."; |
michael@428 | 77 | } |
michael@428 | 78 | eval { |
michael@428 | 79 | require Fcntl; |
michael@428 | 80 | }; |
michael@428 | 81 | if ($@) { |
michael@428 | 82 | die "openpkg:index:FATAL: The -C option requires an installed Fcntl perl module."; |
michael@428 | 83 | } |
michael@428 | 84 | tie %cache, 'DB_File', $opt_C, Fcntl::O_CREAT()|Fcntl::O_RDWR(), 0666, $DB_File::DB_HASH |
michael@428 | 85 | or die "openpkg:index:FATAL: cannot tie cache '$opt_C' ($!)"; |
michael@428 | 86 | } |
michael@428 | 87 | |
michael@428 | 88 | # provide default for repository path |
michael@428 | 89 | $opt_r = 'OpenPKG-CURRENT/Source/' if (not defined($opt_r)); |
michael@428 | 90 | |
michael@428 | 91 | # create make_resource function closure |
michael@428 | 92 | my $make_resource = gen_make_resource(); |
michael@428 | 93 | |
michael@428 | 94 | # determine output channel |
michael@428 | 95 | my $fh; |
michael@428 | 96 | my $tmpo; |
michael@428 | 97 | if (defined($opt_o)) { |
michael@428 | 98 | $tmpo = $opt_o . '.tmp'; |
michael@428 | 99 | if ($opt_c) { |
michael@428 | 100 | open(FH, "| $BZ -c > '$tmpo'") |
michael@428 | 101 | or die "openpkg:index:FATAL: cannot write '$tmpo' ($!)"; |
michael@428 | 102 | } else { |
michael@428 | 103 | open(FH, "> $tmpo") |
michael@428 | 104 | or die "openpkg:index:FATAL: cannot write '$tmpo' ($!)"; |
michael@428 | 105 | } |
michael@428 | 106 | } else { |
michael@428 | 107 | if ($opt_c) { |
michael@428 | 108 | open(FH, "| $BZ -c") |
michael@428 | 109 | or die "openpkg:index:FATAL: cannot write to stdout ($!)"; |
michael@428 | 110 | } else { |
michael@428 | 111 | open(FH, ">&=1") |
michael@428 | 112 | or die "openpkg:index:FATAL: cannot write to stdout ($!)"; |
michael@428 | 113 | } |
michael@428 | 114 | } |
michael@428 | 115 | $fh = \*FH; |
michael@428 | 116 | |
michael@428 | 117 | # generate XML/RDF output |
michael@428 | 118 | xml_head($fh, $opt_r); |
michael@428 | 119 | foreach my $prefix (@ARGV) { |
michael@428 | 120 | my $list; |
michael@428 | 121 | if (-d $prefix) { |
michael@428 | 122 | if ($opt_i) { |
michael@428 | 123 | $list = list_rpmdir($prefix); |
michael@428 | 124 | } else { |
michael@428 | 125 | $list = list_specdir($prefix); |
michael@428 | 126 | } |
michael@428 | 127 | } else { |
michael@428 | 128 | $list = [ $prefix ]; |
michael@428 | 129 | $prefix = dirname($prefix); |
michael@428 | 130 | } |
michael@428 | 131 | write_index($fh, $prefix, $opt_r, $opt_p, $list, $opt_C ? \%cache : undef); |
michael@428 | 132 | } |
michael@428 | 133 | xml_foot($fh); |
michael@428 | 134 | |
michael@428 | 135 | # close output channel |
michael@428 | 136 | close($fh) |
michael@428 | 137 | or die "openpkg:index:FATAL: write error on output ($!)"; |
michael@428 | 138 | |
michael@428 | 139 | # post-process output |
michael@428 | 140 | if (defined($tmpo)) { |
michael@428 | 141 | rename($tmpo, $opt_o) |
michael@428 | 142 | or die "openpkg:index:FATAL: cannot rename $tmpo to $opt_o ($!)"; |
michael@428 | 143 | } |
michael@428 | 144 | |
michael@428 | 145 | # die gracefully |
michael@428 | 146 | exit(0); |
michael@428 | 147 | |
michael@428 | 148 | ############################################################################# |
michael@428 | 149 | ## |
michael@428 | 150 | ## FUNCTIONS: PARSING |
michael@428 | 151 | ## |
michael@428 | 152 | ############################################################################# |
michael@428 | 153 | |
michael@428 | 154 | # home-brewn getopt(3) style option parser |
michael@428 | 155 | sub getopts ($) { |
michael@428 | 156 | my ($opts) = @_; |
michael@428 | 157 | my (%optf) = map { m/(\w)/; $1 => $_ } $opts =~ m/(\w:|\w)/g; |
michael@428 | 158 | my (%opts, @argv, $optarg); |
michael@428 | 159 | |
michael@428 | 160 | foreach (@ARGV) { |
michael@428 | 161 | if (@argv) { |
michael@428 | 162 | push @argv, $_; |
michael@428 | 163 | } elsif (defined $optarg) { |
michael@428 | 164 | if (exists $opts{$optarg}) { |
michael@428 | 165 | $opts{$optarg} .= " $_"; |
michael@428 | 166 | } else { |
michael@428 | 167 | $opts{$optarg} = $_; |
michael@428 | 168 | } |
michael@428 | 169 | $optarg = undef; |
michael@428 | 170 | } elsif (!/^[-]/) { |
michael@428 | 171 | push @argv, $_; |
michael@428 | 172 | } else { |
michael@428 | 173 | while (/^\-(\w)(.*)/) { |
michael@428 | 174 | if (exists $optf{$1}) { |
michael@428 | 175 | if (length($optf{$1}) > 1) { |
michael@428 | 176 | if ($2 ne '') { |
michael@428 | 177 | if (exists $opts{$1}) { |
michael@428 | 178 | $opts{$1} .= " $2"; |
michael@428 | 179 | } else { |
michael@428 | 180 | $opts{$1} = $2; |
michael@428 | 181 | } |
michael@428 | 182 | } else { |
michael@428 | 183 | $optarg = $1; |
michael@428 | 184 | } |
michael@428 | 185 | last; |
michael@428 | 186 | } else { |
michael@428 | 187 | $opts{$1} = 1; |
michael@428 | 188 | } |
michael@428 | 189 | } else { |
michael@428 | 190 | warn "openpkg:index:WARNING: unknown option $_\n"; |
michael@428 | 191 | } |
michael@428 | 192 | $_ = "-$2"; |
michael@428 | 193 | } |
michael@428 | 194 | } |
michael@428 | 195 | } |
michael@428 | 196 | if (defined $optarg) { |
michael@428 | 197 | warn "openpkg:index:WARNING: option $optarg requires an argument\n"; |
michael@428 | 198 | } |
michael@428 | 199 | foreach (keys %opts) { |
michael@428 | 200 | eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";'; |
michael@428 | 201 | } |
michael@428 | 202 | @ARGV = @argv; |
michael@428 | 203 | } |
michael@428 | 204 | |
michael@428 | 205 | ############################################################################# |
michael@428 | 206 | ## |
michael@428 | 207 | ## OpenPKG RPM Package Specification Parsing |
michael@428 | 208 | ## |
michael@428 | 209 | ############################################################################# |
michael@428 | 210 | |
michael@428 | 211 | # escape XML for output in RDF file |
michael@428 | 212 | sub e ($) { |
michael@428 | 213 | my ($s) = @_; |
michael@428 | 214 | my ($i); |
michael@428 | 215 | |
michael@428 | 216 | # remove trailing whitespace |
michael@428 | 217 | $s =~ s/\n+$//sg; |
michael@428 | 218 | $s =~ s/[^\S\n]+$//mg; |
michael@428 | 219 | |
michael@428 | 220 | # remove common leading whitespace |
michael@428 | 221 | $i = undef; |
michael@428 | 222 | while ($s =~ m/^([^\S\n]+)/mg) { |
michael@428 | 223 | $i = $1 if (!defined($i) || (length($1) < length($i))); |
michael@428 | 224 | } |
michael@428 | 225 | $s =~ s/^\Q$i\E//mg if (defined($i)); |
michael@428 | 226 | |
michael@428 | 227 | # escape XML special characters |
michael@428 | 228 | $s =~ s/&/&/sg; |
michael@428 | 229 | $s =~ s/</</sg; |
michael@428 | 230 | $s =~ s/>/>/sg; |
michael@428 | 231 | |
michael@428 | 232 | return $s; |
michael@428 | 233 | } |
michael@428 | 234 | |
michael@428 | 235 | # make_resource closure |
michael@428 | 236 | sub gen_make_resource { |
michael@428 | 237 | # generate operator regular expression |
michael@428 | 238 | # (used in make_resource below) |
michael@428 | 239 | my %attrname = ( |
michael@428 | 240 | '==' => 'equ', |
michael@428 | 241 | '=' => 'equ', |
michael@428 | 242 | '>=' => 'geq', |
michael@428 | 243 | '=>' => 'geq', |
michael@428 | 244 | '<=' => 'leq', |
michael@428 | 245 | '=<' => 'leq', |
michael@428 | 246 | '>' => 'gt', |
michael@428 | 247 | '<' => 'lt' |
michael@428 | 248 | ); |
michael@428 | 249 | my ($opreg) = |
michael@428 | 250 | join '|', |
michael@428 | 251 | map { |
michael@428 | 252 | "\Q$_\E" |
michael@428 | 253 | } sort { |
michael@428 | 254 | length($b) <=> length($a) |
michael@428 | 255 | || $b cmp $a |
michael@428 | 256 | } keys(%attrname); |
michael@428 | 257 | |
michael@428 | 258 | # return function which makes resource object |
michael@428 | 259 | return sub ($) { |
michael@428 | 260 | my ($s) = @_; |
michael@428 | 261 | |
michael@428 | 262 | if ($s =~ m/(\S+)\s*($opreg)\s*(.*?)\s*$/o) { |
michael@428 | 263 | return { |
michael@428 | 264 | resource => $1, |
michael@428 | 265 | attrname => $attrname{$2}, |
michael@428 | 266 | attrval => $3 |
michael@428 | 267 | } |
michael@428 | 268 | } |
michael@428 | 269 | else { |
michael@428 | 270 | return { |
michael@428 | 271 | resource => $s |
michael@428 | 272 | } |
michael@428 | 273 | } |
michael@428 | 274 | }; |
michael@428 | 275 | }; |
michael@428 | 276 | |
michael@428 | 277 | # split string into array at comma seperator |
michael@428 | 278 | # (optioanlly map resource operators into resource objects) |
michael@428 | 279 | sub commasep ($$) { |
michael@428 | 280 | my ($k, $v) = @_; |
michael@428 | 281 | |
michael@428 | 282 | if ($k =~ m/^(NoSource)$/) { |
michael@428 | 283 | return split(/\s*,\s*/, $v); |
michael@428 | 284 | } elsif ($k =~ /^(PreReq|BuildPreReq|Provides|Conflicts)$/) { |
michael@428 | 285 | return map { &$make_resource($_) } |
michael@428 | 286 | split(/\s*,\s*/, $v); |
michael@428 | 287 | } |
michael@428 | 288 | return $v; |
michael@428 | 289 | } |
michael@428 | 290 | |
michael@428 | 291 | # escape option string |
michael@428 | 292 | sub optesc ($) { |
michael@428 | 293 | my ($s) = @_; |
michael@428 | 294 | |
michael@428 | 295 | $s =~ s/([\x00-\x1f\x80-\xbf\s\%])/sprintf("%%%02x", ord($1))/eg; |
michael@428 | 296 | return $s; |
michael@428 | 297 | } |
michael@428 | 298 | |
michael@428 | 299 | # variable substitution |
michael@428 | 300 | sub vsub ($$) { |
michael@428 | 301 | my ($var, $v) = @_; |
michael@428 | 302 | |
michael@428 | 303 | $v =~ s/\%\{([^}]+)\}/ |
michael@428 | 304 | exists $var->{$1} ? $var->{$1} : '%{'.$1.'}'/emg; |
michael@428 | 305 | return $v; |
michael@428 | 306 | } |
michael@428 | 307 | |
michael@428 | 308 | # Umgekehrte Polnische Notation (UPN) |
michael@428 | 309 | # Reverse Polish Notation (RPN) |
michael@428 | 310 | # << ( %{foo} == "yes" ) && ( ! %{bar} == "no" ) || ( %{baz} == "yes" ) |
michael@428 | 311 | # >> %{foo} %{bar} ! && %{baz} || |
michael@428 | 312 | sub upn ($) { |
michael@428 | 313 | my ($t) = @_; |
michael@428 | 314 | my (@tok); |
michael@428 | 315 | my (@out, $op, $o); |
michael@428 | 316 | my (@save); |
michael@428 | 317 | |
michael@428 | 318 | # split string into tokens |
michael@428 | 319 | @tok = ($t =~ m/(\(|\)|\&\&|\|\||\!|\S+)/g); |
michael@428 | 320 | |
michael@428 | 321 | # iterate over all tokens |
michael@428 | 322 | $op = []; |
michael@428 | 323 | foreach (@tok) { |
michael@428 | 324 | if ($_ eq '(') { |
michael@428 | 325 | push(@save, $op); |
michael@428 | 326 | $op = []; |
michael@428 | 327 | } elsif ($_ eq ')') { |
michael@428 | 328 | die "openpkg:index:FATAL: unresolved operators in: @tok\n" if (@$op); |
michael@428 | 329 | $op = pop(@save) |
michael@428 | 330 | or die "openpkg:index:FATAL: unmatched closing parenthesis in: @tok\n"; |
michael@428 | 331 | while ($o = pop(@$op)) { |
michael@428 | 332 | push(@out, $o->[0]); |
michael@428 | 333 | last if ($o->[1]); |
michael@428 | 334 | } |
michael@428 | 335 | } elsif ($_ eq '&&') { |
michael@428 | 336 | push(@$op, [ '+', 1 ]); |
michael@428 | 337 | } elsif ($_ eq '||') { |
michael@428 | 338 | push(@$op, [ '|', 1 ]); |
michael@428 | 339 | } elsif ($_ eq '!') { |
michael@428 | 340 | push(@$op, [ '!', 0 ]); |
michael@428 | 341 | } elsif (m/^\%\{(\S*?)\}$/) { |
michael@428 | 342 | push(@out, $1); |
michael@428 | 343 | while ($o = pop(@$op)) { |
michael@428 | 344 | push(@out, $o->[0]); |
michael@428 | 345 | last if ($o->[1]); # binary operator |
michael@428 | 346 | } |
michael@428 | 347 | } |
michael@428 | 348 | } |
michael@428 | 349 | return join (' ', @out); |
michael@428 | 350 | } |
michael@428 | 351 | |
michael@428 | 352 | # deduce external variables from description |
michael@428 | 353 | # (backward compatibility for times before openpkg-20021230) |
michael@428 | 354 | sub find_options ($) { |
michael@428 | 355 | my ($descr) = @_; |
michael@428 | 356 | my $evar = {}; |
michael@428 | 357 | $descr =~ s/--define\s*'(\S+)\s*\%\{\1\}'/$evar->{$1} = '%{'.$1.'}', ''/sge; |
michael@428 | 358 | return $evar; |
michael@428 | 359 | } |
michael@428 | 360 | |
michael@428 | 361 | # translate default section from spec-file into a hash |
michael@428 | 362 | # - %if/%ifdef/%define... are translated to #/#ifdef/#define |
michael@428 | 363 | # - #defines are interpolated (correct ?) |
michael@428 | 364 | # - #if/#ifdef/... sections are stripped |
michael@428 | 365 | # result is the same as if all conditions evaluate false (!) |
michael@428 | 366 | # - all attributes are of the form key: value |
michael@428 | 367 | # - repeated attributes are coalesced into a list |
michael@428 | 368 | # hint: evar = expansion variables, ovar = option variables |
michael@428 | 369 | sub package2data ($$) { |
michael@428 | 370 | my ($s, $ovar) = @_; |
michael@428 | 371 | my (%evar, %var); |
michael@428 | 372 | my (@term, $term); |
michael@428 | 373 | my (%attr, %avar); |
michael@428 | 374 | my ($l, $v, $cond, $d, $p); |
michael@428 | 375 | my ($re, @defs); |
michael@428 | 376 | |
michael@428 | 377 | # combine multilines (line continuation!) |
michael@428 | 378 | $s =~ s/\\\n/ /sg; |
michael@428 | 379 | |
michael@428 | 380 | # map syntax of conditional variable macros |
michael@428 | 381 | $s =~ s/^#\{\!\?([^:]*):\s*%(.*?)\s*\}\s*$/#ifndef $1\n#$2\n#endif/mg; |
michael@428 | 382 | $s =~ s/^#\{\!\?([^:]*):\s*(.*?)\s*\}\s*$/#ifndef $1\n$2\n#endif/mg; |
michael@428 | 383 | |
michael@428 | 384 | # map syntax of option macro |
michael@428 | 385 | $s =~ s/^#option\s+(\S+)\s*(.*?)\s*$/#ifndef $1\n#define $1 $2\n#endif\n#provides $1 $2/mg; |
michael@428 | 386 | |
michael@428 | 387 | # use option variables for expansion |
michael@428 | 388 | %evar = %$ovar; |
michael@428 | 389 | |
michael@428 | 390 | # guess more external parameters by scanning for (and removing) "set option default" sections |
michael@428 | 391 | $re = '^\#ifndef\s+[\w\_]+\s*\n((?:\#define\s+[\w\_]+\s.*\n)+)\#endif\n'; |
michael@428 | 392 | @defs = ($s =~ m/$re/gm); |
michael@428 | 393 | foreach (@defs) { |
michael@428 | 394 | while (m/^\#define\s+([\w\_]+)\s(.*?)\s*$/mg) { |
michael@428 | 395 | $ovar->{$1} = $2; |
michael@428 | 396 | $evar{$1} = '%{'.$1.'}'; |
michael@428 | 397 | } |
michael@428 | 398 | } |
michael@428 | 399 | $s =~ s/$re//gm; |
michael@428 | 400 | |
michael@428 | 401 | # add everything looking like a "with_xxx" variable |
michael@428 | 402 | $re = '%{(with\_[\w\_]+)}'; |
michael@428 | 403 | @defs = ($s =~ /$re/gm); |
michael@428 | 404 | foreach (@defs) { |
michael@428 | 405 | next if (exists($ovar->{$1})); |
michael@428 | 406 | $ovar->{$1} = '%{'.$1.'}'; # unexpanded |
michael@428 | 407 | $evar{$1} = '%{'.$1.'}'; |
michael@428 | 408 | } |
michael@428 | 409 | |
michael@428 | 410 | # extract all conditional sections (#if/#else/#endif) |
michael@428 | 411 | @term = (); |
michael@428 | 412 | %var = (); |
michael@428 | 413 | $cond = ''; |
michael@428 | 414 | foreach $l (split(/\n/, $s)) { |
michael@428 | 415 | # expand variables |
michael@428 | 416 | $v = vsub(\%avar, vsub(\%var, $l)); |
michael@428 | 417 | |
michael@428 | 418 | if (($p) = ($v =~ /^\#if\s+(.*?)\s*$/)) { |
michael@428 | 419 | # normalize "%{variable}" != ..." |
michael@428 | 420 | $p =~ s/("\%\{[^}]+\}")\s*!=\s*"(yes|no)"/$1 . " == \"" . ($2 ne "yes" ? "yes" : "no") . "\""/sge; |
michael@428 | 421 | |
michael@428 | 422 | # normalize #if expressions |
michael@428 | 423 | # - "%{variable}" == "yes" |
michael@428 | 424 | # - "%{variable}" == "no" |
michael@428 | 425 | # - operators ! && || |
michael@428 | 426 | # warn on: |
michael@428 | 427 | # - operator != |
michael@428 | 428 | # - any other word |
michael@428 | 429 | $term = ''; |
michael@428 | 430 | while ($p =~ m/(!=)|(\!|\|\||\&\&|\(|\))|"\%\{([^}]+)\}"\s*==\s*"(yes|no)"|(\S+)/g) { |
michael@428 | 431 | if (defined($1)) { |
michael@428 | 432 | warn "openpkg:index:WARNING: unknown token '$1':\n< $l\n> $v"; |
michael@428 | 433 | } elsif (defined($5)) { |
michael@428 | 434 | warn "openpkg:index:WARNING: unknown token '$5':\n< $l\n> $v"; |
michael@428 | 435 | } elsif (defined($2)) { |
michael@428 | 436 | $term .= " $2 "; |
michael@428 | 437 | } elsif (exists($evar{$3})) { |
michael@428 | 438 | $term .= ($4 eq 'no' ? '! ' : '').vsub(\%evar, '%{'.$3.'}'); |
michael@428 | 439 | } else { |
michael@428 | 440 | warn "openpkg:index:WARNING: unknown condition variable '$3':\n< $l\n> $v"; |
michael@428 | 441 | } |
michael@428 | 442 | } |
michael@428 | 443 | |
michael@428 | 444 | # join with previous conditions for this #if/#endif block |
michael@428 | 445 | if ($term ne '') { |
michael@428 | 446 | push(@term, "( $term )"); |
michael@428 | 447 | $cond = join(' && ', grep { $_ ne '' } @term) . ''; |
michael@428 | 448 | } else { |
michael@428 | 449 | push(@term, ''); |
michael@428 | 450 | } |
michael@428 | 451 | } |
michael@428 | 452 | elsif ($v =~ /^\#else\s*$/) { |
michael@428 | 453 | # reverse last condition |
michael@428 | 454 | if (@term) { |
michael@428 | 455 | $term[-1] = ' ! '. $term[-1]; |
michael@428 | 456 | $cond = join(' && ', grep { $_ ne '' } @term) . ''; |
michael@428 | 457 | } else { |
michael@428 | 458 | die "openpkg:index:FATAL: \"else\" without \"if\""; |
michael@428 | 459 | } |
michael@428 | 460 | } |
michael@428 | 461 | elsif ($v =~ /^\#endif\s*$/) { |
michael@428 | 462 | # unwind last #if expression |
michael@428 | 463 | pop(@term); |
michael@428 | 464 | $cond = join(' && ', grep { $_ ne '' } @term) . ''; |
michael@428 | 465 | } |
michael@428 | 466 | elsif ($v =~ /^\#(?:define)\s*(\S+)\s*(.*?)\s*$/) { |
michael@428 | 467 | # define conditional variables |
michael@428 | 468 | # - truth-value becomes current condition |
michael@428 | 469 | # define internal variables |
michael@428 | 470 | # - store for subsequent substitution |
michael@428 | 471 | if (exists($evar{$1})) { |
michael@428 | 472 | if ($2 eq 'yes') { |
michael@428 | 473 | if ($cond eq '') { |
michael@428 | 474 | $evar{$1} = "( \%\{$1\} )"; |
michael@428 | 475 | } else { |
michael@428 | 476 | $evar{$1} = "( \%\{$1\} || ( $cond ) )"; |
michael@428 | 477 | } |
michael@428 | 478 | } elsif ($2 eq 'no') { |
michael@428 | 479 | if ($cond eq '') { |
michael@428 | 480 | $evar{$1} = "( \%\{$1\} )"; |
michael@428 | 481 | } else { |
michael@428 | 482 | $evar{$1} = "( %\{$1\} && ! ( $cond ) )"; |
michael@428 | 483 | } |
michael@428 | 484 | } else { |
michael@428 | 485 | warn "openpkg:index:WARNING: logic too complex for '$1' (boolean expressions allowed only):\n< $l\n> $v"; |
michael@428 | 486 | } |
michael@428 | 487 | } else { |
michael@428 | 488 | $var{$1} = $2; |
michael@428 | 489 | } |
michael@428 | 490 | } |
michael@428 | 491 | elsif ($v =~ /^\#(?:undefine)\s*(\S+)\s*$/) { |
michael@428 | 492 | # undefine conditional variables |
michael@428 | 493 | # undefine internal variables |
michael@428 | 494 | if (exists($evar{$1})) { |
michael@428 | 495 | $evar{$1} = "\%\{$1\}"; |
michael@428 | 496 | } else { |
michael@428 | 497 | delete($var{$1}); |
michael@428 | 498 | } |
michael@428 | 499 | } |
michael@428 | 500 | elsif ($v =~ /^\#(?:provides)\s*(\S+)\s*(.*?)\s*$/) { |
michael@428 | 501 | # store option for current condition |
michael@428 | 502 | if (exists($attr{'Name'}->{''})) { |
michael@428 | 503 | push(@{$attr{'Provides'}->{$cond}}, { |
michael@428 | 504 | resource => $attr{'Name'}->{''}->[0] . '::' . $1, |
michael@428 | 505 | attrname => 'equ', |
michael@428 | 506 | attrval => optesc($2) |
michael@428 | 507 | }); |
michael@428 | 508 | } else { |
michael@428 | 509 | warn "openpkg:index:ERROR: no package name set for option \"$1 = $2\""; |
michael@428 | 510 | } |
michael@428 | 511 | } |
michael@428 | 512 | elsif ($v =~ /^\#NoSource\s*(.*?)\s*$/) { |
michael@428 | 513 | # store conditional NoSource attribute |
michael@428 | 514 | push(@{$attr{'NoSource'}->{$cond}}, commasep('NoSource', $1)); |
michael@428 | 515 | |
michael@428 | 516 | } |
michael@428 | 517 | elsif ($v =~ /^\s*([^\#]\S*)\s*:\s*(.*?)\s*$/) { |
michael@428 | 518 | # store "attribute: value" headers for current condition |
michael@428 | 519 | push(@{$attr{$1}->{$cond}}, commasep($1,$2)); |
michael@428 | 520 | $avar{lc($1)} = $2 if ($cond eq ''); |
michael@428 | 521 | } |
michael@428 | 522 | } |
michael@428 | 523 | |
michael@428 | 524 | # return all header "attributes" |
michael@428 | 525 | return \%attr; |
michael@428 | 526 | } |
michael@428 | 527 | |
michael@428 | 528 | # split spec file into sections starting with a %word |
michael@428 | 529 | # - concatenate extended lines |
michael@428 | 530 | # - strip comment lines |
michael@428 | 531 | # - map %command to #command |
michael@428 | 532 | # - split sections |
michael@428 | 533 | # - return package2data() from default section (before first %xxx section) |
michael@428 | 534 | sub spec2data ($) { |
michael@428 | 535 | my ($s) = @_; |
michael@428 | 536 | my (%map); |
michael@428 | 537 | my ($a, $o); |
michael@428 | 538 | my $spec = $s; |
michael@428 | 539 | |
michael@428 | 540 | # remove comments |
michael@428 | 541 | $s =~ s/^\s*#.*?\n//mg; |
michael@428 | 542 | |
michael@428 | 543 | # map commands |
michael@428 | 544 | $s =~ s/^%(ifdef|ifndef|if|NoSource|option|undefine|define|else|endif|\{)/#$1/mg; |
michael@428 | 545 | |
michael@428 | 546 | # split sections |
michael@428 | 547 | foreach (split(m/^(?=%\w+\s*\n)/m, $s)) { |
michael@428 | 548 | if (m/^%(\w+)\s*\n/) { |
michael@428 | 549 | $map{$1} .= $'; |
michael@428 | 550 | } else { |
michael@428 | 551 | $map{'*'} .= $_; |
michael@428 | 552 | } |
michael@428 | 553 | } |
michael@428 | 554 | |
michael@428 | 555 | # translate package information into "data" |
michael@428 | 556 | if (exists($map{'description'})) { |
michael@428 | 557 | # backward compatibility (options are in description) |
michael@428 | 558 | $o = find_options($map{'description'}); |
michael@428 | 559 | $a = package2data($map{'*'}, $o ); |
michael@428 | 560 | $a->{'Description'} = { '' => [ $map{'description'} ] }; |
michael@428 | 561 | } else { |
michael@428 | 562 | # standard case |
michael@428 | 563 | $a = package2data($map{'*'}, {}); |
michael@428 | 564 | } |
michael@428 | 565 | return $a; |
michael@428 | 566 | } |
michael@428 | 567 | |
michael@428 | 568 | ############################################################################# |
michael@428 | 569 | ## |
michael@428 | 570 | ## XML/RDF Generation |
michael@428 | 571 | ## |
michael@428 | 572 | ############################################################################# |
michael@428 | 573 | |
michael@428 | 574 | # start of XML file |
michael@428 | 575 | sub xml_head ($$) { |
michael@428 | 576 | my ($fh, $res) = @_; |
michael@428 | 577 | print $fh <<EOFEOF; |
michael@428 | 578 | <?xml version="1.0" encoding="iso-8859-1"?> |
michael@428 | 579 | <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
michael@428 | 580 | xmlns="http://www.openpkg.org/xml-rdf-index/0.9"> |
michael@428 | 581 | <Repository rdf:resource="$res"> |
michael@428 | 582 | EOFEOF |
michael@428 | 583 | } |
michael@428 | 584 | |
michael@428 | 585 | # end of XML file, corresponds with start tags |
michael@428 | 586 | sub xml_foot ($) { |
michael@428 | 587 | my($fh) = @_; |
michael@428 | 588 | print $fh <<EOFEOF; |
michael@428 | 589 | </Repository> |
michael@428 | 590 | </rdf:RDF> |
michael@428 | 591 | EOFEOF |
michael@428 | 592 | } |
michael@428 | 593 | |
michael@428 | 594 | # conditional expansion of attribute |
michael@428 | 595 | sub n ($$) { |
michael@428 | 596 | my ($a, $k) = @_; |
michael@428 | 597 | return if (not $a->{$k}); |
michael@428 | 598 | return if (not $a->{$k}->{''}); |
michael@428 | 599 | return ($a->{$k}->{''}->[0]); |
michael@428 | 600 | } |
michael@428 | 601 | |
michael@428 | 602 | # send out $a->{$k} as text-style tag |
michael@428 | 603 | sub xml_text ($$$;$) { |
michael@428 | 604 | my ($i, $a, $k, $tag) = @_; |
michael@428 | 605 | my ($out); |
michael@428 | 606 | return "" if (not exists($a->{$k})); |
michael@428 | 607 | $tag = $k if (not defined($tag)); |
michael@428 | 608 | $i = ' ' x $i; |
michael@428 | 609 | $out = e(n($a, $k)); |
michael@428 | 610 | return if $out eq ''; |
michael@428 | 611 | return "$i<$tag>\n$out\n$i</$tag>\n"; |
michael@428 | 612 | } |
michael@428 | 613 | |
michael@428 | 614 | # send out @{$a->{$k}} as body of an XML tag |
michael@428 | 615 | # $k is the name of the tag unless overridden by $tag |
michael@428 | 616 | # $i denotes the depth of indentation to form nicely |
michael@428 | 617 | # looking files. |
michael@428 | 618 | # all data from the list is flattened into a single |
michael@428 | 619 | # body, separated by LF and escaped for XML metachars. |
michael@428 | 620 | sub xml_tag ($$$;$) { |
michael@428 | 621 | my ($i, $a, $k, $tag) = @_; |
michael@428 | 622 | my ($out, $cond, $upn); |
michael@428 | 623 | return "" if (not exists($a->{$k})); |
michael@428 | 624 | $tag = $k if (not defined($tag)); |
michael@428 | 625 | $out = ''; |
michael@428 | 626 | $i = ' ' x $i; |
michael@428 | 627 | foreach $cond (sort keys(%{$a->{$k}})) { |
michael@428 | 628 | $upn = e(upn($cond)); |
michael@428 | 629 | $out .= $i . |
michael@428 | 630 | ($cond ne '' ? "<$tag cond=\"$upn\">" : "<$tag>") . |
michael@428 | 631 | join("\n", map { e($_) } @{$a->{$k}->{$cond}}) . |
michael@428 | 632 | "</$tag>\n"; |
michael@428 | 633 | } |
michael@428 | 634 | return $out; |
michael@428 | 635 | } |
michael@428 | 636 | |
michael@428 | 637 | # send out @{$a->{$k}} as a rdf:bag |
michael@428 | 638 | # $k is the name of the outer tag unless overriden by $tag |
michael@428 | 639 | # $i denotes the depth of indentation, inner tags are indented |
michael@428 | 640 | # 2 or 4 more character positions. |
michael@428 | 641 | # each element of the bag is listed |
michael@428 | 642 | sub xml_bag ($$$;$) { |
michael@428 | 643 | my ($i, $a, $k, $tag) = @_; |
michael@428 | 644 | my ($out, $cond, $upn); |
michael@428 | 645 | return "" if (not exists($a->{$k})); |
michael@428 | 646 | $tag = $k if (not defined($tag)); |
michael@428 | 647 | $out = ''; |
michael@428 | 648 | $i = ' ' x $i; |
michael@428 | 649 | foreach $cond (sort keys %{$a->{$k}}) { |
michael@428 | 650 | next if (not @{$a->{$k}->{$cond}}); |
michael@428 | 651 | $upn = e(upn($cond)); |
michael@428 | 652 | $out .= $i . |
michael@428 | 653 | ($cond ne '' ? "<$tag cond=\"$upn\">\n" : "<$tag>\n") . |
michael@428 | 654 | "$i <rdf:bag>\n" . |
michael@428 | 655 | join("", |
michael@428 | 656 | map { |
michael@428 | 657 | ref $_ |
michael@428 | 658 | ? "$i <resource". |
michael@428 | 659 | ( exists $_->{attrname} |
michael@428 | 660 | ? " $_->{attrname}=\"".e($_->{attrval})."\"" |
michael@428 | 661 | : "" |
michael@428 | 662 | ) . |
michael@428 | 663 | ">".e($_->{resource})."</resource>\n" |
michael@428 | 664 | : "$i <rdf:li>".e($_)."</rdf:li>\n" |
michael@428 | 665 | } |
michael@428 | 666 | @{$a->{$k}->{$cond}}) . |
michael@428 | 667 | "$i </rdf:bag>\n" . |
michael@428 | 668 | "$i</$tag>\n"; |
michael@428 | 669 | } |
michael@428 | 670 | return $out; |
michael@428 | 671 | } |
michael@428 | 672 | |
michael@428 | 673 | # send out reference to another RDF |
michael@428 | 674 | sub xml_reference ($$$$$$) { |
michael@428 | 675 | my($fh, $res, $href, $S, $T, $D) = @_; |
michael@428 | 676 | print $fh " <Repository rdf:resource=\"$res\" href=\"$href\""; |
michael@428 | 677 | if ($opt_S && defined($S) && $S ne "") { |
michael@428 | 678 | print $fh " size=\"$S\""; |
michael@428 | 679 | } |
michael@428 | 680 | if ($opt_T && defined($T) && $T ne "") { |
michael@428 | 681 | print $fh " mtime=\"$T\""; |
michael@428 | 682 | } |
michael@428 | 683 | if ($opt_D && defined($D) && $D ne "") { |
michael@428 | 684 | print $fh " md5=\"$D\""; |
michael@428 | 685 | } |
michael@428 | 686 | print $fh "/>\n"; |
michael@428 | 687 | } |
michael@428 | 688 | |
michael@428 | 689 | # send out reference to other resources |
michael@428 | 690 | sub xml_other ($$$$$$) { |
michael@428 | 691 | my($fh, $about, $href, $S, $T, $D) = @_; |
michael@428 | 692 | print $fh " <rdf:Description about=\"$about\" href=\"$href\""; |
michael@428 | 693 | if ($opt_S && defined($S) && $S ne "") { |
michael@428 | 694 | print $fh " size=\"$S\""; |
michael@428 | 695 | } |
michael@428 | 696 | if ($opt_T && defined($T) && $T ne "") { |
michael@428 | 697 | print $fh " mtime=\"$T\""; |
michael@428 | 698 | } |
michael@428 | 699 | if ($opt_D && defined($D) && $D ne "") { |
michael@428 | 700 | print $fh " md5=\"$D\""; |
michael@428 | 701 | } |
michael@428 | 702 | print $fh ">\n"; |
michael@428 | 703 | print $fh " </rdf:Description>\n"; |
michael@428 | 704 | } |
michael@428 | 705 | |
michael@428 | 706 | # translate attributes from %$a as generated by package2data |
michael@428 | 707 | # into XML and write to file $fh |
michael@428 | 708 | sub xml_record ($$$$$$) { |
michael@428 | 709 | my ($fh, $a, $href, $S, $T, $D) = @_; |
michael@428 | 710 | my ($maj, $min, $rel, $about); |
michael@428 | 711 | |
michael@428 | 712 | $about = |
michael@428 | 713 | n($a, 'Name') . '-' . |
michael@428 | 714 | n($a, 'Version') . '-' . |
michael@428 | 715 | n($a, 'Release'); |
michael@428 | 716 | if (not defined($href)) { |
michael@428 | 717 | # guess location from Information in Specfile |
michael@428 | 718 | if (exists($a->{'NoSource'})) { |
michael@428 | 719 | $href = "$about.nosrc.rpm"; |
michael@428 | 720 | } else { |
michael@428 | 721 | $href = "$about.src.rpm"; |
michael@428 | 722 | } |
michael@428 | 723 | ($maj, $min, $rel) = (n($a, 'Release') =~ m/^(\d+)\.(\d+)\.(\d+)/); |
michael@428 | 724 | if (defined($min)) { |
michael@428 | 725 | if ($maj > 1 || ($maj == 1 && $min > 0)) { |
michael@428 | 726 | # OpenPKG-1.1-RELEASE or later |
michael@428 | 727 | if (n($a, 'Distribution') =~ /\[PLUS\]/) { |
michael@428 | 728 | $href = 'PLUS/'.$href; |
michael@428 | 729 | } |
michael@428 | 730 | } |
michael@428 | 731 | if ($maj > 1 || ($maj == 1 && $min >= 0)) { |
michael@428 | 732 | # OpenPKG-1.0-RELEASE or later |
michael@428 | 733 | if ($rel > 0) { |
michael@428 | 734 | $href = 'UPD/'.$href; |
michael@428 | 735 | } |
michael@428 | 736 | } |
michael@428 | 737 | } else { |
michael@428 | 738 | # OpenPKG-CURRENT |
michael@428 | 739 | } |
michael@428 | 740 | } |
michael@428 | 741 | |
michael@428 | 742 | print $fh " <rdf:Description about=\"$about\" href=\"$href\""; |
michael@428 | 743 | if ($opt_S && defined($S) && $S ne "") { |
michael@428 | 744 | print $fh " size=\"$S\""; |
michael@428 | 745 | } |
michael@428 | 746 | if ($opt_T && defined($T) && $T ne "") { |
michael@428 | 747 | print $fh " mtime=\"$T\""; |
michael@428 | 748 | } |
michael@428 | 749 | if ($opt_D && defined($D) && $D ne "") { |
michael@428 | 750 | print $fh " md5=\"$D\""; |
michael@428 | 751 | } |
michael@428 | 752 | print $fh ">\n"; |
michael@428 | 753 | |
michael@428 | 754 | # fake Source attribute from Source\d attribtutes |
michael@428 | 755 | # XXX only default conditional |
michael@428 | 756 | $a->{'Source'} = { '' => [ |
michael@428 | 757 | map { |
michael@428 | 758 | s/\Q%{name}\E/n($a, 'Name')/esg; |
michael@428 | 759 | s/\Q%{version}\E/n($a, 'Version')/esg; |
michael@428 | 760 | s/\Q%{release}\E/n($a, 'Release')/esg; |
michael@428 | 761 | $_; |
michael@428 | 762 | } |
michael@428 | 763 | map { |
michael@428 | 764 | $a->{$_}->{''} ? @{$a->{$_}->{''}} : () |
michael@428 | 765 | } |
michael@428 | 766 | sort { |
michael@428 | 767 | my ($x) = ($a =~ /^(\d*)$/); |
michael@428 | 768 | my ($y) = ($b =~ /^(\d*)$/); |
michael@428 | 769 | return $x <=> $y; |
michael@428 | 770 | } |
michael@428 | 771 | grep { |
michael@428 | 772 | /^Source\d*$/ |
michael@428 | 773 | } keys(%$a) |
michael@428 | 774 | ]}; |
michael@428 | 775 | delete($a->{'Source'}) if (not @{$a->{'Source'}->{''}}); |
michael@428 | 776 | |
michael@428 | 777 | print $fh |
michael@428 | 778 | xml_tag(6, $a, 'Name'), |
michael@428 | 779 | xml_tag(6, $a, 'Version'), |
michael@428 | 780 | xml_tag(6, $a, 'Release'), |
michael@428 | 781 | xml_tag(6, $a, 'Distribution'), |
michael@428 | 782 | xml_tag(6, $a, 'Class'), |
michael@428 | 783 | xml_tag(6, $a, 'Group'), |
michael@428 | 784 | xml_tag(6, $a, 'License'), |
michael@428 | 785 | xml_tag(6, $a, 'Packager'), |
michael@428 | 786 | xml_tag(6, $a, 'Summary'), |
michael@428 | 787 | xml_tag(6, $a, 'URL'), |
michael@428 | 788 | xml_tag(6, $a, 'Vendor'), |
michael@428 | 789 | xml_tag(6, $a, 'SourceRPM'), |
michael@428 | 790 | xml_tag(6, $a, 'Arch'), |
michael@428 | 791 | xml_tag(6, $a, 'Os'), |
michael@428 | 792 | xml_tag(6, $a, 'BuildHost'), |
michael@428 | 793 | xml_tag(6, $a, 'BuildSystem'), |
michael@428 | 794 | xml_tag(6, $a, 'BuildTime'), |
michael@428 | 795 | xml_tag(6, $a, 'Relocations'), |
michael@428 | 796 | xml_tag(6, $a, 'Size'), |
michael@428 | 797 | xml_tag(6, $a, 'Prefixes'), |
michael@428 | 798 | xml_tag(6, $a, 'Platform'), |
michael@428 | 799 | xml_tag(6, $a, 'SigSize'), |
michael@428 | 800 | xml_tag(6, $a, 'SigMD5'), |
michael@428 | 801 | xml_tag(6, $a, 'SigPGP'), |
michael@428 | 802 | xml_tag(6, $a, 'SigGPG'), |
michael@428 | 803 | xml_bag(6, $a, 'BuildPreReq'), |
michael@428 | 804 | xml_bag(6, $a, 'PreReq'), |
michael@428 | 805 | xml_bag(6, $a, 'Provides'), |
michael@428 | 806 | xml_bag(6, $a, 'Conflicts'), |
michael@428 | 807 | xml_bag(6, $a, 'Source'), |
michael@428 | 808 | xml_bag(6, $a, 'NoSource'), |
michael@428 | 809 | xml_bag(6, $a, 'Filenames'), |
michael@428 | 810 | xml_text(6, $a, 'Description'); |
michael@428 | 811 | |
michael@428 | 812 | print $fh " </rdf:Description>\n"; |
michael@428 | 813 | } |
michael@428 | 814 | |
michael@428 | 815 | ############################################################################# |
michael@428 | 816 | ## |
michael@428 | 817 | ## OpenPKG RPM Package Payload Extraction |
michael@428 | 818 | ## |
michael@428 | 819 | ############################################################################# |
michael@428 | 820 | |
michael@428 | 821 | # extract foo.spec from foo-V-R.src.rpm file |
michael@428 | 822 | sub rpm2spec ($) { |
michael@428 | 823 | my ($fn) = @_; |
michael@428 | 824 | local($SIG{'PIPE'}) = 'IGNORE'; |
michael@428 | 825 | local(*PIPE); |
michael@428 | 826 | open(PIPE, "$R2C '$fn' |") |
michael@428 | 827 | or die "openpkg:index:FATAL: cannot read '$fn' ($!)\n"; |
michael@428 | 828 | my ($buf, @hdr, $n, $m, $name, $step); |
michael@428 | 829 | my ($spec); |
michael@428 | 830 | while (read(PIPE, $buf, 110) == 110) { |
michael@428 | 831 | @hdr = unpack('a6a8a8a8a8a8a8a8a8a8a8a8a8a8', $buf); |
michael@428 | 832 | $n = hex($hdr[12]); # filename length |
michael@428 | 833 | $m = int(($n+5)/4)*4-2; # filename size (padded) |
michael@428 | 834 | last if (not (read(PIPE,$buf,$m) == $m)); |
michael@428 | 835 | $name = substr($buf, 0, $n-1); |
michael@428 | 836 | $n = hex($hdr[7]); # file length |
michael@428 | 837 | $m = int(($n+3)/4)*4; # file size (padded) |
michael@428 | 838 | if ($name !~ m/.spec$/) { |
michael@428 | 839 | while ($m > 0) { |
michael@428 | 840 | $step = $m > 8192 ? 8192 : $m; |
michael@428 | 841 | last if (not read(PIPE,$buf,$step)); |
michael@428 | 842 | $m -= length($buf); |
michael@428 | 843 | } |
michael@428 | 844 | } else { |
michael@428 | 845 | if (read(PIPE,$buf,$n) == $n) { |
michael@428 | 846 | $spec = $buf; |
michael@428 | 847 | } |
michael@428 | 848 | last; |
michael@428 | 849 | } |
michael@428 | 850 | } |
michael@428 | 851 | close(PIPE); |
michael@428 | 852 | return $spec; |
michael@428 | 853 | } |
michael@428 | 854 | |
michael@428 | 855 | ############################################################################# |
michael@428 | 856 | ## |
michael@428 | 857 | ## OpenPKG RPM Package Header Extraction |
michael@428 | 858 | ## |
michael@428 | 859 | ############################################################################# |
michael@428 | 860 | |
michael@428 | 861 | # extract header information from foo-V-R.src.rpm |
michael@428 | 862 | sub rpm2data ($$) { |
michael@428 | 863 | my ($fn, $platform) = @_; |
michael@428 | 864 | my ($q, %a); |
michael@428 | 865 | my ($t, $v); |
michael@428 | 866 | local(*PIPE); |
michael@428 | 867 | |
michael@428 | 868 | if (not defined($platform)) { |
michael@428 | 869 | die "openpkg:index:FATAL: indexing binary package '$fn' requires -p option\n"; |
michael@428 | 870 | } |
michael@428 | 871 | |
michael@428 | 872 | $q = <<EOFEOF; |
michael@428 | 873 | Name %{Name} |
michael@428 | 874 | Version %{Version} |
michael@428 | 875 | Release %{Release} |
michael@428 | 876 | URL %{URL} |
michael@428 | 877 | Summary %{Summary} |
michael@428 | 878 | Copyright %{Copyright} |
michael@428 | 879 | License %{License} |
michael@428 | 880 | Distribution %{Distribution} |
michael@428 | 881 | Vendor %{Vendor} |
michael@428 | 882 | Class %{Class} |
michael@428 | 883 | Group %{Group} |
michael@428 | 884 | Packager %{Packager} |
michael@428 | 885 | Prefixes %{Prefixes} |
michael@428 | 886 | BuildHost %{BuildHost} |
michael@428 | 887 | BuildTime %{BuildTime} |
michael@428 | 888 | Arch %{Arch} |
michael@428 | 889 | Os %{Os} |
michael@428 | 890 | Size %{Size} |
michael@428 | 891 | SigSize %{SigSize} |
michael@428 | 892 | SigMD5 %{SigMD5} |
michael@428 | 893 | SigPGP %{SigPGP} |
michael@428 | 894 | SigGPG %{SigGPG} |
michael@428 | 895 | SourceRPM %{SourceRPM} |
michael@428 | 896 | [Patch %{Patch} |
michael@428 | 897 | ] |
michael@428 | 898 | [Source %{Source} |
michael@428 | 899 | ] |
michael@428 | 900 | [Filenames %{Filenames} |
michael@428 | 901 | ] |
michael@428 | 902 | [Conflicts %{CONFLICTNAME} %|CONFLICTFLAGS?{%{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}}:{}| |
michael@428 | 903 | ] |
michael@428 | 904 | [PreReq %{REQUIRENAME} %|REQUIREFLAGS?{%{REQUIREFLAGS:depflags} %{REQUIREVERSION}}:{}| |
michael@428 | 905 | ] |
michael@428 | 906 | [Provides %{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}| |
michael@428 | 907 | ] |
michael@428 | 908 | Description %{Description} |
michael@428 | 909 | EOFEOF |
michael@428 | 910 | |
michael@428 | 911 | open(PIPE, "$RPM -qp --qf '$q' '$fn' |") |
michael@428 | 912 | or die "FATAL: cannot read '$fn' ($!)\n"; |
michael@428 | 913 | while (<PIPE>) { |
michael@428 | 914 | if (/^(\S+)\s+(.*?)\s*$/) { |
michael@428 | 915 | $t = $1; |
michael@428 | 916 | $v = $2; |
michael@428 | 917 | } elsif (/^(\s+.+?)\s*$/) { |
michael@428 | 918 | next if (not defined($t)); |
michael@428 | 919 | $v = $1; |
michael@428 | 920 | } else { |
michael@428 | 921 | $t = undef; |
michael@428 | 922 | next; |
michael@428 | 923 | } |
michael@428 | 924 | if (exists($a{$t})) { |
michael@428 | 925 | $a{$t} .= "\n$v"; |
michael@428 | 926 | } else { |
michael@428 | 927 | $a{$t} = $v; |
michael@428 | 928 | } |
michael@428 | 929 | } |
michael@428 | 930 | close(PIPE); |
michael@428 | 931 | |
michael@428 | 932 | %a = map { $_ => $a{$_} } |
michael@428 | 933 | grep { $a{$_} ne '(none)' } |
michael@428 | 934 | keys(%a); |
michael@428 | 935 | if ($a{'Relocations'} eq '(non relocatable)') { |
michael@428 | 936 | delete($a{'Relocations'}); |
michael@428 | 937 | } |
michael@428 | 938 | if ($a{'SigMD5'} eq '(unknown type)') { |
michael@428 | 939 | delete($a{'SigMD5'}); |
michael@428 | 940 | } |
michael@428 | 941 | if (defined($platform)) { |
michael@428 | 942 | $a{'Platform'} = $platform; |
michael@428 | 943 | } |
michael@428 | 944 | $a{'Description'} = [ $a{'Description'} ]; |
michael@428 | 945 | |
michael@428 | 946 | foreach ('Conflicts', 'PreReq', 'Provides') { |
michael@428 | 947 | $a{$_} = [ |
michael@428 | 948 | map { &$make_resource($_) } |
michael@428 | 949 | grep { !/^rpmlib\(/ } |
michael@428 | 950 | split(/\n+/, $a{$_}) |
michael@428 | 951 | ]; |
michael@428 | 952 | } |
michael@428 | 953 | |
michael@428 | 954 | return { map { |
michael@428 | 955 | $_ => { '' => (ref $a{$_} ? $a{$_} : [ split(/\n+/, $a{$_}) ]) } |
michael@428 | 956 | } keys(%a) }; |
michael@428 | 957 | } |
michael@428 | 958 | |
michael@428 | 959 | ############################################################################# |
michael@428 | 960 | ## |
michael@428 | 961 | ## OpenPKG XML/RDF Index Locating |
michael@428 | 962 | ## |
michael@428 | 963 | ############################################################################# |
michael@428 | 964 | |
michael@428 | 965 | # detect index files |
michael@428 | 966 | sub getindex ($) { |
michael@428 | 967 | my ($dir) = @_; |
michael@428 | 968 | my (@idx) = |
michael@428 | 969 | sort { -M $a <=> -M $b; } |
michael@428 | 970 | grep { -f $_ } |
michael@428 | 971 | ( <$dir/00INDEX.rdf>, <$dir/00INDEX.rdf.*> ); |
michael@428 | 972 | return if (not @idx); |
michael@428 | 973 | return $idx[0]; |
michael@428 | 974 | } |
michael@428 | 975 | |
michael@428 | 976 | # list RPM directories of unpacked .spec files |
michael@428 | 977 | sub list_specdir ($) { |
michael@428 | 978 | my ($dir) = @_; |
michael@428 | 979 | my ($d, $path); |
michael@428 | 980 | my (@list); |
michael@428 | 981 | |
michael@428 | 982 | local(*DH); |
michael@428 | 983 | opendir(DH, $dir); |
michael@428 | 984 | while ($d = readdir(DH)) { |
michael@428 | 985 | next if ($d =~ m/^\./); |
michael@428 | 986 | $path = "$dir/$d/$d.spec"; |
michael@428 | 987 | push(@list, $path) if (-f $path); |
michael@428 | 988 | } |
michael@428 | 989 | closedir(DH); |
michael@428 | 990 | return \@list; |
michael@428 | 991 | } |
michael@428 | 992 | |
michael@428 | 993 | # list index or RPM file directories |
michael@428 | 994 | sub list_rpmdir ($) { |
michael@428 | 995 | my ($dir) = @_; |
michael@428 | 996 | my ($d, $path); |
michael@428 | 997 | my (@list, $idx, $sub); |
michael@428 | 998 | |
michael@428 | 999 | local(*DH); |
michael@428 | 1000 | opendir(DH, $dir); |
michael@428 | 1001 | while ($d = readdir(DH)) { |
michael@428 | 1002 | next if ($d =~ m/^\./); |
michael@428 | 1003 | $path = "$dir/$d"; |
michael@428 | 1004 | if (-d $path) { |
michael@428 | 1005 | $idx = getindex($path); |
michael@428 | 1006 | if (defined($idx)) { |
michael@428 | 1007 | push(@list, $idx); |
michael@428 | 1008 | } else { |
michael@428 | 1009 | $sub = list_rpmdir($path); |
michael@428 | 1010 | push(@list, @$sub); |
michael@428 | 1011 | undef $sub; |
michael@428 | 1012 | } |
michael@428 | 1013 | } else { |
michael@428 | 1014 | next if (not ($d =~ m/\.(?:rpm|sh)$/ && -f $path)); |
michael@428 | 1015 | push(@list, $path); |
michael@428 | 1016 | } |
michael@428 | 1017 | } |
michael@428 | 1018 | closedir(DH); |
michael@428 | 1019 | return \@list; |
michael@428 | 1020 | } |
michael@428 | 1021 | |
michael@428 | 1022 | ############################################################################# |
michael@428 | 1023 | ## |
michael@428 | 1024 | ## OpenPKG XML/RDF Index Reading |
michael@428 | 1025 | ## |
michael@428 | 1026 | ############################################################################# |
michael@428 | 1027 | |
michael@428 | 1028 | # fetch a whole file |
michael@428 | 1029 | sub readfile ($) { |
michael@428 | 1030 | my ($fn) = @_; |
michael@428 | 1031 | local(*FH); |
michael@428 | 1032 | open(FH, "< $fn") |
michael@428 | 1033 | or die "FATAL: cannot read '$fn' ($!)\n"; |
michael@428 | 1034 | my $l; { local $/; $l = <FH>; } |
michael@428 | 1035 | close(FH); |
michael@428 | 1036 | return $l; |
michael@428 | 1037 | } |
michael@428 | 1038 | |
michael@428 | 1039 | # create relative path by stripping instance prefix |
michael@428 | 1040 | sub relpath ($$) { |
michael@428 | 1041 | my ($prefix, $path) = @_; |
michael@428 | 1042 | $path =~ s/^\Q$prefix\E\///s; |
michael@428 | 1043 | return $path; |
michael@428 | 1044 | } |
michael@428 | 1045 | |
michael@428 | 1046 | # create directory path with trailing slash |
michael@428 | 1047 | sub dirname ($) { |
michael@428 | 1048 | my ($path) = @_; |
michael@428 | 1049 | $path =~ s/\/[^\/]*$//s; |
michael@428 | 1050 | return $path.'/'; |
michael@428 | 1051 | } |
michael@428 | 1052 | |
michael@428 | 1053 | # peek at resource identifier of index file |
michael@428 | 1054 | sub getresource ($) { |
michael@428 | 1055 | my ($fn) = @_; |
michael@428 | 1056 | my ($fh, $buf); |
michael@428 | 1057 | |
michael@428 | 1058 | local(*FH); |
michael@428 | 1059 | if ($fn =~ /\.bz2$/) { |
michael@428 | 1060 | open(FH, "$BZ -dc $fn |") |
michael@428 | 1061 | or die "FATAL: cannot read '$fn' ($!)\n"; |
michael@428 | 1062 | } else { |
michael@428 | 1063 | open(FH, "< $fn") |
michael@428 | 1064 | or die "FATAL: cannot read '$fn' ($!)\n"; |
michael@428 | 1065 | } |
michael@428 | 1066 | read(FH, $buf, 1024); |
michael@428 | 1067 | close(FH); |
michael@428 | 1068 | if ($buf =~ /<Repository.*?rdf:resource="([^"]+)"/) { |
michael@428 | 1069 | return $1; |
michael@428 | 1070 | } |
michael@428 | 1071 | return undef; |
michael@428 | 1072 | } |
michael@428 | 1073 | |
michael@428 | 1074 | ############################################################################# |
michael@428 | 1075 | ## |
michael@428 | 1076 | ## OpenPKG XML/RDF Index Output Generation |
michael@428 | 1077 | ## |
michael@428 | 1078 | ############################################################################# |
michael@428 | 1079 | |
michael@428 | 1080 | sub sizetime ($) { |
michael@428 | 1081 | my ($file) = @_; |
michael@428 | 1082 | my @stat = stat($file); |
michael@428 | 1083 | return ($stat[7], $stat[9]); |
michael@428 | 1084 | } |
michael@428 | 1085 | |
michael@428 | 1086 | sub digest ($) { |
michael@428 | 1087 | my ($file) = @_; |
michael@428 | 1088 | my $digest = ""; |
michael@428 | 1089 | my $output = `($OPENSSL md5 -hex "$file") 2>/dev/null || true`; |
michael@428 | 1090 | if ($output =~ m/MD5\(.+?\)=\s*([0-9a-fA-F]+)/s) { |
michael@428 | 1091 | $digest = uc($1); |
michael@428 | 1092 | } |
michael@428 | 1093 | return $digest; |
michael@428 | 1094 | } |
michael@428 | 1095 | |
michael@428 | 1096 | sub write_index ($$$$$$) { |
michael@428 | 1097 | my ($fh, $prefix, $resource, $platform, $list, $cache) = @_; |
michael@428 | 1098 | my ($a, $h, $r, $spec, $T, $D); |
michael@428 | 1099 | |
michael@428 | 1100 | foreach (@$list) { |
michael@428 | 1101 | $a = undef; |
michael@428 | 1102 | $h = undef; |
michael@428 | 1103 | $r = undef; |
michael@428 | 1104 | $S = undef; |
michael@428 | 1105 | $T = undef; |
michael@428 | 1106 | $D = undef; |
michael@428 | 1107 | $o = undef; |
michael@428 | 1108 | |
michael@428 | 1109 | # determine information |
michael@428 | 1110 | if (m/\.spec$/) { |
michael@428 | 1111 | $spec = readfile($_); |
michael@428 | 1112 | $a = spec2data($spec); |
michael@428 | 1113 | ($S, $T) = sizetime($_) if ($opt_S or $opt_T); |
michael@428 | 1114 | $D = digest($_) if ($opt_D); |
michael@428 | 1115 | } elsif (m/([^\/]+\.(?:no)?src\.rpm)$/) { |
michael@428 | 1116 | $h = relpath($prefix, $_); |
michael@428 | 1117 | ($S, $T) = sizetime($_) if ($opt_S or $opt_T or $cache); |
michael@428 | 1118 | if ($cache) { |
michael@428 | 1119 | if ( exists($cache->{"T$_"}) |
michael@428 | 1120 | && $cache->{"T$_"} == $T |
michael@428 | 1121 | && exists($cache->{"s$_"}) |
michael@428 | 1122 | && $cache->{"s$_"} ne "" |
michael@428 | 1123 | && (!$opt_S || ($opt_S && exists($cache->{"S$_"}) && $cache->{"S$_"} ne "")) |
michael@428 | 1124 | && (!$opt_D || ($opt_D && exists($cache->{"D$_"}) && $cache->{"D$_"} ne ""))) { |
michael@428 | 1125 | # found in cache |
michael@428 | 1126 | $spec = $cache->{"s$_"}; |
michael@428 | 1127 | $S = $cache->{"S$_"} if ($opt_S); |
michael@428 | 1128 | $D = $cache->{"D$_"} if ($opt_D); |
michael@428 | 1129 | } else { |
michael@428 | 1130 | # not found in cache |
michael@428 | 1131 | $spec = rpm2spec($_); |
michael@428 | 1132 | $cache->{"s$_"} = $spec; |
michael@428 | 1133 | $cache->{"S$_"} = $S; |
michael@428 | 1134 | $cache->{"T$_"} = $T; |
michael@428 | 1135 | $D = digest($_) if ($opt_D); |
michael@428 | 1136 | $cache->{"D$_"} = $D if ($opt_D); |
michael@428 | 1137 | } |
michael@428 | 1138 | } else { |
michael@428 | 1139 | # no cache at all |
michael@428 | 1140 | $spec = rpm2spec($_); |
michael@428 | 1141 | $D = digest($_) if ($opt_D); |
michael@428 | 1142 | } |
michael@428 | 1143 | $a = spec2data($spec); |
michael@428 | 1144 | } elsif (m/([^\/]+\.rpm)$/) { |
michael@428 | 1145 | $h = relpath($prefix, $_); |
michael@428 | 1146 | $a = rpm2data($_, $platform); |
michael@428 | 1147 | ($S, $T) = sizetime($_) if ($opt_S or $opt_T); |
michael@428 | 1148 | $D = digest($_) if ($opt_D); |
michael@428 | 1149 | } elsif (m/(([^\/]+)\.(?:no)?src\.sh)$/) { |
michael@428 | 1150 | $h = relpath($prefix, $_); |
michael@428 | 1151 | ($S, $T) = sizetime($_) if ($opt_S or $opt_T); |
michael@428 | 1152 | $D = digest($_) if ($opt_D); |
michael@428 | 1153 | $o = "$2:bootstrap"; |
michael@428 | 1154 | } elsif (m/([^\/]+\.rdf[^\/]*)$/) { |
michael@428 | 1155 | $h = relpath($prefix, $_); |
michael@428 | 1156 | $r = getresource($_) || $resource.dirname($h); |
michael@428 | 1157 | ($S, $T) = sizetime($_) if ($opt_S or $opt_T); |
michael@428 | 1158 | $D = digest($_) if ($opt_D); |
michael@428 | 1159 | } |
michael@428 | 1160 | |
michael@428 | 1161 | # process information |
michael@428 | 1162 | if ($a) { |
michael@428 | 1163 | xml_record($fh, $a, $h, $S, $T, $D); |
michael@428 | 1164 | } elsif ($o) { |
michael@428 | 1165 | xml_other($fh, $o, $h, $S, $T, $D); |
michael@428 | 1166 | } elsif ($r) { |
michael@428 | 1167 | xml_reference($fh, $r, $h, $S, $T, $D); |
michael@428 | 1168 | } else { |
michael@428 | 1169 | warn "openpkg:index:ERROR: cannot process $_"; |
michael@428 | 1170 | } |
michael@428 | 1171 | } |
michael@428 | 1172 | } |
michael@428 | 1173 |