3 # Copyright © 1996 Andy Guy <andy@cyteen.org>
4 # Copyright © 1998 Martin Schulze <joey@infodrom.north.de>
5 # Copyright © 1999, 2009 Raphaël Hertzog <hertzog@debian.org>
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; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <https://www.gnu.org/licenses/>.
23 use File
::Path
qw(make_path remove_tree);
33 warn "Missing Dpkg modules required by the FTP access method.\n\n";
38 use Dselect
::Method
::Ftp
;
46 my $vardir = $ARGV[0];
47 my $method = $ARGV[1];
48 my $option = $ARGV[2];
50 if ($option eq 'manual') {
51 print "manual mode not supported yet\n";
54 #print "vardir: $vardir, method: $method, option: $option\n";
56 my $methdir = "$vardir/methods/ftp";
58 # get info from control file
59 read_config
("$methdir/vars");
62 make_path
("$methdir/$CONFIG{dldir}", { mode
=> 0755 });
65 #Read md5sums already calculated
67 if (-f
"$methdir/md5sums") {
68 my $code = file_slurp
("$methdir/md5sums");
69 my $VAR1; ## no critic (Variables::ProhibitUnusedVariables)
72 die "couldn't eval $methdir/md5sums content: $@\n";
74 if (ref($res)) { %md5sums = %{$res} }
78 # returns a ref to a hash containing flds->fld contents
79 # white space from the ends of lines is removed and newlines added
80 # (no trailing newline).
81 # die's if something unexpected happens
89 if ( /^(\S+):\s*(.*)\s*$/ ) {
95 } elsif ( /^(\s.*)$/ ) {
96 $flds{$fld} = $flds{$fld} . "\n" . $1;
103 die "expected a start of field line, but got:\n$_";
111 # process status file
112 # create curpkgs hash with version (no version implies not currently installed)
113 # of packages we want
114 print "Processing status file...\n";
118 open(my $status_fh, '<', "$vardir/status") or
119 die 'Could not open status file';
120 while (%flds = get_stanza
($status_fh), %flds) {
121 if($flds{'status'} =~ /^install ok/) {
122 my $cs = (split(/ /, $flds{'status'}))[2];
123 if (($cs eq 'not-installed') ||
124 ($cs eq 'half-installed') ||
125 ($cs eq 'config-files')) {
126 $curpkgs{$flds{'package'}} = '';
128 $curpkgs{$flds{'package'}} = $flds{'version'};
139 $r = system('dpkg', '--compare-versions', "$a", "$p", "$b");
146 die "dpkg --compare-versions $a $p $b - failed with $r";
149 # process package files, looking for packages to install
150 # create a hash of these packages pkgname => version, filenames...
151 # filename => md5sum, size
159 my (@files, @sizes, @md5sums, $pkg, $ver, $nfs, $fld);
161 open(my $pkgfile_fh, '<', $fn) or die "could not open package file $fn";
162 while (%flds = get_stanza
($pkgfile_fh), %flds) {
163 $pkg = $flds{'package'};
164 $ver = $curpkgs{$pkg};
165 @files = split(/[\s\n]+/, $flds{'filename'});
166 @sizes = split(/[\s\n]+/, $flds{'size'});
167 @md5sums = split(/[\s\n]+/, $flds{'md5sum'});
168 if (defined($ver) && (($ver eq '') || dcmpvers
($ver, 'lt', $flds{'version'}))) {
169 $pkgs{$pkg} = [ $flds{'version'}, [ @files ], $site ];
170 $curpkgs{$pkg} = $flds{'version'};
172 $nfs = scalar(@files);
173 if(($nfs != scalar(@sizes)) || ($nfs != scalar(@md5sums)) ) {
174 print "Different number of filenames, sizes and md5sums for $flds{'package'}\n";
177 foreach my $fl (@files) {
178 $pkgfiles{$fl} = [ $md5sums[$i], $sizes[$i], $site, $dist ];
183 close $pkgfile_fh or die "cannot close package file $fn: $!\n";
186 print "\nProcessing Package files...\n";
189 foreach my $site (@
{$CONFIG{site
}}) {
191 foreach my $dist (@
{$site->[2]}) {
194 $fn = "Packages.$site->[0].$fn";
196 print " $site->[0] $dist...\n";
197 procpkgfile
($fn,$i,$j);
199 print "Could not find packages file for $site->[0] $dist distribution (re-run Update)\n"
206 my $dldir = $CONFIG{dldir
};
210 my $m = qx(md5sum
$fn);
211 $m = (split(' ', $m))[0];
212 $md5sums{"$dldir/$fn"} = $m;
216 # construct list of files to get
217 # hash of filenames => size of downloaded part
218 # query user for each partial file
219 print "\nConstructing list of files to get...\n";
221 my ($dir, @info, @files, $csize, $size);
223 foreach my $pkg (keys(%pkgs)) {
224 @files = @
{$pkgs{$pkg}[1]};
225 foreach my $fn (@files) {
226 #Look for a partial file
227 if (-f
"$dldir/$fn.partial") {
228 rename "$dldir/$fn.partial", "$dldir/$fn";
231 if(! -d
"$dldir/$dir") {
232 make_path
("$dldir/$dir", { mode
=> 0755 });
234 @info = @
{$pkgfiles{$fn}};
235 $csize = int($info[1]/1024)+1;
236 if(-f
"$dldir/$fn") {
237 $size = -s
"$dldir/$fn";
238 if($info[1] > $size) {
240 if (yesno
('y', "continue file: $fn (" . nb
($size) . '/' .
241 nb
($info[1]) . ')')) {
242 $downloads{$fn} = $size;
243 $totsize += $csize - int($size/1024);
250 if (! exists $md5sums{"$dldir/$fn"}) {
251 $md5sums{"$dldir/$fn"} = md5sum
("$dldir/$fn");
253 if ($md5sums{"$dldir/$fn"} eq $info[0]) {
254 print "already got: $fn\n";
256 print "corrupted: $fn\n";
262 $ffn =~ s/binary-[^\/]+/.../;
264 $CONFIG{site
}[$pkgfiles{$fn}[2]][0] . " $ffn (${csize}k)\n";
271 my $avsp = qx(df
-Pk
$dldir| awk
'{ print \$4}' | tail
-n
1);
274 print "\nApproximate total space required: ${totsize}k\n";
275 print "Available space in $dldir: ${avsp}k\n";
277 #$avsp = qx(df -k $::dldir| paste -s | awk '{ print \$11});
281 print 'Nothing to get.';
283 if($totsize > $avsp) {
284 print "Space required is greater than available space,\n";
285 print "you will need to select which items to get.\n";
287 # ask user which files to get
288 if (($totsize > $avsp) ||
289 yesno
('n', 'Do you want to select the files to get')) {
291 my @files = sort(keys(%downloads));
293 foreach my $fn (@files) {
294 my @info = @
{$pkgfiles{$fn}};
295 my $csize = int($info[1] / 1024) + 1;
296 my $rsize = int(($info[1] - $downloads{$fn}) / 1024) + 1;
297 if ($rsize + $totsize > $avsp) {
298 print "no room for: $fn\n";
299 delete $downloads{$fn};
300 } elsif (yesno
($def, $downloads{$fn}
301 ?
"download: $fn ${rsize}k/${csize}k (total = ${totsize}k)"
302 : "download: $fn ${rsize}k (total = ${totsize}k)")) {
307 delete $downloads{$fn};
316 foreach my $site (@
{$CONFIG{site
}}) {
317 my @getfiles = grep { $pkgfiles{$_}[2] == $i } keys %downloads;
318 my @pre_dist = (); # Directory to add before $fn
320 #Scan distributions for looking at "(../)+/dir/dir"
323 foreach (@
{$site->[2]}) {
326 $n = (s{\.\./}{../}g);
328 if (m
<^((?
:\
.\
./){$n}(?:[^/]+/){$n})>) {
333 if (! @getfiles) { $i++; next; }
335 $ftp = do_connect
(ftpsite
=> $site->[0],
336 ftpdir
=> $site->[1],
337 passive
=> $site->[3],
338 username
=> $site->[4],
339 password
=> $site->[5],
340 useproxy
=> $CONFIG{use_auth_proxy
},
341 proxyhost
=> $CONFIG{proxyhost
},
342 proxylogname
=> $CONFIG{proxylogname
},
343 proxypassword
=> $CONFIG{proxypassword
});
345 local $SIG{INT
} = sub { die "Interrupted !\n"; };
347 my ($rsize, $res, $pre);
348 foreach my $fn (@getfiles) {
349 $pre = $pre_dist[$pkgfiles{$fn}[3]] || '';
350 if ($downloads{$fn}) {
351 $rsize = ${pkgfiles
{$fn}}[1] - $downloads{$fn};
352 print "getting: $pre$fn (" . nb
($rsize) . '/' .
353 nb
($pkgfiles{$fn}[1]) . ")\n";
355 print "getting: $pre$fn (". nb
($pkgfiles{$fn}[1]) . ")\n";
357 $res = $ftp->get("$pre$fn", "$dldir/$fn", $downloads{$fn});
359 my $r = $ftp->code();
360 print $ftp->message() . "\n";
361 if (!($r == 550 || $r == 450)) {
364 #Try to find another file or this package
365 print "Looking for another version of the package...\n";
366 my ($dir, $package) = ($fn =~ m{^(.*)/([^/]+)_[^/]+.deb$});
367 my $list = $ftp->ls("$pre$dir");
368 if ($ftp->ok() && ref($list)) {
369 foreach my $file (@
{$list}) {
370 if ($file =~ m/($dir\/\Q
$package\E_
[^\
/]+.deb)/i) {
371 print "Package found : $file\n";
372 print "getting: $file (size not known)\n";
373 $res = $ftp->get($file, "$dldir/$1");
376 print $ftp->message() . "\n";
377 return 1 if ($r != 550 and $r != 450);
384 # fully got, remove it from list in case we have to re-download
385 delete $downloads{$fn};
393 # download stuff (protect from ^C)
395 if (yesno
('y', "\nDo you want to download the required files")) {
396 DOWNLOAD_TRY
: while (1) {
397 print "Downloading files... use ^C to stop\n";
399 if ((download
() == 1) &&
400 yesno
('y', "\nDo you want to retry downloading at once")) {
404 if($@
=~ /Interrupted|Timeout/i ) {
405 # close the FTP connection if needed
406 if ((ref($ftp) =~ /Net::FTP/) and ($@
=~ /Interrupted/i)) {
412 if (yesno
('y', "\nDo you want to retry downloading at once")) {
413 # get the first $fn that foreach would give:
414 # this is the one that got interrupted.
416 MY_ITER
: foreach my $ffn (keys(%downloads)) {
420 my $size = -s
"$dldir/$fn";
422 if (yesno
('y', "continue file: $fn (at $size)")) {
423 $downloads{$fn} = $size;
433 print "An error occurred ($@) : stopping download\n";
440 # remove duplicate packages (keep latest versions)
441 # move half downloaded files out of the way
442 # delete corrupted files
443 print "\nProcessing downloaded files...(for corrupt/old/partial)\n";
444 my %vers; # package => version
445 my %files; # package-version => files...
447 # check a deb or split deb file
448 # return 1 if it a deb file, 2 if it is a split deb file
452 # check to see if it is a .deb file
453 if (!system "dpkg-deb --info $fn >/dev/null 2>&1 && dpkg-deb --contents $fn >/dev/null 2>&1") {
455 } elsif (!system "dpkg-split --info $fn >/dev/null 2>&1") {
462 my $type = chkdeb
($fn);
465 open(my $pkgfile_fh, '-|', "dpkg-deb --field $fn")
466 or die "cannot create pipe for 'dpkg-deb --field $fn'";
467 my %fields = get_stanza
($pkgfile_fh);
469 $pkg = $fields{'package'};
470 $ver = $fields{'version'};
472 } elsif ( $type == 2) {
473 open(my $pkgfile_fh, '-|', "dpkg-split --info $fn")
474 or die "cannot create pipe for 'dpkg-split --info $fn'";
475 while (<$pkgfile_fh>) {
476 /Part of package:\s*(\S+)/ and $pkg = $1;
477 /\.\.\. version:\s*(\S+)/ and $ver = $1;
482 print "could not figure out type of $fn\n";
486 # process deb file to make sure we only keep latest versions
489 my ($pkg, $ver) = getdebinfo
($fn);
490 if(!defined($pkg) || !defined($ver)) {
491 print "could not get package info from file\n";
495 if (dcmpvers
($vers{$pkg}, 'eq', $ver)) {
496 $files{$pkg . $ver} = [ $files{$pkg . $ver }, "$dir/$fn" ];
497 } elsif (dcmpvers
($vers{$pkg}, 'gt', $ver)) {
498 print "old version\n";
500 } else { # else $ver is gt current version
501 foreach my $c (@
{$files{$pkg . $vers{$pkg}}}) {
502 print "replaces: $c\n";
503 unlink "$vardir/methods/ftp/$dldir/$c";
506 $files{$pkg . $ver} = [ "$dir/$fn" ];
510 $files{$pkg . $ver} = [ "$dir/$fn" ];
516 if (-f
$fn and $fn ne '.') {
518 if (length($File::Find
::dir
) > length($dldir)) {
519 $dir = substr($File::Find
::dir
, length($dldir)+1);
522 if(defined($pkgfiles{"$dir/$fn"})) {
523 my @info = @
{$pkgfiles{"$dir/$fn"}};
526 print "zero length file\n";
528 } elsif($size < $info[1]) {
529 print "partial file\n";
530 rename $fn, "$fn.partial";
531 } elsif(( (exists $md5sums{"$dldir/$fn"})
532 and ($md5sums{"$dldir/$fn"} ne $info[0]) )
534 (md5sum
($fn) ne $info[0])) {
535 print "corrupt file\n";
540 } elsif($fn =~ /.deb$/) {
544 print "corrupt file\n";
548 print "non-debian file\n";
552 find
(\
&prcfile
, "$dldir/");
555 if (yesno
('y', "\nDo you want to install the files fetched")) {
556 print "Installing files...\n";
557 #Installing pre-dependent package before !
558 my (@flds, $package, @filename, $r);
559 while (@flds = qx(dpkg
--predep
-package), $?
== 0) {
560 foreach my $field (@flds) {
562 $package = $field if $field =~ s/^Package: //i;
563 @filename = split / +/, $field if $field =~ s/^Filename: //i;
565 @filename = map { "$dldir/$_" } @filename;
566 next if (! @filename);
567 $r = system('dpkg', '-iB', '--', @filename);
568 if ($r) { print "DPKG ERROR\n"; $exit = 1; }
570 #Installing other packages after
571 $r = system('dpkg', '-iGREOB', $dldir);
573 print "DPKG ERROR\n";
578 sub removeinstalled
{
580 if (-f
$fn and $fn ne '.') {
582 if (length($File::Find
::dir
) > length($dldir)) {
583 $dir = substr($File::Find
::dir
, length($dldir)+1);
586 my($pkg, $ver) = getdebinfo
($fn);
587 if(!defined($pkg) || !defined($ver)) {
588 print "Could not get info for: $dir/$fn\n";
589 } elsif ($curpkgs{$pkg} and dcmpvers
($ver, 'le', $curpkgs{$pkg})) {
590 print "deleting: $dir/$fn\n";
593 print "leaving: $dir/$fn\n";
596 print "non-debian: $dir/$fn\n";
601 # remove .debs that have been installed (query user)
602 # first need to reprocess status file
603 if (yesno
('y', "\nDo you wish to delete the installed package (.deb) files?")) {
604 print "Removing installed files...\n";
607 find
(\
&removeinstalled
, "$dldir/");
610 # remove whole ./debian directory if user wants to
611 if (yesno
('n', "\nDo you want to remove $dldir directory?")) {
615 #Store useful md5sums
616 foreach my $file (keys %md5sums) {
618 delete $md5sums{$file};
620 file_dump
("$methdir/md5sums", Dumper
(\
%md5sums));