install.pl 17 KB

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