rpmx.pl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. ##
  2. ## rpmx -- RPM eXtension (Perl program)
  3. ## Copyright (c) 2001 Ralf S. Engelschall <rse@engelschall.com>
  4. ##
  5. ## list all files in the hierarchy which are not listed in RPM database
  6. ## $ rpm --stowaway
  7. ##
  8. ## generate repository information (on server only)
  9. ## $ rpm --makeindex <pkgdir>
  10. ##
  11. ## update local database with latest repository information
  12. ## $ rpm --update
  13. ##
  14. ## update the package sources by downloading missing files
  15. ## $ rpm --fetch <spec-file>
  16. ##
  17. ## query repository information
  18. ## $ rpm -rqa
  19. ## $ rpm -rqai
  20. ## $ rpm -rqal
  21. ## $ rpm -rqalv
  22. ## $ rpm -rqi package
  23. ## $ rpm -rql package
  24. ## $ rpm -rqlv package
  25. ##
  26. ## update already installed packages
  27. ## $ rpm -Uvhs '*'
  28. ##
  29. ## install/update a particular package and its dependencies
  30. ## $ rpm -Uvhs file-pattern
  31. ##
  32. ## erase a particular package and its dependencies
  33. ## $ rpm -es file-pattern
  34. ##
  35. require 5.000;
  36. use IO::File;
  37. use IO::Handle;
  38. use Data::Dumper;
  39. $|++;
  40. ## ______________________________________________________________________
  41. ##
  42. ## Determine Configuration and Run-Time Information
  43. ## ______________________________________________________________________
  44. ##
  45. my $CFG = {
  46. 'PRG' => {},
  47. 'RC' => {},
  48. 'OPT' => [],
  49. 'OPT_OPT' => {},
  50. 'OPT_ARG' => {},
  51. 'ARG' => [],
  52. };
  53. # determine path to executables
  54. my $prefix = shift(@ARGV);
  55. $CFG->{PRG}->{"rpm"} = $prefix."/bin/rpm";
  56. $CFG->{PRG}->{"curl"} = $prefix."/lib/openpkg/curl";
  57. $CFG->{PRG}->{"bzip2"} = $prefix."/lib/openpkg/bzip2";
  58. $CFG->{PRG}->{"gzip"} = $prefix."/lib/openpkg/gzip";
  59. # determine a few RPM rc-file configuration variables
  60. my $var;
  61. my $vars = '';
  62. foreach $var (qw(
  63. _dbpath _rpmdir _srcrpmdir _tmppath
  64. _target
  65. l_prefix
  66. l_repo_cache
  67. l_repo_0 l_repo_1 l_repo_2 l_repo_3 l_repo_4
  68. l_repo_5 l_repo_6 l_repo_7 l_repo_8 l_repo_9
  69. )) {
  70. $vars .= "${var}=\%{${var}};";
  71. }
  72. my @assign = split(/;/, `$CFG->{PRG}->{"rpm"} --eval '$vars'`);
  73. foreach $assign (@assign) {
  74. if ($assign =~ m|^(\S+)=(.*)$|s) {
  75. $CFG->{RC}->{$1} = $2;
  76. }
  77. }
  78. # parse argument line
  79. #foreach my $arg (@ARGV) {
  80. # print "<$arg>";
  81. #}
  82. #print "\n";
  83. my $op = 'pass';
  84. my $isopt = 1;
  85. my $optname = '';
  86. my $arg;
  87. foreach $arg (@ARGV) {
  88. if ($arg =~ m/^--(stowaway|makeindex|update|fetch|repo|smart)$/) {
  89. $op = $1;
  90. next;
  91. }
  92. if ($arg eq '--') {
  93. $isopt = 0;
  94. next;
  95. }
  96. if ($isopt) {
  97. push(@{$CFG->{OPT}}, $arg);
  98. if ($arg =~ m|^-.|) {
  99. $optname = $arg;
  100. $CFG->{OPT_OPT}->{$optname} = 1;
  101. $CFG->{OPT_ARG}->{$optname} = '--';
  102. }
  103. else {
  104. $CFG->{OPT_ARG}->{$optname} = $arg;
  105. }
  106. }
  107. else {
  108. push(@{$CFG->{ARG}}, $arg);
  109. }
  110. }
  111. #print Data::Dumper->Dump([$CFG]);
  112. if ($op eq 'pass') {
  113. # exec $CFG->{PRG}->{"rpm"} (@{$CFG->{OPT}}, @{$CFG->{ARG}});
  114. }
  115. else {
  116. my $rc;
  117. #eval "\$rc = \&op_${op}(\$CFG);";
  118. if ($op eq 'stowaway') {
  119. $rc = &op_stowaway($CFG);
  120. }
  121. elsif ($op eq 'makeindex') {
  122. $rc = &op_makeindex($CFG);
  123. }
  124. elsif ($op eq 'update') {
  125. $rc = &op_update($CFG);
  126. }
  127. elsif ($op eq 'fetch') {
  128. $rc = &op_fetch($CFG);
  129. }
  130. elsif ($op eq 'repo') {
  131. $rc = &op_repo($CFG);
  132. }
  133. elsif ($op eq 'smart') {
  134. $rc = &op_smart($CFG);
  135. }
  136. exit($rc);
  137. }
  138. ## ______________________________________________________________________
  139. ##
  140. ## Database Handling
  141. ## ______________________________________________________________________
  142. ##
  143. # read database file from disk
  144. sub db_load {
  145. my ($dbfile) = @_;
  146. if (not -f $dbfile) {
  147. my $DB = {};
  148. return $DB;
  149. }
  150. my $fp = new IO::File ("<$dbfile") || die "cannot read from Database file `$dbfile'";
  151. my $db = '';
  152. my $buf;
  153. $db .= $buf while ($fp->read($buf, 128*1024));
  154. $fp->close;
  155. my $DB = {};
  156. $db =~ s|<rpm\s+(\S+)>\n(.+?)</rpm>|&load_rpm($DB, $2, $1), ''|isge;
  157. sub load_rpm {
  158. my ($DB, $db, $rpm) = @_;
  159. $DB->{$rpm} = {};
  160. $db =~ s/<(repos|info|files|requires)>\n(.+?)<\/\1>/$DB->{$rpm}->{uc($1)} = $2, ''/isge;
  161. }
  162. return $DB;
  163. }
  164. # write database file to disk
  165. sub db_store {
  166. my ($dbfile, $DB) = @_;
  167. my $fp = new IO::File (">$dbfile") || die "cannot store to Database file `$dbfile'";
  168. my $rpm;
  169. foreach $rpm (sort(keys(%{$DB}))) {
  170. print $fp "<rpm $rpm>\n" .
  171. "<repos>\n" .
  172. $DB->{$rpm}->{REPOS} .
  173. "</repos>\n" .
  174. "<requires>\n" .
  175. $DB->{$rpm}->{REQUIRES} .
  176. "</requires>\n" .
  177. "<info>\n" .
  178. $DB->{$rpm}->{INFO} .
  179. "</info>\n" .
  180. "<files>\n" .
  181. $DB->{$rpm}->{FILES} .
  182. "</files>\n" .
  183. "</rpm>\n";
  184. }
  185. $fp->close;
  186. return;
  187. }
  188. ## ______________________________________________________________________
  189. ##
  190. ## Fetch an URL
  191. ## ______________________________________________________________________
  192. ##
  193. sub fetch_url {
  194. my ($CFG, $src, $dst) = @_;
  195. # make sure file URLs have a fully-qualified scheme.
  196. if ($src =~ m|^/.+|) {
  197. $src = "file://$src"
  198. }
  199. # make sure only schemes curl(1) supports are used.
  200. if ($src !~ m;^(file|http|ftp)://.+;) {
  201. return "invalid URL - only file, http and ftp schemes supported";
  202. }
  203. # try to fetch the URL
  204. unlink("$dst.err");
  205. unlink("$dst.hdr");
  206. $rc = system($CFG->{PRG}->{"curl"}.
  207. " --location" .
  208. " --max-time 120" .
  209. " --stderr $dst.err" .
  210. " --dump-header $dst.hdr" .
  211. " --output $dst '$src'");
  212. # check whether command failed
  213. if ($rc != 0) {
  214. open(FP, "<$dst.err");
  215. my $err = <FP>;
  216. close(FP);
  217. $err =~ s|\n$||;
  218. return $err;
  219. }
  220. # check whether remote sites failed
  221. if (-s "$dst.hdr") {
  222. open(FP, "<$dst.hdr");
  223. my $response = <FP>;
  224. close(FP);
  225. if ($response =~ m|^HTTP/[\d.]+\s+(\d+)|) {
  226. if ($1 ne 200) {
  227. $response =~ s|\n$||;
  228. return $response;
  229. }
  230. }
  231. }
  232. # cleanup
  233. unlink("$dst.err");
  234. unlink("$dst.hdr");
  235. return '';
  236. }
  237. ## ______________________________________________________________________
  238. ##
  239. ## List all files which are not known to RPM
  240. ## ______________________________________________________________________
  241. ##
  242. sub op_stowaway {
  243. my ($CFG) = @_;
  244. my $file;
  245. my $prefix = $CFG->{RC}->{"l_prefix"};
  246. my $rpm = $CFG->{PRG}->{"rpm"};
  247. print "OpenPKG Hierarchy $prefix\n";
  248. my @known = `$rpm -qla`;
  249. my %known = ();
  250. foreach $file (@known) {
  251. $file =~ s|\n$||s;
  252. $known{$file} = 1;
  253. }
  254. my @exist = `cd $prefix && find . -print 2>/dev/null`;
  255. foreach $file (sort(@exist)) {
  256. $file =~ s|^\./|$prefix/|s;
  257. $file =~ s|\n$||s;
  258. next if ($file eq '.');
  259. if (not $known{$file}) {
  260. next if ($file =~ m|^$prefix/RPM/?|);
  261. print "$file\n";
  262. }
  263. }
  264. return 0;
  265. }
  266. ## ______________________________________________________________________
  267. ##
  268. ## Generate Repository Index
  269. ## ______________________________________________________________________
  270. ##
  271. sub op_makeindex {
  272. my ($CFG) = @_;
  273. if ($#{$CFG->{ARG}} ne 0) {
  274. print STDERR "rpm: option --makeindex requires an argument\n";
  275. return 1;
  276. }
  277. my $dir = $CFG->{ARG}->[0];
  278. if (not -d $dir) {
  279. print STDERR "rpm: --makeindex argument `$dir' is not a directory\n";
  280. return 1;
  281. }
  282. my $index = '';
  283. my $file;
  284. my $rpm = $CFG->{PRG}->{"rpm"};
  285. foreach $file (sort(glob("$dir/*.rpm"))) {
  286. $file =~ s|^$dir/*||;
  287. $index .= "<rpm $file>\n";
  288. $index .= "<requires>\n";
  289. $index .= `$rpm -qp --qf '[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]' $dir/$file`;
  290. $index .= "</requires>\n";
  291. $index .= "<info>\n";
  292. $index .= `$rpm -qpi $dir/$file`;
  293. $index .= "</info>\n";
  294. $index .= "<files>\n";
  295. $index .= `$rpm -qplv $dir/$file`;
  296. $index .= "</files>\n";
  297. $index .= "</rpm>\n";
  298. }
  299. open(FP, "|".$CFG->{PRG}->{"bzip2"}.">$dir/INDEX.bz2");
  300. print FP $index;
  301. close(FP);
  302. return 0;
  303. }
  304. ## ______________________________________________________________________
  305. ##
  306. ## Fetch Repository Indices
  307. ## ______________________________________________________________________
  308. ##
  309. sub op_update {
  310. my ($CFG) = @_;
  311. my $DB = {};
  312. my $tmpfile = $CFG->{RC}->{_tmppath}."/rpm.update.tmp.".$$;
  313. for ($i = 9; $i >= 0; $i--) {
  314. my $repo = $CFG->{RC}->{"l_repo_$i"};
  315. next if (not defined($repo) or $repo eq '' or $repo eq '-');
  316. $repo =~ s|([^/])$|$1/|;
  317. my $src = $repo."INDEX.bz2";
  318. my $prefix = $src;
  319. $prefix = substr($prefix, 0, 37)."..." if (length($prefix) > 40);
  320. printf(STDOUT "%-40s ", $prefix);
  321. if (($err = &fetch_url($CFG, $src, $tmpfile))) {
  322. $err = substr($err, 0, 37)."..." if (length($err) > 40);
  323. print STDOUT "FAILED: $err\n";
  324. next;
  325. }
  326. my $size = (stat($tmpfile))[7];
  327. print STDOUT "OK: $size bytes\n";
  328. my $fh = new IO::Handle;
  329. open($fh, $CFG->{PRG}->{"bzip2"}." -d -c $tmpfile|");
  330. my $db = '';
  331. my $buf;
  332. $db .= $buf while ($fh->read($buf, 128*1024));
  333. $fh->close;
  334. unlink($tmpfile);
  335. $db =~ s|<rpm\s+(\S+)>\n(.+?)</rpm>|&load_rpm2($DB, $repo, $2, $1), ''|isge;
  336. sub load_rpm2 {
  337. my ($DB, $repo, $db, $rpm) = @_;
  338. my $target = $CFG->{RC}->{_target};
  339. return if ($rpm !~ m|\.src\.rpm$| and $rpm !~ m|\.$target\.rpm$|);
  340. my $repos = '';
  341. if (defined($DB->{$rpm})) {
  342. $repos = $DB->{$rpm}->{REPOS};
  343. }
  344. $repos = $repo."\n".$repos;
  345. $DB->{$rpm} = {};
  346. $DB->{$rpm}->{REPOS} = $repos;
  347. $db =~ s/<(info|files|requires)>\n(.+?)<\/\1>/$DB->{$rpm}->{uc($1)} = $2, ''/isge;
  348. }
  349. }
  350. my $dbfile = $CFG->{RC}->{_dbpath}."/Repository";
  351. &db_store($dbfile, $DB);
  352. return 0;
  353. }
  354. ## ______________________________________________________________________
  355. ##
  356. ## Repository Queries
  357. ## ______________________________________________________________________
  358. ##
  359. sub op_repo {
  360. my ($CFG) = @_;
  361. # argument line consistency check
  362. if (not defined($CFG->{OPT_OPT}->{"-q"})) {
  363. print STDERR "rpm: option --repo always requires option -q\n";
  364. return 1;
  365. }
  366. my @optok = (qw(--rcfile -q --all -i --list --verbose));
  367. my $opt;
  368. my $check;
  369. foreach $opt (keys(%{$CFG->{OPT_OPT}})) {
  370. my $ok = 0;
  371. foreach $check (@optok) {
  372. if ($check eq $opt) {
  373. $ok = 1;
  374. last;
  375. }
  376. }
  377. if (not $ok) {
  378. print STDERR "rpm: option $opt not supported in conjunction with option --repo\n";
  379. return 1;
  380. }
  381. }
  382. # load database
  383. my $dbfile = $CFG->{RC}->{_dbpath}."/Repository";
  384. if (not -f $dbfile) {
  385. print STDERR "rpm: option --repo requires local repository index.\n";
  386. print STDERR "rpm: run \"rpm --update\", first.\n";
  387. return 1;
  388. }
  389. my $DB = &db_load($dbfile);
  390. # perform query operation
  391. my $target = $CFG->{RC}->{_target};
  392. my $rpm;
  393. my $rpms = {};
  394. foreach $rpm (sort(keys(%{$DB}))) {
  395. my $name = $rpm;
  396. $name =~ s|\.src\.rpm$||;
  397. $name =~ s|\.${target}\.rpm$||;
  398. next if (defined($rpms->{$name}));
  399. $rpms->{$name} = 1;
  400. if ((defined($CFG->{ARG}->[0]) and $rpm =~ m|^$CFG->{ARG}->[0]|)
  401. or defined($CFG->{OPT_OPT}->{"--all"})) {
  402. if (not defined($CFG->{OPT_OPT}->{"--list"}) and not defined($CFG->{OPT_OPT}->{"-i"})) {
  403. print "$name\n";
  404. }
  405. if (defined($CFG->{OPT_OPT}->{"-i"})) {
  406. print $DB->{$rpm}->{INFO};
  407. }
  408. if (defined($CFG->{OPT_OPT}->{"--list"})) {
  409. my $files = $DB->{$rpm}->{FILES};
  410. if (not defined($CFG->{OPT_OPT}->{"--verbose"})) {
  411. if (not ($files =~ s|^.*\s+(\S+\s+->\s+\S+)\s*$|$1|mg)) {
  412. $files =~ s|^.*\s+(\S+)\s*$|$1|mg;
  413. }
  414. }
  415. print "$files";
  416. }
  417. }
  418. }
  419. }
  420. ## ______________________________________________________________________
  421. ##
  422. ## Fetch Operation
  423. ## ______________________________________________________________________
  424. ##
  425. sub op_fetch {
  426. my ($CFG) = @_;
  427. if ($#{$CFG->{ARG}} ne 0) {
  428. print STDERR "rpm: option --fetch requires an argument\n";
  429. return 1;
  430. }
  431. my $spec = $CFG->{ARG}->[0];
  432. if (not -f $spec) {
  433. print STDERR "rpm: spec file `$spec' not found\n";
  434. return 1;
  435. }
  436. # determine package name and source directory
  437. my $name = $spec;
  438. $name =~ s|\.[^.]+$||;
  439. $name =~ s|^.+/([^/]+)$|$1|;
  440. my $srcdir = `$CFG->{PRG}->{"rpm"} --define 'name $name' --eval '%_sourcedir'`;
  441. $srcdir =~ s|\n+$||s;
  442. # parse spec file
  443. my $DEF = {};
  444. my $SRC = {};
  445. open(SPEC, "<$spec");
  446. while (<SPEC>) {
  447. s|\n+$||s;
  448. if (m/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.+?)\s*$/) {
  449. $DEF->{lc($1)} = $2;
  450. }
  451. if (m/^%define\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+(.+?)\s*$/) {
  452. $DEF->{lc($1)} = $2;
  453. }
  454. if (m/^((Source|Patch)[0-9]+?):\s*(.+)\s*$/i) {
  455. my ($srcid, $srcurl) = ($1, $3);
  456. 1 while ($srcurl =~ s|%{?([a-zA-Z_][a-zA-Z0-9_]*)}?|$DEF->{lc($1)}|sge);
  457. my $srcfile = $srcurl;
  458. $srcfile =~ s|^.*/([^/]+)$|$1|;
  459. my $file = $srcfile;
  460. $file = substr($file, 0, 40) if (length($file) > 40);
  461. printf(STDERR "%-9s %-40s ", "$srcid:", $file);
  462. if (-f "$srcdir/$srcfile") {
  463. my $size = (stat("$srcdir/$srcfile"))[7];
  464. if ($size > 1024*1024) {
  465. $size = sprintf("%.1fMB", $size / (1024*1024));
  466. }
  467. elsif ($size > 1024) {
  468. $size = sprintf("%.0fKB", $size / 1024);
  469. }
  470. else {
  471. $size = sprintf("%d", $size);
  472. }
  473. print STDERR "...OK [$size]\n";
  474. }
  475. else {
  476. print STDERR "...MISSING\n";
  477. print STDERR "rpm: Fetching $srcurl\n";
  478. my $cmd = $CFG->{PRG}->{"curl"}.
  479. " --location" .
  480. " --output $srcdir/$srcfile" .
  481. " $srcurl";
  482. my $rc = system($cmd);
  483. if ($rc != 0) {
  484. print STDERR "rpm: Failed to fetch source file `$srcfile'\n";
  485. return 1;
  486. }
  487. }
  488. }
  489. }
  490. close(SPEC);
  491. return 0;
  492. }
  493. ## ______________________________________________________________________
  494. ##
  495. ## Smart Operations
  496. ## ______________________________________________________________________
  497. ##
  498. sub op_smart {
  499. my ($CFG) = @_;
  500. if ($#{$CFG->{ARG}} ne 0) {
  501. print STDERR "rpm: option --smart requires one argument\n";
  502. return 1;
  503. }
  504. my $package = $CFG->{ARG}->[0];
  505. # load database
  506. my $dbfile = $CFG->{RC}->{_dbpath}."/Repository";
  507. if (not -f $dbfile) {
  508. print STDERR "rpm: option --smart requires local repository index.\n";
  509. print STDERR "rpm: run \"rpm --update\", first.\n";
  510. return 1;
  511. }
  512. my $DB = &db_load($dbfile);
  513. my $deps = [];
  514. &find_deps($CFG, $DB, $deps, $package, '=', 'ANY');
  515. sub find_deps {
  516. my ($CFG, $DB, $deps, $pkg, $op, $ver) = @_;
  517. push(@{$deps}, "$pkg $op $ver");
  518. my $rpm;
  519. foreach $rpm (keys(%{$DB})) {
  520. if (&match_rpm($CFG, $rpm, $pkg, $op, $ver)) {
  521. my $req;
  522. foreach $req (split(/\n/, $DB->{$rpm}->{REQUIRES})) {
  523. $req =~ s|^\s+||sg;
  524. $req =~ s|\s+$||sg;
  525. if ($req =~ m|^OpenPKG$| or
  526. $req =~ m|^rpmlib\(.+\)|) {
  527. next;
  528. }
  529. if ($req =~ m|^(\S+)$|) {
  530. $req .= " = ANY";
  531. }
  532. if ($req =~ m|^(\S+)\s+([=><]+)\s+(\S+)$|) {
  533. &find_deps($CFG, $DB, $deps, $1, $2, $3);
  534. }
  535. }
  536. }
  537. }
  538. }
  539. sub match_rpm {
  540. my ($CFG, $rpm, $name, $op, $ver) = @_;
  541. my $rc = 0;
  542. my $target = $CFG->{RC}->{_target};
  543. $rpm =~ s|\.src\.rpm$||;
  544. $rpm =~ s|\.${target}\.rpm$||;
  545. if ($rpm =~ m|^(\S+)-([^-]+-[^-]+)$|) {
  546. my ($tname, $tver) = ($1, $2);
  547. if ($name eq $tname and &match_version($ver, $op, $tver)) {
  548. $rc = 1;
  549. }
  550. }
  551. return $rc;
  552. }
  553. sub match_version {
  554. my ($ver, $op, $tver) = @_;
  555. return 1;
  556. }
  557. my $dep;
  558. foreach $dep (@{$deps}) {
  559. print "$dep\n";
  560. }
  561. }