openpkg-index.pl 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. ##
  2. ## openpkg-index.pl -- OpenPKG Maintenance Tool (backend for indexing)
  3. ## Copyright (c) 2000-2003 The OpenPKG Project <http://www.openpkg.org/>
  4. ## Copyright (c) 2000-2003 Ralf S. Engelschall <rse@engelschall.com>
  5. ## Copyright (c) 2000-2003 Cable & Wireless <http://www.cw.com/>
  6. ##
  7. ## Permission to use, copy, modify, and distribute this software for
  8. ## any purpose with or without fee is hereby granted, provided that
  9. ## the above copyright notice and this permission notice appear in all
  10. ## copies.
  11. ##
  12. ## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  13. ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  14. ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  15. ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
  16. ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  17. ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  18. ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  19. ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  20. ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  21. ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  22. ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23. ## SUCH DAMAGE.
  24. ##
  25. require 5;
  26. use strict;
  27. use Getopt::Std;
  28. getopts('r:p:C:o:ci');
  29. use vars qw/$opt_r $opt_p $opt_C $opt_o $opt_c $opt_i/;
  30. use FileHandle;
  31. use DirHandle;
  32. my $l_prefix = '@l_prefix@';
  33. my $RPM = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/rpm") ?
  34. "$l_prefix/bin/openpkg rpm" : "$l_prefix/bin/rpm");
  35. my $R2C = ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/rpm2cpio") ?
  36. "$l_prefix/bin/openpkg rpm2cpio" : "$l_prefix/bin/rpm2cpio");
  37. my $BZ = "$l_prefix/lib/openpkg/bzip2 -9";
  38. #########################################################################
  39. #
  40. # escape XML special characters for output in RDF file
  41. #
  42. # remove trailing whitespace
  43. # remove common leading whitespace
  44. #
  45. sub e ($) {
  46. my($s) = @_;
  47. my($i);
  48. $s =~ s/\n+$//sg;
  49. $s =~ s/[^\S\n]+$//mg;
  50. $i = undef;
  51. while ($s =~ /^([^\S\n]+)/mg) {
  52. $i = $1 if !defined $i || length($1) < length($i);
  53. }
  54. $s =~ s/^\Q$i\E//mg if defined $i;
  55. $s =~ s/&/&amp;/sg;
  56. $s =~ s/</&lt;/sg;
  57. $s =~ s/>/&gt;/sg;
  58. return $s;
  59. }
  60. my %attrname = (
  61. '==' => 'equ',
  62. '=' => 'equ',
  63. '>=' => 'geq',
  64. '=>' => 'geq',
  65. '<=' => 'leq',
  66. '=<' => 'leq',
  67. '>' => 'gt',
  68. '<' => 'lt'
  69. );
  70. my($opreg) = join '|',
  71. map {
  72. "\Q$_\E"
  73. } sort {
  74. length($b) <=> length($a) ||
  75. $b cmp $a
  76. } keys %attrname;
  77. sub make_resource ($) {
  78. my($s) = @_;
  79. if ($s =~ /(\S+)\s*($opreg)\s*(.*?)\s*$/o) {
  80. return {
  81. resource => $1,
  82. attrname => $attrname{$2},
  83. attrval => $3
  84. }
  85. }
  86. return {
  87. resource => $s
  88. }
  89. }
  90. sub commasep ($$) {
  91. my($k,$v) = @_;
  92. if ($k =~ /^(NoSource)$/) {
  93. return split(/\s*,\s*/, $v);
  94. } elsif ($k =~ /^(PreReq|BuildPreReq|Provides|Conflicts)$/) {
  95. return map { make_resource($_) }
  96. split(/\s*,\s*/, $v);
  97. }
  98. return $v;
  99. }
  100. sub optesc ($) {
  101. my($s) = @_;
  102. $s =~ s/([\x00-\x1f\x80-\xbf\s\%])/sprintf("%%%02x",ord($1))/eg;
  103. return $s;
  104. }
  105. sub vsub ($$) {
  106. my($var,$v) = @_;
  107. $v =~ s/\%\{([^}]+)\}/
  108. exists $var->{$1} ? $var->{$1} : '%{'.$1.'}'/emg;
  109. return $v;
  110. }
  111. sub upn ($) {
  112. my($t) = @_;
  113. my(@tok) = $t =~ /(\(|\)|\&\&|\|\||\!|\S+)/g;
  114. my(@out,$op,$o);
  115. my(@save);
  116. $op = [];
  117. foreach (@tok) {
  118. if ($_ eq '(') {
  119. push @save, $op;
  120. $op = [];
  121. } elsif ($_ eq ')') {
  122. die "FATAL: unresolved operators in: @tok\n" if @$op;
  123. $op = pop @save
  124. or die "FATAL: parenthesis stack underflow in: @tok\n";
  125. while ($o = pop @$op) {
  126. push @out, $o->[0];
  127. last if $o->[1];
  128. }
  129. } elsif ($_ eq '&&') {
  130. push @$op, [ '+', 1 ] ;
  131. } elsif ($_ eq '||') {
  132. push @$op, [ '|', 1 ] ;
  133. } elsif ($_ eq '!') {
  134. push @$op, [ '!', 0 ];
  135. } elsif (/^\%\{(\S*?)\}$/) {
  136. push @out, $1;
  137. while ($o = pop @$op) {
  138. push @out, $o->[0];
  139. last if $o->[1]; # binop
  140. }
  141. }
  142. }
  143. return join (' ',@out);
  144. }
  145. #
  146. # deduce external variables from description
  147. #
  148. # before openpkg-20021230
  149. #
  150. sub find_options ($) {
  151. my($descr) = @_;
  152. my $evar = {};
  153. $descr =~ s/--define\s*'(\S+)\s*\%\{\1\}'/$evar->{$1} = '%{'.$1.'}', ''/sge;
  154. return $evar;
  155. }
  156. #
  157. # translate default section from spec-file
  158. # into a hash
  159. # %if/%ifdef/%define... are translated to #if/#ifdef/#define
  160. #
  161. # #defines are interpolated (correct ?)
  162. #
  163. # #if/#ifdef/... sections are stripped
  164. # result is the same as if all conditions evaluate false (!)
  165. #
  166. # all attributes are of the form key: value
  167. # repeated attributes are coalesced into a list
  168. #
  169. sub package2data ($$) {
  170. my($s,$ovar) = @_;
  171. my(%evar,%var);
  172. my(@term, $term);
  173. my(%attr,%avar);
  174. my($l, $v, $cond, $d, $p);
  175. my($re,@defs);
  176. # combine multilines
  177. $s =~ s/\\\n/ /sg;
  178. #
  179. # map conditional variable macros
  180. #
  181. $s =~ s/^#\{\!\?([^:]*):\s*%(.*?)\s*\}\s*$/#ifndef $1\n#$2\n#endif/mg;
  182. $s =~ s/^#\{\!\?([^:]*):\s*(.*?)\s*\}\s*$/#ifndef $1\n$2\n#endif/mg;
  183. #
  184. # map option macro
  185. #
  186. $s =~ s/^#option\s+(\S+)\s*(.*?)\s*$/#ifndef $1\n#define $1 $2\n#endif\n#provides $1 $2/mg;
  187. #
  188. # use option variables for interpolation
  189. #
  190. %evar = %$ovar;
  191. #
  192. # guess more external parameters by scanning for "default" sections.
  193. #
  194. $re = '^\#ifndef\s+[\w\_]+\s*\n((?:\#define\s+[\w\_]+\s.*\n)+)\#endif\n';
  195. @defs = $s =~ /$re/gm;
  196. foreach (@defs) {
  197. while (/^\#define\s+([\w\_]+)\s(.*?)\s*$/mg) {
  198. $ovar->{$1} = $2;
  199. $evar{$1} = '%{'.$1.'}';
  200. }
  201. }
  202. $s =~ s/$re//gm;
  203. #
  204. # add everything looking like a with_ variable
  205. #
  206. $re = '%{(with\_[\w\_]+)}';
  207. @defs = $s =~ /$re/gm;
  208. foreach (@defs) {
  209. next if exists $ovar->{$1};
  210. $ovar->{$1} = '%{'.$1.'}';
  211. $evar{$1} = '%{'.$1.'}';
  212. }
  213. #
  214. # extract all conditional sections
  215. #
  216. @term = ();
  217. %var = ();
  218. $cond = '';
  219. foreach $l (split(/\n/, $s)) {
  220. $v = vsub(\%avar, vsub(\%var, $l));
  221. if (($p) = $v =~ /^\#if\s+(.*?)\s*$/) {
  222. #
  223. # normalize #if expressions
  224. # "%{variable}" == "yes"
  225. # "%{variable}" == "no"
  226. # operators ! && ||
  227. #
  228. $term = '';
  229. while ($p =~ /(!=)|(\!|\|\||\&\&|\(|\))|"\%\{([^}]+)\}"\s*==\s*"(yes|no)"|(\S+)/g) {
  230. if (defined $1) {
  231. warn "WARNING: unknown token '$1':\n< $l\n> $v\n";
  232. } elsif (defined $5) {
  233. warn "WARNING: unknown token '$5':\n< $l\n> $v\n";
  234. } elsif (defined $2) {
  235. $term .= " $2 ";
  236. } elsif (exists $evar{$3}) {
  237. $term .= ($4 eq 'no' ? '! ' : '').vsub(\%evar,'%{'.$3.'}');
  238. } else {
  239. warn "WARNING: unknown conditional '$3':\n< $l\n> $v\n";
  240. }
  241. }
  242. #
  243. # join with previous conditions for this #if/#endif block
  244. #
  245. if ($term ne '') {
  246. push @term, "( $term )";
  247. $cond = join(' && ', grep { $_ ne '' } @term).'';
  248. } else {
  249. push @term, '';
  250. }
  251. } elsif ($v =~ /^\#else\s*$/) {
  252. #
  253. # reverse last condition
  254. #
  255. if (@term) {
  256. $term[-1] = ' ! '.$term[-1];
  257. $cond = join(' && ', grep { $_ ne '' } @term).'';
  258. } else {
  259. die "FATAL: else without if\n";
  260. }
  261. } elsif ($v =~ /^\#endif\s*$/) {
  262. #
  263. # unwind last #if expression
  264. #
  265. pop @term;
  266. $cond = join(' && ', grep { $_ ne '' } @term).'';
  267. } elsif ($v =~ /^\#(?:define)\s*(\S+)\s*(.*?)\s*$/) {
  268. #
  269. # define conditional variables
  270. # truth-value becomes current condition
  271. #
  272. # define internal variables
  273. # -> store for subsequent substitution
  274. #
  275. if (exists $evar{$1}) {
  276. if ($2 eq 'yes') {
  277. if ($cond eq '') {
  278. $evar{$1} = "( \%\{$1\} )";
  279. } else {
  280. $evar{$1} = "( \%\{$1\} || ( $cond ) )";
  281. }
  282. } elsif ($2 eq 'no') {
  283. if ($cond eq '') {
  284. $evar{$1} = "( \%\{$1\} )";
  285. } else {
  286. $evar{$1} = "( %\{$1\} && ! ( $cond ) )";
  287. }
  288. } else {
  289. warn "WARNING: logic too complex for '$1':\n< $l\n> $v\n";
  290. }
  291. } else {
  292. $var{$1} = $2;
  293. }
  294. } elsif ($v =~ /^\#(?:undefine)\s*(\S+)\s*$/) {
  295. if (exists $evar{$1}) {
  296. $evar{$1} = "\%\{$1\}";
  297. } else {
  298. delete $var{$1};
  299. }
  300. } elsif ($v =~ /^\#(?:provides)\s*(\S+)\s*(.*?)\s*$/) {
  301. #
  302. # store option for current condition
  303. #
  304. if (exists $attr{'Name'}->{''}) {
  305. push @{$attr{'Provides'}->{$cond}}, {
  306. resource => $attr{'Name'}->{''}->[0].'::'.$1,
  307. attrname => 'equ',
  308. attrval => optesc($2)
  309. }
  310. } else {
  311. warn "ERROR: no package name set for option $1 = $2\n";
  312. }
  313. } elsif ($v =~ /^\#NoSource\s*(.*?)\s*$/) {
  314. #
  315. # store conditional NoSource attribute
  316. #
  317. push @{$attr{'NoSource'}->{$cond}}, commasep('NoSource',$1);
  318. } elsif ($v =~ /^\s*([^\#]\S*)\s*:\s*(.*?)\s*$/) {
  319. #
  320. # store attribute=value for current condition
  321. #
  322. push @{$attr{$1}->{$cond}}, commasep($1,$2);
  323. $avar{lc($1)} = $2 if $cond eq '';
  324. }
  325. }
  326. return \%attr;
  327. }
  328. #
  329. # split spec file into sections starting with a %word
  330. #
  331. # concatenate extended lines
  332. # strip comment lines
  333. # map %command to #command
  334. # split sections
  335. #
  336. # return package2data from default section.
  337. #
  338. sub spec2data ($) {
  339. my($s) = @_;
  340. my(%map);
  341. my($a,$o);
  342. my $spec = $s;
  343. # remove comments
  344. $s =~ s/^\s*#.*?\n//mg;
  345. # map commands
  346. $s =~ s/^%(ifdef|ifndef|if|NoSource|option|undefine|define|else|endif|\{)/#$1/mg;
  347. # split sections
  348. foreach (split(/^(?=%\w+\s*\n)/m, $s)) {
  349. if (/^%(\w+)\s*\n/) {
  350. $map{$1} .= $';
  351. } else {
  352. $map{'*'} .= $_;
  353. }
  354. }
  355. if (exists $map{'description'}) {
  356. $o = find_options($map{'description'});
  357. $a = package2data($map{'*'}, $o );
  358. $a->{'Description'} = { '' => [ $map{'description'} ] };
  359. } else {
  360. $a = package2data($map{'*'}, {});
  361. }
  362. return $a;
  363. }
  364. ##########################################################################
  365. #
  366. # start of XML file
  367. #
  368. sub xml_head ($$) {
  369. my($fh,$res) = @_;
  370. print $fh <<EOFEOF;
  371. <?xml version="1.0" encoding="iso-8859-1"?>
  372. <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  373. xmlns="http://www.openpkg.org/xml-rdf-index/0.9">
  374. <Repository rdf:resource="$res">
  375. EOFEOF
  376. }
  377. #
  378. # end of XML file, corresponds with start tags
  379. #
  380. sub xml_foot ($) {
  381. my($fh) = @_;
  382. print $fh <<EOFEOF;
  383. </Repository>
  384. </rdf:RDF>
  385. EOFEOF
  386. }
  387. sub n($$) {
  388. my($a,$k) = @_;
  389. return unless $a->{$k};
  390. return unless $a->{$k}->{''};
  391. return $a->{$k}->{''}->[0];
  392. }
  393. #
  394. # send out $a->{$k} as text-style tag
  395. #
  396. sub xml_text ($$$;$) {
  397. my($i,$a,$k,$tag) = @_;
  398. my($out);
  399. return "" unless exists $a->{$k};
  400. $tag = $k unless defined $tag;
  401. $i = ' ' x $i;
  402. $out = e(n($a,$k));
  403. return if $out eq '';
  404. return "$i<$tag>\n$out\n$i</$tag>\n";
  405. }
  406. #
  407. # send out @{$a->{$k}} as body of an XML tag
  408. # $k is the name of the tag unless overridden by $tag
  409. # $i denotes the depth of indentation to form nicely
  410. # looking files.
  411. #
  412. # all data from the list is flattened into a single
  413. # body, separated by LF and escaped for XML metachars.
  414. #
  415. sub xml_tag ($$$;$) {
  416. my($i,$a,$k,$tag) = @_;
  417. my($out,$cond,$upn);
  418. return "" unless exists $a->{$k};
  419. $tag = $k unless defined $tag;
  420. $out = '';
  421. $i = ' ' x $i;
  422. foreach $cond (sort keys %{$a->{$k}}) {
  423. $upn = e(upn($cond));
  424. $out .= $i.
  425. ($cond ne '' ? "<$tag cond=\"$upn\">" : "<$tag>").
  426. join("\n", map { e($_) } @{$a->{$k}->{$cond}}).
  427. "</$tag>\n";
  428. }
  429. return $out;
  430. }
  431. #
  432. # send out @{$a->{$k}} as a rdf:bag
  433. # $k is the name of the outer tag unless overriden by $tag
  434. # $i denotes the depth of indentation, inner tags are indented
  435. # 2 or 4 more character positions.
  436. #
  437. # each element of the bag is listed
  438. #
  439. sub xml_bag ($$$;$) {
  440. my($i,$a,$k,$tag) = @_;
  441. my($out,$cond,$upn);
  442. return "" unless exists $a->{$k};
  443. $tag = $k unless defined $tag;
  444. $out = '';
  445. $i = ' ' x $i;
  446. foreach $cond (sort keys %{$a->{$k}}) {
  447. next unless @{$a->{$k}->{$cond}};
  448. $upn = e(upn($cond));
  449. $out .= $i.
  450. ($cond ne '' ? "<$tag cond=\"$upn\">\n" : "<$tag>\n").
  451. "$i <rdf:bag>\n".
  452. join("",
  453. map {
  454. ref $_
  455. ? "$i <resource".
  456. ( exists $_->{attrname}
  457. ? " $_->{attrname}=\"".e($_->{attrval})."\""
  458. : ""
  459. ).
  460. ">".e($_->{resource})."</resource>\n"
  461. : "$i <rdf:li>".e($_)."</rdf:li>\n"
  462. }
  463. @{$a->{$k}->{$cond}}).
  464. "$i </rdf:bag>\n".
  465. "$i</$tag>\n";
  466. }
  467. return $out;
  468. }
  469. #
  470. # send out reference to another RDF
  471. #
  472. sub xml_reference ($$$) {
  473. my($fh, $res, $href) = @_;
  474. print $fh <<EOFEOF;
  475. <Repository rdf:resource="$res" href="$href"/>
  476. EOFEOF
  477. }
  478. #
  479. # translate attributes from %$a as generated by package2data
  480. # into XML and write to file $fh
  481. #
  482. sub xml_record ($$$) {
  483. my($fh, $a, $href) = @_;
  484. my($maj,$min,$rel,$about);
  485. $about =
  486. n($a,'Name').'-'.
  487. n($a,'Version').'-'.
  488. n($a,'Release');
  489. unless (defined $href) {
  490. # guess location from Information in Specfile
  491. if (exists $a->{'NoSource'}) {
  492. $href = "$about.nosrc.rpm";
  493. } else {
  494. $href = "$about.src.rpm";
  495. }
  496. ($maj,$min,$rel) = n($a,'Release') =~ /^(\d+)\.(\d+)\.(\d+)/;
  497. if (defined $min) {
  498. if ($maj > 1 || ($maj == 1 && $min > 0)) {
  499. # 1.1 or later
  500. if (n($a,'Distribution') =~ /\[PLUS\]/) {
  501. $href = 'PLUS/'.$href;
  502. }
  503. }
  504. if ($maj > 1 || ($maj == 1 && $min >= 0)) {
  505. # 1.0 or later
  506. if ($rel > 0) {
  507. $href = 'UPD/'.$href;
  508. }
  509. }
  510. } else {
  511. # current
  512. }
  513. }
  514. print $fh <<EOFEOF;
  515. <rdf:Description about="$about" href="$href">
  516. EOFEOF
  517. # fake Source attribute from Source\d attribtutes
  518. # XXX only default conditional
  519. $a->{'Source'} = { '' => [
  520. map {
  521. s/\Q%{name}\E/n($a,'Name')/esg;
  522. s/\Q%{version}\E/n($a,'Version')/esg;
  523. s/\Q%{release}\E/n($a,'Release')/esg;
  524. #s/.*\///;
  525. $_;
  526. }
  527. map {
  528. $a->{$_}->{''} ? @{$a->{$_}->{''}} : ()
  529. }
  530. sort {
  531. my($x) = $a =~ /^(\d*)$/;
  532. my($y) = $b =~ /^(\d*)$/;
  533. return $x <=> $y;
  534. }
  535. grep {
  536. /^Source\d*$/
  537. } keys %$a
  538. ]};
  539. delete $a->{'Source'} unless @{$a->{'Source'}->{''}};
  540. print $fh
  541. xml_tag(6, $a, 'Name'),
  542. xml_tag(6, $a, 'Version'),
  543. xml_tag(6, $a, 'Release'),
  544. xml_tag(6, $a, 'Distribution'),
  545. xml_tag(6, $a, 'Group'),
  546. xml_tag(6, $a, 'License'),
  547. xml_tag(6, $a, 'Packager'),
  548. xml_tag(6, $a, 'Summary'),
  549. xml_tag(6, $a, 'URL'),
  550. xml_tag(6, $a, 'Vendor'),
  551. xml_tag(6, $a, 'SourceRPM'),
  552. xml_tag(6, $a, 'Arch'),
  553. xml_tag(6, $a, 'Os'),
  554. xml_tag(6, $a, 'BuildHost'),
  555. xml_tag(6, $a, 'BuildSystem'),
  556. xml_tag(6, $a, 'BuildTime'),
  557. xml_tag(6, $a, 'Relocations'),
  558. xml_tag(6, $a, 'Size'),
  559. xml_tag(6, $a, 'Prefixes'),
  560. xml_tag(6, $a, 'Platform'),
  561. xml_tag(6, $a, 'SigSize'),
  562. xml_tag(6, $a, 'SigMD5'),
  563. xml_tag(6, $a, 'SigPGP'),
  564. xml_tag(6, $a, 'SigGPG'),
  565. xml_bag(6, $a, 'BuildPreReq'),
  566. xml_bag(6, $a, 'PreReq'),
  567. xml_bag(6, $a, 'Provides'),
  568. xml_bag(6, $a, 'Conflicts'),
  569. xml_bag(6, $a, 'Source'),
  570. xml_bag(6, $a, 'NoSource'),
  571. xml_bag(6, $a, 'Filenames'),
  572. xml_text(6, $a, 'Description');
  573. print $fh <<EOFEOF;
  574. </rdf:Description>
  575. EOFEOF
  576. }
  577. #####################################################################
  578. sub rpm2spec ($) {
  579. my($fn) = @_;
  580. local($SIG{'PIPE'}) = 'IGNORE';
  581. my($pipe) = new FileHandle "$R2C '$fn' |"
  582. or die "FATAL: cannot read '$fn' ($!)\n";
  583. my($buf,@hdr,$n,$m,$name,$step);
  584. my($spec);
  585. while (read($pipe,$buf,110) == 110) {
  586. @hdr = unpack('a6a8a8a8a8a8a8a8a8a8a8a8a8a8',$buf);
  587. $n = hex($hdr[12]); # filename length
  588. $m = int(($n+5)/4)*4-2; # filename size (padded)
  589. last unless read($pipe,$buf,$m) == $m;
  590. $name = substr($buf,0,$n-1);
  591. $n = hex($hdr[7]); # file length
  592. $m = int(($n+3)/4)*4; # file size (padded)
  593. if ($name !~ /.spec$/) {
  594. while ($m > 0) {
  595. $step = $m > 8192 ? 8192 : $m;
  596. last unless read($pipe,$buf,$step);
  597. $m -= length($buf);
  598. }
  599. } else {
  600. if (read($pipe,$buf,$n) == $n) {
  601. $spec = $buf;
  602. }
  603. last;
  604. }
  605. }
  606. $pipe->close;
  607. return $spec;
  608. }
  609. #####################################################################
  610. sub rpm2data ($$) {
  611. my($fn,$platform) = @_;
  612. my($q,$pipe,%a);
  613. my($t,$v);
  614. unless (defined $platform) {
  615. die "FATAL: indexing binary package '$fn' requires -p option\n";
  616. }
  617. $q = <<EOFEOF;
  618. Name %{Name}
  619. Version %{Version}
  620. Release %{Release}
  621. URL %{URL}
  622. Summary %{Summary}
  623. Copyright %{Copyright}
  624. License %{License}
  625. Distribution %{Distribution}
  626. Vendor %{Vendor}
  627. Group %{Group}
  628. Packager %{Packager}
  629. Prefixes %{Prefixes}
  630. BuildHost %{BuildHost}
  631. BuildTime %{BuildTime}
  632. Arch %{Arch}
  633. Os %{Os}
  634. Size %{Size}
  635. SigSize %{SigSize}
  636. SigMD5 %{SigMD5}
  637. SigPGP %{SigPGP}
  638. SigGPG %{SigGPG}
  639. SourceRPM %{SourceRPM}
  640. [Patch %{Patch}
  641. ]
  642. [Source %{Source}
  643. ]
  644. [Filenames %{Filenames}
  645. ]
  646. [Conflicts %{CONFLICTNAME} %|CONFLICTFLAGS?{%{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}}:{}|
  647. ]
  648. [PreReq %{REQUIRENAME} %|REQUIREFLAGS?{%{REQUIREFLAGS:depflags} %{REQUIREVERSION}}:{}|
  649. ]
  650. [Provides %{PROVIDENAME} %|PROVIDEFLAGS?{%{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}}:{}|
  651. ]
  652. Description %{Description}
  653. EOFEOF
  654. $pipe = new FileHandle "$RPM -qp --qf '$q' '$fn' |"
  655. or die "FATAL: cannot read '$fn' ($!)\n";
  656. while (<$pipe>) {
  657. if (/^(\S+)\s+(.*?)\s*$/) {
  658. $t = $1;
  659. $v = $2;
  660. } elsif (/^(\s+.+?)\s*$/) {
  661. next unless defined $t;
  662. $v = $1;
  663. } else {
  664. $t = undef;
  665. next;
  666. }
  667. if (exists $a{$t}) {
  668. $a{$t} .= "\n$v";
  669. } else {
  670. $a{$t} = $v;
  671. }
  672. }
  673. $pipe->close;
  674. %a = map { $_ => $a{$_} }
  675. grep { $a{$_} ne '(none)' }
  676. keys %a;
  677. if ($a{'Relocations'} eq '(non relocatable)') {
  678. delete $a{'Relocations'};
  679. }
  680. if ($a{'SigMD5'} eq '(unknown type)') {
  681. delete $a{'SigMD5'};
  682. }
  683. if (defined $platform) {
  684. $a{'Platform'} = $platform;
  685. }
  686. $a{'Description'} = [ $a{'Description'} ];
  687. foreach ('Conflicts', 'PreReq', 'Provides') {
  688. $a{$_} = [
  689. map { make_resource($_) }
  690. grep { !/^rpmlib\(/ }
  691. split(/\n+/, $a{$_})
  692. ];
  693. }
  694. return { map {
  695. $_ => { '' => (ref $a{$_} ? $a{$_} : [ split(/\n+/, $a{$_}) ]) }
  696. } keys %a };
  697. }
  698. #####################################################################
  699. sub getindex ($) {
  700. my($dir) = @_;
  701. my(@idx) = sort { -M $a <=> -M $b; }
  702. grep { -f $_ }
  703. ( <$dir/00INDEX.rdf>, <$dir/00INDEX.rdf.*> );
  704. return unless @idx;
  705. return $idx[0];
  706. }
  707. sub list_specdir ($) {
  708. my($dir) = @_;
  709. my($dh,$d,$path);
  710. my(@list);
  711. $dh = new DirHandle($dir);
  712. while ($d = $dh->read) {
  713. next if $d =~ /^\./;
  714. $path = "$dir/$d/$d.spec";
  715. push @list, $path if -f $path;
  716. }
  717. return \@list;
  718. }
  719. sub list_rpmdir ($) {
  720. my($dir) = @_;
  721. my($dh,$d,$path);
  722. my(@list,$idx,$sub);
  723. $dh = new DirHandle($dir);
  724. while ($d = $dh->read) {
  725. next if $d =~ /^\./;
  726. $path = "$dir/$d";
  727. if (-d $path) {
  728. $idx = getindex($path);
  729. if (defined $idx) {
  730. push @list, $idx;
  731. } else {
  732. $sub = list_rpmdir($path);
  733. push @list, @$sub;
  734. undef $sub;
  735. }
  736. } else {
  737. next unless $d =~ /\.rpm$/ && -f $path;
  738. push @list, $path;
  739. }
  740. }
  741. return \@list;
  742. }
  743. #####################################################################
  744. sub readfile ($) {
  745. my($fn) = @_;
  746. my($fh) = new FileHandle "< $fn"
  747. or die "FATAL: cannot read '$fn' ($!)\n";
  748. my(@l) = <$fh>;
  749. $fh->close;
  750. return join('',@l);
  751. }
  752. sub relpath ($$) {
  753. my($prefix,$path) = @_;
  754. $path =~ s/^\Q$prefix\E\///s;
  755. return $path;
  756. }
  757. sub dirname ($) {
  758. my($path) = @_;
  759. $path =~ s/\/[^\/]*$//s;
  760. return $path.'/';
  761. }
  762. sub getresource ($) {
  763. my($fn) = @_;
  764. my($fh, $buf);
  765. if ($fn =~ /\.bz2$/) {
  766. $fh = new FileHandle "$BZ -dc $fn |"
  767. or die "FATAL: cannot read '$fn' ($!)\n";
  768. } else {
  769. $fh = new FileHandle "< $fn"
  770. or die "FATAL: cannot read '$fn' ($!)\n";
  771. }
  772. $fh->read($buf, 1024);
  773. $fh->close;
  774. if ($buf =~ /<Repository.*?rdf:resource="([^"]+)"/) {
  775. return $1;
  776. }
  777. return undef;
  778. }
  779. #####################################################################
  780. sub write_index ($$$$$$) {
  781. my($fh,$prefix,$resource,$platform,$list,$cache) = @_;
  782. my($a,$h,$r,$spec);
  783. my($mtime);
  784. foreach (@$list) {
  785. $a = undef;
  786. $h = undef;
  787. $r = undef;
  788. if (/\.spec$/) {
  789. $spec = readfile($_);
  790. $a = spec2data($spec);
  791. } elsif (/([^\/]+\.(?:no)?src\.rpm)$/) {
  792. $h = relpath($prefix, $_);
  793. if ($cache) {
  794. $mtime = (stat $_)[9];
  795. if (exists $cache->{"M$_"} &&
  796. $cache->{"M$_"} == $mtime) {
  797. $spec = $cache->{"S$_"};
  798. } else {
  799. $spec = rpm2spec($_);
  800. $cache->{"S$_"} = $spec;
  801. $cache->{"M$_"} = $mtime;
  802. }
  803. } else {
  804. $spec = rpm2spec($_);
  805. }
  806. $a = spec2data($spec);
  807. } elsif (/([^\/]+\.rpm)$/) {
  808. $h = relpath($prefix, $_);
  809. $a = rpm2data($_, $platform);
  810. } elsif (/([^\/]+\.rdf[^\/]*)$/) {
  811. $h = relpath($prefix, $_);
  812. $r = getresource($_) || $resource.dirname($h);
  813. }
  814. if ($a) {
  815. xml_record($fh, $a, $h);
  816. } elsif ($r) {
  817. xml_reference($fh, $r, $h);
  818. } else {
  819. warn "ERROR: cannot process $_\n";
  820. }
  821. }
  822. }
  823. #####################################################################
  824. my($prefix,$list,$fh,%cache,$tmpo);
  825. if ($#ARGV < 0) {
  826. print "openpkg:index:USAGE: $0 [-r resource] [-p platform] [-C cache.db] [-o index.rdf] [-c] [-i] dir ...\n";
  827. exit(1);
  828. }
  829. if ($opt_C) {
  830. eval {
  831. require DB_File;
  832. };
  833. if ($@) {
  834. die "Sorry. The -C option requires an installed DB_File perl module.\n";
  835. }
  836. tie %cache, 'DB_File', $opt_C, O_CREAT|O_RDWR, 0666, $DB_File::DB_HASH
  837. or die "FATAL: cannot tie cache '$opt_C' ($!)\n";
  838. }
  839. $opt_r = 'OpenPKG-CURRENT/Source/' unless defined $opt_r;
  840. if (defined $opt_o) {
  841. $tmpo = $opt_o . '.tmp';
  842. if ($opt_c) {
  843. $fh = new FileHandle "| $BZ -c > '$tmpo'"
  844. or die "FATAL: cannot write '$tmpo' ($!)\n";
  845. } else {
  846. $fh = new FileHandle "> $tmpo"
  847. or die "FATAL: cannot write '$tmpo' ($!)\n";
  848. }
  849. } else {
  850. if ($opt_c) {
  851. $fh = new FileHandle "| $BZ -c"
  852. or die "FATAL: cannot write to stdout ($!)\n";
  853. } else {
  854. $fh = new FileHandle ">&=1"
  855. or die "FATAL: cannot write to stdout ($!)\n";
  856. }
  857. }
  858. xml_head($fh, $opt_r);
  859. foreach $prefix (@ARGV) {
  860. if (-d $prefix) {
  861. if ($opt_i) {
  862. $list = list_rpmdir($prefix);
  863. } else {
  864. $list = list_specdir($prefix);
  865. }
  866. } else {
  867. $list = [ $prefix ];
  868. $prefix = dirname($prefix);
  869. }
  870. write_index($fh, $prefix, $opt_r, $opt_p, $list, $opt_C ? \%cache : undef);
  871. }
  872. xml_foot($fh);
  873. $fh->close
  874. or die "FATAL: write error on output ($!)\n";
  875. if (defined $tmpo) {
  876. rename $tmpo,$opt_o
  877. or die "FATAL: cannot rename $tmpo to $opt_o ($!)\n";
  878. }