install 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. #!/usr/bin/perl
  2. # -*-perl-*-
  3. #
  4. # Copyright © 1996 Andy Guy <awpguy@acs.ucalgary.ca>
  5. # Copyright © 1998 Martin Schulze <joey@infodrom.north.de>
  6. # Copyright © 1999, 2009 Raphaël Hertzog <hertzog@debian.org>
  7. #
  8. # This program has been distributed under the terms of the GNU GPL.
  9. use strict;
  10. use warnings;
  11. use vars qw(%config $ftp);
  12. #use diagnostics;
  13. use lib '/usr/lib/perl5/Debian';
  14. use lib '/usr/share/perl5/Debian';
  15. eval q{
  16. use Net::FTP;
  17. use File::Path;
  18. use File::Basename;
  19. use File::Find;
  20. use Data::Dumper;
  21. };
  22. if ($@) {
  23. print STDERR "Please install the 'perl' package if you want to use the\n" .
  24. "FTP access method of dselect.\n\n";
  25. exit 1;
  26. }
  27. use Dselect::Ftp;
  28. # exit value
  29. my $exit = 0;
  30. # deal with arguments
  31. my $vardir = $ARGV[0];
  32. my $method = $ARGV[1];
  33. my $option = $ARGV[2];
  34. if ($option eq "manual" ) {
  35. print "manual mode not supported yet\n";
  36. exit 1;
  37. }
  38. #print "vardir: $vardir, method: $method, option: $option\n";
  39. my $methdir = "$vardir/methods/ftp";
  40. # get info from control file
  41. read_config("$methdir/vars");
  42. chdir "$methdir";
  43. mkpath(["$methdir/$config{'dldir'}"], 0, 0755);
  44. #Read md5sums already calculated
  45. my %md5sums;
  46. if (-f "$methdir/md5sums") {
  47. local $/;
  48. open(MD5SUMS, "$methdir/md5sums") ||
  49. die "Couldn't read file $methdir/md5sums";
  50. my $code = <MD5SUMS>;
  51. close MD5SUMS;
  52. use vars qw($VAL1);
  53. my $res = eval $code;
  54. if ($@) {
  55. die "Couldn't eval $methdir/md5sums content: $@\n";
  56. }
  57. if (ref($res)) { %md5sums = %{$res} }
  58. }
  59. # get a block
  60. # returns a ref to a hash containing flds->fld contents
  61. # white space from the ends of lines is removed and newlines added
  62. # (no trailing newline).
  63. # die's if something unexpected happens
  64. sub getblk {
  65. my $fh = shift;
  66. my %flds;
  67. my $fld;
  68. while (<$fh>) {
  69. if ( ! /^$/ ) {
  70. FLDLOOP: while (1) {
  71. if ( /^(\S+):\s*(.*)\s*$/ ) {
  72. $fld = lc($1);
  73. $flds{$fld} = $2;
  74. while (<$fh>) {
  75. if ( /^$/ ) {
  76. return %flds;
  77. } elsif ( /^(\s.*)$/ ) {
  78. $flds{$fld} = $flds{$fld} . "\n" . $1;
  79. } else {
  80. next FLDLOOP;
  81. }
  82. }
  83. return %flds;
  84. } else {
  85. die "Expected a start of field line, but got:\n$_";
  86. }
  87. }
  88. }
  89. }
  90. return %flds;
  91. }
  92. # process status file
  93. # create curpkgs hash with version (no version implies not currently installed)
  94. # of packages we want
  95. print "Processing status file...\n";
  96. my %curpkgs;
  97. sub procstatus {
  98. my (%flds, $fld);
  99. open (STATUS, "$vardir/status") or die "Could not open status file";
  100. while (%flds = getblk(\*STATUS), %flds) {
  101. if($flds{'status'} =~ /^install ok/) {
  102. my $cs = (split(/ /, $flds{'status'}))[2];
  103. if(($cs eq "not-installed") ||
  104. ($cs eq "half-installed") ||
  105. ($cs eq "config-files")) {
  106. $curpkgs{$flds{'package'}} = "";
  107. } else {
  108. $curpkgs{$flds{'package'}} = $flds{'version'};
  109. }
  110. }
  111. }
  112. close(STATUS);
  113. }
  114. procstatus();
  115. sub dcmpvers {
  116. my($a, $p, $b) = @_;
  117. my ($r);
  118. $r = system("dpkg", "--compare-versions", "$a", "$p", "$b");
  119. $r = $r/256;
  120. if ($r == 0) {
  121. return 1;
  122. } elsif ($r == 1) {
  123. return 0;
  124. }
  125. die "dpkg --compare-versions $a $p $b - failed with $r";
  126. }
  127. # process package files, looking for packages to install
  128. # create a hash of these packages pkgname => version, filenames...
  129. # filename => md5sum, size
  130. # for all packages
  131. my %pkgs;
  132. my %pkgfiles;
  133. sub procpkgfile {
  134. my $fn = shift;
  135. my $site = shift;
  136. my $dist = shift;
  137. my(@files,@sizes,@md5sums,$pkg,$ver,$fl,$nfs,$fld);
  138. my(%flds);
  139. open(PKGFILE, "$fn") or die "Could not open package file $fn";
  140. while(%flds = getblk(\*PKGFILE), %flds) {
  141. $pkg = $flds{'package'};
  142. $ver = $curpkgs{$pkg};
  143. @files = split(/[\s\n]+/, $flds{'filename'});
  144. @sizes = split(/[\s\n]+/, $flds{'size'});
  145. @md5sums = split(/[\s\n]+/, $flds{'md5sum'});
  146. if ( defined($ver) && ( ($ver eq "") || dcmpvers( $ver, "lt", $flds{'version'} ) )) {
  147. $pkgs{$pkg} = [ $flds{'version'}, [ @files ], $site ];
  148. $curpkgs{$pkg} = $flds{'version'};
  149. }
  150. $nfs = scalar(@files);
  151. if(($nfs != scalar(@sizes)) || ($nfs != scalar(@md5sums)) ) {
  152. print "Different number of filenames, sizes and md5sums for $flds{'package'}\n";
  153. } else {
  154. my $i = 0;
  155. foreach $fl (@files) {
  156. $pkgfiles{$fl} = [ $md5sums[$i], $sizes[$i], $site, $dist ];
  157. $i++;
  158. }
  159. }
  160. }
  161. }
  162. print "\nProcessing Package files...\n";
  163. my ($dist,$site,$fn,$i,$j);
  164. $i = 0;
  165. foreach $site (@{$config{'site'}}) {
  166. $j = 0;
  167. foreach $dist (@{$site->[2]}) {
  168. $fn = $dist;
  169. $fn =~ tr#/#_#;
  170. $fn = "Packages.$site->[0].$fn";
  171. if (-f $fn) {
  172. print " $site->[0] $dist...\n";
  173. procpkgfile($fn,$i,$j);
  174. } else {
  175. print "Could not find packages file for $site->[0] $dist distribution (re-run Update)\n"
  176. }
  177. $j++;
  178. }
  179. $i++;
  180. }
  181. my $dldir = $config{'dldir'};
  182. # md5sum
  183. sub md5sum($) {
  184. my $fn = shift;
  185. my $m = `md5sum $fn`;
  186. $m = (split(" ", $m))[0];
  187. $md5sums{"$dldir/$fn"} = $m;
  188. return $m;
  189. }
  190. # construct list of files to get
  191. # hash of filenames => size of downloaded part
  192. # query user for each paritial file
  193. print "\nConstructing list of files to get...\n";
  194. my %downloads;
  195. my ($pkg, $dir, @info, @files, $csize, $size);
  196. my $totsize = 0;
  197. foreach $pkg (keys(%pkgs)) {
  198. @files = @{$pkgs{$pkg}[1]};
  199. foreach $fn (@files) {
  200. #Look for a partial file
  201. if (-f "$dldir/$fn.partial") {
  202. rename "$dldir/$fn.partial", "$dldir/$fn";
  203. }
  204. $dir = dirname($fn);
  205. if(! -d "$dldir/$dir") {
  206. mkpath(["$dldir/$dir"], 0, 0755);
  207. }
  208. @info = @{$pkgfiles{$fn}};
  209. $csize = int($info[1]/1024)+1;
  210. if(-f "$dldir/$fn") {
  211. $size = -s "$dldir/$fn";
  212. if($info[1] > $size) {
  213. # partial download
  214. if(yesno("y", "continue file: $fn (" . nb($size) ."/" .
  215. nb($info[1]). ")")) {
  216. $downloads{$fn} = $size;
  217. $totsize += $csize - int($size/1024);
  218. } else {
  219. $downloads{$fn} = 0;
  220. $totsize += $csize;
  221. }
  222. } else {
  223. # check md5sum
  224. if (! exists $md5sums{"$dldir/$fn"}) {
  225. $md5sums{"$dldir/$fn"} = md5sum("$dldir/$fn");
  226. }
  227. if ($md5sums{"$dldir/$fn"} eq $info[0]) {
  228. print "already got: $fn\n";
  229. } else {
  230. print "corrupted: $fn\n";
  231. $downloads{$fn} = 0;
  232. }
  233. }
  234. } else {
  235. my $ffn = $fn;
  236. $ffn =~ s/binary-[^\/]+/.../;
  237. print "want: " .
  238. $config{'site'}[$pkgfiles{$fn}[2]][0] . " $ffn (${csize}k)\n";
  239. $downloads{$fn} = 0;
  240. $totsize += $csize;
  241. }
  242. }
  243. }
  244. my $avsp = `df -Pk $dldir| awk '{ print \$4}' | tail -n 1`;
  245. chomp $avsp;
  246. print "\nApproximate total space required: ${totsize}k\n";
  247. print "Available space in $dldir: ${avsp}k\n";
  248. #$avsp = `df -k $::dldir| paste -s | awk '{ print \$11}'`;
  249. #chomp $avsp;
  250. if($totsize == 0) {
  251. print "Nothing to get.";
  252. } else {
  253. if($totsize > $avsp) {
  254. print "Space required is greater than available space,\n";
  255. print "you will need to select which items to get.\n";
  256. }
  257. # ask user which files to get
  258. if(($totsize > $avsp) || yesno("n", "Do you want to select the files to get")) {
  259. $totsize = 0;
  260. my @files = sort(keys(%downloads));
  261. my $fn;
  262. my $def = "y";
  263. foreach $fn (@files) {
  264. my @info = @{$pkgfiles{$fn}};
  265. my $csize = int($info[1] / 1024) + 1;
  266. my $rsize = int(($info[1] - $downloads{$fn}) / 1024) + 1;
  267. if ($rsize + $totsize > $avsp) {
  268. print "no room for: $fn\n";
  269. delete $downloads{$fn};
  270. } else {
  271. if(yesno($def, $downloads{$fn}
  272. ? "download: $fn ${rsize}k/${csize}k (total = ${totsize}k)"
  273. : "download: $fn ${rsize}k (total = ${totsize}k)")) {
  274. $def = "y";
  275. $totsize += $rsize;
  276. } else {
  277. $def = "n";
  278. delete $downloads{$fn};
  279. }
  280. }
  281. }
  282. }
  283. }
  284. sub download() {
  285. my $i = 0;
  286. my ($site, $ftp);
  287. foreach $site (@{$config{'site'}}) {
  288. my @getfiles = grep { $pkgfiles{$_}[2] == $i } keys %downloads;
  289. my @pre_dist = (); # Directory to add before $fn
  290. #Scan distributions for looking at "(../)+/dir/dir"
  291. my ($n,$cp);
  292. $cp = -1;
  293. foreach (@{$site->[2]}) {
  294. $cp++;
  295. $pre_dist[$cp] = "";
  296. $n = (s#\.\./#../#g);
  297. next if (! $n);
  298. if (m#^((?:\.\./){$n}(?:[^/]+/){$n})#) {
  299. $pre_dist[$cp] = $1;
  300. }
  301. }
  302. if (! @getfiles) { $i++; next; }
  303. $ftp = do_connect ($site->[0], #$::ftpsite,
  304. $site->[4], #$::username,
  305. $site->[5], #$::password,
  306. $site->[1], #$::ftpdir,
  307. $site->[3], #$::passive,
  308. $config{'use_auth_proxy'},
  309. $config{'proxyhost'},
  310. $config{'proxylogname'},
  311. $config{'proxypassword'});
  312. $::ftp = $ftp;
  313. local $SIG{'INT'} = sub { die "Interrupted !\n"; };
  314. my ($fn,$rsize,$res,$pre);
  315. foreach $fn (@getfiles) {
  316. $pre = $pre_dist[$pkgfiles{$fn}[3]] || "";
  317. if ($downloads{$fn}) {
  318. $rsize = ${pkgfiles{$fn}}[1] - $downloads{$fn};
  319. print "getting: $pre$fn (". nb($rsize) . "/" .
  320. nb($pkgfiles{$fn}[1]) . ")\n";
  321. } else {
  322. print "getting: $pre$fn (". nb($pkgfiles{$fn}[1]) . ")\n";
  323. }
  324. $res = $ftp->get("$pre$fn", "$dldir/$fn", $downloads{$fn});
  325. if(! $res) {
  326. my $r = $ftp->code();
  327. print $ftp->message() . "\n";
  328. if (!($r == 550 || $r == 450)) {
  329. return 1;
  330. } else {
  331. #Try to find another file or this package
  332. print "Looking for another version of the package...\n";
  333. my ($dir,$package) = ($fn =~ m#^(.*)/([^/]+)_[^/]+.deb$#);
  334. my $protected = $package;
  335. $protected =~ s/\+/\\\+/g;
  336. my $list = $ftp->ls("$pre$dir");
  337. if ($ftp->ok() && ref($list)) {
  338. foreach (@{$list}) {
  339. if (m/($dir\/${protected}_[^\/]+.deb)/i) {
  340. print "Package found : $_\n";
  341. print "getting: $_ (size not known)\n";
  342. $res = $ftp->get($_, "$dldir/$1");
  343. if (! $res) {
  344. $r = $ftp->code();
  345. print $ftp->message() . "\n";
  346. return 1 if ($r != 550 and $r != 450);
  347. }
  348. }
  349. }
  350. }
  351. }
  352. }
  353. # fully got, remove it from list in case we have to re-download
  354. delete $downloads{$fn};
  355. }
  356. $ftp->quit();
  357. $i++;
  358. }
  359. return 0;
  360. }
  361. # download stuff (protect from ^C)
  362. if($totsize != 0) {
  363. if(yesno("y", "\nDo you want to download the required files")) {
  364. DOWNLOAD_TRY: while (1) {
  365. print "Downloading files... use ^C to stop\n";
  366. eval {
  367. if ((download() == 1) && yesno("y", "\nDo you want to retry downloading at once")) {
  368. next DOWNLOAD_TRY;
  369. }
  370. };
  371. if($@ =~ /Interrupted|Timeout/i ) {
  372. # close the FTP connection if needed
  373. if ((ref($::ftp) =~ /Net::FTP/) and ($@ =~ /Interrupted/i)) {
  374. $::ftp->abort();
  375. $::ftp->quit();
  376. undef $::ftp;
  377. }
  378. print "FTP ERROR\n";
  379. if (yesno("y", "\nDo you want to retry downloading at once")) {
  380. # get the first $fn that foreach would give:
  381. # this is the one that got interrupted.
  382. my $ffn;
  383. MY_ITER: foreach $ffn (keys(%downloads)) {
  384. $fn = $ffn;
  385. last MY_ITER;
  386. }
  387. my $size = -s "$dldir/$fn";
  388. # partial download
  389. if(yesno("y", "continue file: $fn (at $size)")) {
  390. $downloads{$fn} = $size;
  391. } else {
  392. $downloads{$fn} = 0;
  393. }
  394. next DOWNLOAD_TRY;
  395. } else {
  396. $exit = 1;
  397. last DOWNLOAD_TRY;
  398. }
  399. } elsif ($@) {
  400. print "An error occurred ($@) : stopping download\n";
  401. }
  402. last DOWNLOAD_TRY;
  403. }
  404. }
  405. }
  406. # remove duplicate packages (keep latest versions)
  407. # move half downloaded files out of the way
  408. # delete corrupted files
  409. print "\nProcessing downloaded files...(for corrupt/old/partial)\n";
  410. my %vers; # package => version
  411. my %files; # package-version => files...
  412. # check a deb or split deb file
  413. # return 1 if it a deb file, 2 if it is a split deb file
  414. # else 0
  415. sub chkdeb($) {
  416. my ($fn) = @_;
  417. # check to see if it is a .deb file
  418. if(!system("dpkg-deb --info $fn 2>&1 >/dev/null && dpkg-deb --contents $fn 2>&1 >/dev/null")) {
  419. return 1;
  420. } elsif(!system("dpkg-split --info $fn 2>&1 >/dev/null")) {
  421. return 2;
  422. }
  423. return 0;
  424. }
  425. sub getdebinfo($) {
  426. my ($fn) = @_;
  427. my $type = chkdeb($fn);
  428. my ($pkg, $ver);
  429. if($type == 1) {
  430. open(PKGFILE, "dpkg-deb --field $fn |");
  431. my %fields = getblk(\*PKGFILE);
  432. close(PKGFILE);
  433. $pkg = $fields{'package'};
  434. $ver = $fields{'version'};
  435. if($fields{'package_revision'}) { $ver .= '-' . $fields{'package_revision'}; }
  436. return $pkg, $ver;
  437. } elsif ( $type == 2) {
  438. open(PKGFILE, "dpkg-split --info $fn|");
  439. while(<PKGFILE>) {
  440. /Part of package:\s*(\S+)/ and $pkg = $+;
  441. /\.\.\. version:\s*(\S+)/ and $ver = $+;
  442. }
  443. close(PKGFILE);
  444. return $pkg, $ver;
  445. }
  446. print "could not figure out type of $fn\n";
  447. return $pkg, $ver;
  448. }
  449. # process deb file to make sure we only keep latest versions
  450. sub prcdeb($$) {
  451. my ($dir, $fn) = @_;
  452. my ($pkg, $ver) = getdebinfo($fn);
  453. if(!defined($pkg) || !defined($ver)) {
  454. print "could not get package info from file\n";
  455. return 0;
  456. }
  457. if($vers{$pkg}) {
  458. if(dcmpvers($vers{$pkg}, "eq", $ver)) {
  459. $files{$pkg . $ver} = [ $files{$pkg . $ver }, "$dir/$fn" ];
  460. } elsif (dcmpvers($vers{$pkg}, "gt", $ver)) {
  461. print "old version\n";
  462. unlink $fn;
  463. } else { # else $ver is gt current version
  464. my ($c);
  465. foreach $c (@{$files{$pkg . $vers{$pkg}}}) {
  466. print "replaces: $c\n";
  467. unlink "$vardir/methods/ftp/$dldir/$c";
  468. }
  469. $vers{$pkg} = $ver;
  470. $files{$pkg . $ver} = [ "$dir/$fn" ];
  471. }
  472. } else {
  473. $vers{$pkg} = $ver;
  474. $files{$pkg . $ver} = [ "$dir/$fn" ];
  475. }
  476. }
  477. sub prcfile() {
  478. my ($fn) = $_;
  479. if (-f $fn and $fn ne '.') {
  480. my $dir = ".";
  481. if (length($File::Find::dir) > length($dldir)) {
  482. $dir = substr($File::Find::dir, length($dldir)+1);
  483. }
  484. print "$dir/$fn\n";
  485. if(defined($pkgfiles{"$dir/$fn"})) {
  486. my @info = @{$pkgfiles{"$dir/$fn"}};
  487. my $size = -s $fn;
  488. if($size == 0) {
  489. print "zero length file\n";
  490. unlink $fn;
  491. } elsif($size < $info[1]) {
  492. print "partial file\n";
  493. rename $fn, "$fn.partial";
  494. } elsif(( (exists $md5sums{"$dldir/$fn"})
  495. and ($md5sums{"$dldir/$fn"} ne $info[0]) )
  496. or
  497. (md5sum($fn) ne $info[0])) {
  498. print "corrupt file\n";
  499. unlink $fn;
  500. } else {
  501. prcdeb($dir, $fn);
  502. }
  503. } elsif($fn =~ /.deb$/) {
  504. if(chkdeb($fn)) {
  505. prcdeb($dir, $fn);
  506. } else {
  507. print "corrupt file\n";
  508. unlink $fn;
  509. }
  510. } else {
  511. print "non-debian file\n";
  512. }
  513. }
  514. }
  515. find(\&prcfile, "$dldir/");
  516. # install .debs
  517. if(yesno("y", "\nDo you want to install the files fetched")) {
  518. print "Installing files...\n";
  519. #Installing pre-dependent package before !
  520. my (@flds, $package, @filename, $r);
  521. while (@flds = `dpkg --predep-package`, $? == 0) {
  522. foreach (@flds) {
  523. s/\s*\n//;
  524. $package= $_ if s/^Package: //i;
  525. @filename= split(/ +/,$_) if s/^Filename: //i;
  526. }
  527. @filename = map { "$dldir/$_" } @filename;
  528. next if (! @filename);
  529. $r = system('dpkg', '-iB', '--', @filename);
  530. if ($r) { print "DPKG ERROR\n"; $exit = 1; }
  531. }
  532. #Installing other packages after
  533. $r = system("dpkg", "-iGREOB", $dldir);
  534. if($r) {
  535. print "DPKG ERROR\n";
  536. $exit = 1;
  537. }
  538. }
  539. sub removeinstalled {
  540. my $fn = $_;
  541. if (-f $fn and $fn ne '.') {
  542. my $dir = ".";
  543. if (length($File::Find::dir) > length($dldir)) {
  544. $dir = substr($File::Find::dir, length($dldir)+1);
  545. }
  546. if($fn =~ /.deb$/) {
  547. my($pkg, $ver) = getdebinfo($fn);
  548. if(!defined($pkg) || !defined($ver)) {
  549. print "Could not get info for: $dir/$fn\n";
  550. } else {
  551. if($curpkgs{$pkg} and dcmpvers($ver, "le", $curpkgs{$pkg})) {
  552. print "deleting: $dir/$fn\n";
  553. unlink $fn;
  554. } else {
  555. print "leaving: $dir/$fn\n";
  556. }
  557. }
  558. } else {
  559. print "non-debian: $dir/$fn\n";
  560. }
  561. }
  562. }
  563. # remove .debs that have been installed (query user)
  564. # first need to reprocess status file
  565. if(yesno("y", "\nDo you wish to delete the installed package (.deb) files?")) {
  566. print "Removing installed files...\n";
  567. %curpkgs = ();
  568. procstatus();
  569. find(\&removeinstalled, "$dldir/");
  570. }
  571. # remove whole ./debian directory if user wants to
  572. if(yesno("n", "\nDo you want to remove $dldir directory?")) {
  573. rmtree("$dldir");
  574. }
  575. #Store useful md5sums
  576. foreach (keys %md5sums) {
  577. next if (-f $_);
  578. delete $md5sums{$_};
  579. }
  580. open(MD5SUMS, ">$methdir/md5sums") ||
  581. die "Can't open $methdir/md5sums in write mode : $!\n";
  582. print MD5SUMS Dumper(\%md5sums);
  583. close MD5SUMS;
  584. exit $exit;