home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2000 February
/
PCWorld_2000-02_cd.bin
/
live
/
usr
/
lib
/
dpkg
/
methods
/
ftp
/
install
< prev
next >
Wrap
Text File
|
1998-12-28
|
12KB
|
481 lines
#!/usr/bin/perl
# -*-perl-*-
#use strict;
#use diagnostics;
use Net::FTP;
use File::Path;
use File::Basename;
use File::Find;
use Debian::DpkgFtp;
# exit value
my $exit = 0;
# deal with arguments
my $vardir = $ARGV[0];
my $method = $ARGV[1];
my $option = $ARGV[2];
if ($option eq "manual" ) {
print "manual mode not supported yet\n";
exit 1;
}
#print "vardir: $vardir, method: $method, option: $option\n";
# get info from control file
$::dldir="debian";
do "$vardir/methods/ftp/vars" or die "Could not find state file (re-run Access method)";
mkpath(["$vardir/methods/ftp/debian"], 0, 0755);
my @dists = split(/ +/, $::distribs);
chdir "$vardir/methods/ftp";
# get a block
# returns a ref to a hash containing flds->fld contents
# white space from the ends of lines is removed and newlines added
# (no trailing newline).
# die's if something unexpected happens
sub getblk {
my $fh = shift;
my %flds;
my $fld;
while (<$fh>) {
if ( ! /^$/ ) {
FLDLOOP: while (1) {
if ( /^([^ \t]+):[ \t]*(.*)[ \t]*$/ ) {
$fld = lc($1);
$flds{$fld} = $2;
while (<$fh>) {
if ( /^$/ ) {
return %flds;
} elsif ( /^([ \t].*)$/ ) {
$flds{$fld} = $flds{$fld} . "\n" . $1;
} else {
next FLDLOOP;
}
}
return %flds;
} else {
die "Expected a start of field line, but got:\n$_";
}
}
}
}
return %flds;
}
# process status file
# create curpkgs hash with version (no version implies not currently installed)
# of packages we want
print "Processing status file...\n";
my %curpkgs;
sub procstatus {
my (%flds, $fld);
open (STATUS, "$vardir/status") or die "Could not open status file";
while (%flds = getblk(\*STATUS), %flds) {
if($flds{'status'} =~ /^install ok/) {
my $cs = (split(/ /, $flds{'status'}))[2];
if(($cs eq "not-installed") ||
($cs eq "half-installed") ||
($cs eq "config-files")) {
$curpkgs{$flds{'package'}} = "";
} else {
$curpkgs{$flds{'package'}} = $flds{'version'};
}
}
}
close(STATUS);
}
procstatus();
sub dcmpvers {
my($a, $p, $b) = @_;
my ($r);
$r = system("/usr/bin/dpkg", "--compare-versions", "$a", "$p", "$b");
$r = $r/256;
if( $r == 0) {
return 1;
} if( $r == 1) {
return 0;
}
die "dpkg --compare-versions $a $p $b - failed with $r"
}
# process package files, looking for packages to install
# create a hash of these packages pkgname => version, filenames...
# filename => md5sum, size
# for all packages
my %pkgs;
my %pkgfiles;
sub procpkgfile {
my $fn = shift @_;
my(%flds, $fld);
open(PKGFILE, "$fn") or die "Could not open package file $fn";
while(%flds = getblk(\*PKGFILE), %flds) {
my $pkg = $flds{'package'};
my $ver = $curpkgs{$pkg};
my @files = split(/[ \t\n]+/, $flds{'filename'});
my @sizes = split(/[ \t\n]+/, $flds{'size'});
my @md5sums = split(/[ \t\n]+/, $flds{'md5sum'});
my ($fl,$nfs);
if ( defined($ver) && ( ($ver eq "") || dcmpvers( $ver, "lt", $flds{'version'} ) )) {
$pkgs{$pkg} = [ $flds{'version'}, @files ];
$curpkgs{$pkg} = $flds{'version'};
}
$nfs = scalar(@files);
if(($nfs != scalar(@sizes)) || ($nfs != scalar(@md5sums)) ) {
print "Different number of filenames, sizes and md5sums for $flds{'package'}\n";
} else {
my $i = 0;
foreach $fl (@files) {
$pkgfiles{$fl} = [ $md5sums[$i], $sizes[$i] ];
$i++;
}
}
}
}
print "\nProcessing Package files...\n";
my $dist;
foreach $dist (@dists) {
$dist =~ tr/\//_/;
my $fn = "Packages.$dist";
if (-f $fn) {
print " $dist...\n";
procpkgfile($fn);
} else {
print "Could not find packages file for $dist distribution (re-run Update)\n";
}
}
# md5sum
sub md5sum($) {
my $fn = shift;
my $m = `md5sum $fn`;
$m = (split(" ", $m))[0];
return $m;
}
# rename partial files back
sub renamepartial {
if ( /^(.+).partial$/ ) {
rename $_, $1;
}
}
if (-d $::dldir) {
find(\&renamepartial, $::dldir);
}
# construct list of files to get
# hash of filenames => size of downloaded part
# query user for each paritial file
print "\nConstructing list of files to get...\n";
my %downloads;
my ($pkg, $fn);
my $totsize = 0;
foreach $pkg (keys(%pkgs)) {
my @files = pop(@{$pkgs{$pkg}});
foreach $fn (@files) {
my $dir = dirname($fn);
if(!-d "$dir") {
mkpath(["$::dldir/$dir"], 0, 0755);
}
my @info = @{$pkgfiles{$fn}};
my $csize = int($info[1]/1024)+1;
if(-f "$::dldir/$fn") {
my $size = -s "$::dldir/$fn";
if($info[1] > $size) {
# partial download
if(yesno("y", "continue file: $fn ($size/$info[1])")) {
$downloads{$fn} = $size;
$totsize += $csize - int($size/1024);
} else {
$downloads{$fn} = 0;
$totsize += $csize;
}
} else {
# check md5sum
if(md5sum("$::dldir/$fn") eq $info[0]) {
print "already got: $fn\n";
} else {
print "corrupted: $fn\n";
$downloads{$fn} = 0;
}
}
} else {
print "want: $fn (${csize}k)\n";
$downloads{$fn} = 0;
$totsize += $csize;
}
}
}
my $avsp = `df -Pk $::dldir| awk '{ print \$4}' | tail -n 1`;
chomp $avsp;
print "\nApproximate total space required: ${totsize}k\n";
print "Available space in $::dldir: ${avsp}k\n";
#$avsp = `df -k $::dldir| paste -s | awk '{ print \$11}'`;
#chomp $avsp;
if($totsize == 0) {
print "Nothing to get.";
} else {
if($totsize > $avsp) {
print "Space required is greater than available space,\n";
print "you will need to select which items to get.\n";
}
# ask user which files to get
if(($totsize > $avsp) || yesno("n", "Do you want to select the files to get")) {
$totsize = 0;
my @files = sort(keys(%downloads));
my $fn;
my $def = "y";
foreach $fn (@files) {
my @info = @{$pkgfiles{$fn}};
my $csize = int($info[1] / 1024) + 1;
my $rsize = int(($info[1] - $downloads{$fn}) / 1024) + 1;
if ($rsize + $totsize > $avsp) {
print "no room for: $fn\n";
delete $downloads{$fn};
} else {
if(yesno($def, $downloads{$fn}
? "download: $fn ${rsize}k/${csize}k (total = ${totsize}k)"
: "download: $fn ${rsize}k (total = ${totsize}k)")) {
$def = "y";
$totsize += $rsize;
} else {
$def = "n";
delete $downloads{$fn};
}
}
}
}
}
sub download() {
$ftp = do_connect ($::ftpsite,$::username,$::password,$::ftpdir,$::passive,
$::useproxy,$::proxyhost,$::proxylogname,$::proxypassword);
my $fn;
foreach $fn (keys(%downloads)) {
if ($downloads{$fn}) {
my $rsize = ${pkgfiles{$fn}}[1] - $downloads{$fn};
print "getting: $fn ($rsize/${$pkgfiles{$fn}}[1])\n";
} else {
print "getting: $fn (${$pkgfiles{$fn}}[1])\n";
}
if(!$ftp->get($fn, "$::dldir/$fn", $downloads{$fn})) {
my $r = $ftp->code();
print $ftp->message . "\n";
if (!($r == 550 || $r == 450)) {
return 1;
}
}
# fully got, remove it from list in case we have to re-download
delete $downloads{$fn};
}
$ftp->quit();
return 0;
}
# download stuff (protect from ^C)
if($totsize != 0) {
if(yesno("y", "\nDo you want to download the required files")) {
DOWNLOAD_TRY: while (1) {
print "Downloading files... use ^C to stop\n";
eval {
local $SIG{INT} = sub {
die "Interrupted!\n";
};
if ((download() == 1) && yesno("y", "\nDo you want to retry downloading at once")) {
next DOWNLOAD_TRY;
}
};
if($@) {
print "FTP ERROR\n";
if (yesno("y", "\nDo you want to retry downloading at once")) {
# get the first $fn that foreach would give:
# this is the one that got interrupted.
MY_ITER: foreach $ffn (keys(%downloads)) {
$fn = $ffn;
last MY_ITER;
}
my $size = -s "$::dldir/$fn";
# partial download
if(yesno("y", "continue file: $fn (at $size)")) {
$downloads{$fn} = $size;
} else {
$downloads{$fn} = 0;
}
next DOWNLOAD_TRY;
} else {
$exit = 1;
last DOWNLOAD_TRY;
}
} else {
last DOWNLOAD_TRY;
}
}
}
}
# remove duplicate packages (keep latest versions)
# move half downloaded files out of the way
# delete corrupted files
print "\nProcessing downloaded files...(for corrupt/old/partial)\n";
my %vers; # package => version
my %files; # package-version => files...
# check a deb or split deb file
# return 1 if it a deb file, 2 if it is a split deb file
# else 0
sub chkdeb($) {
my ($fn) = @_;
# check to see if it is a .deb file
if(!system("dpkg-deb --info $fn 2>&1 >/dev/null && dpkg-deb --contents $fn 2>&1 >/dev/null")) {
return 1;
} elsif(!system("dpkg-split --info $fn 2>&1 >/dev/null")) {
return 2;
}
return 0;
}
sub getdebinfo($) {
my ($fn) = @_;
my $type = chkdeb($fn);
my ($pkg, $ver);
if($type == 1) {
open(PKGFILE, "dpkg-deb --field $fn |");
my %fields = getblk(\*PKGFILE);
close(PKGFILE);
$pkg = $fields{'package'};
$ver = $fields{'version'};
if($fields{'package_revision'}) { $ver .= '-' . $fields{'package_revision'}; }
return $pkg, $ver;
} elsif ( $type == 2) {
open(PKGFILE, "dpkg-split --info $fn|");
while(<PKGFILE>) {
/Part of package:[ \t]*([^ \t]+)/ and $pkg = $+;
/\.\.\. version:[ \t]*([^ \t]+)/ and $ver = $+;
}
close(PKGFILE);
return $pkg, $ver;
}
print "could not figure out type of $fn\n";
return $pkg, $ver;
}
# process deb file to make sure we only keep latest versions
sub prcdeb($$) {
my ($dir, $fn) = @_;
my ($pkg, $ver) = getdebinfo($fn);
if(!defined($pkg) || !defined($ver)) {
print "could not get package info from file\n";
return 0;
}
if($vers{$pkg}) {
if(dcmpvers($vers{$pkg}, "eq", $ver)) {
$files{$pkg . $ver} = [ $files{$pkg . $ver }, "$dir/$fn" ];
} elsif (dcmpvers($vers{$pkg}, "gt", $ver)) {
print "old version\n";
unlink $fn;
} else { # else $ver is gt current version
my ($c);
foreach $c (@{$files{$pkg . $vers{$pkg}}}) {
print "replaces: $c\n";
unlink "$vardir/methods/ftp/$::dldir/$c";
}
$vers{$pkg} = $ver;
$files{$pkg . $ver} = [ "$dir/$fn" ];
}
} else {
$vers{$pkg} = $ver;
$files{$pkg . $ver} = [ "$dir/$fn" ];
}
}
sub prcfile() {
my ($fn) = $_;
if (-f $fn) {
my $dir = substr($File::Find::dir, index($File::Find::dir, "::dldir")+length($::dldir)+2);
print "$dir/$fn\n";
if(defined($pkgfiles{"$dir/$fn"})) {
my @info = @{$pkgfiles{"$dir/$fn"}};
my $size = -s $fn;
if($size == 0) {
print "zero length file\n";
unlink $fn;
} elsif($size < $info[1]) {
print "partial file\n";
rename $fn, "$fn.partial";
} elsif(md5sum($fn) ne $info[0]) {
print "corrupt file\n";
unlink $fn;
} else {
prcdeb($dir, $fn);
}
} elsif($fn =~ /.deb$/) {
if(chkdeb($fn)) {
prcdeb($dir, $fn);
} else {
print "corrupt file\n";
unlink $fn;
}
} else {
print "non-debian file\n";
}
}
}
find(\&prcfile, "$::dldir");
# install .debs
if(yesno("y", "\nDo you want to install the files fetched")) {
print "Installing files...\n";
my $r = system("dpkg", "-iGREOB", "$::dldir");
if($r) {
print "DPKG ERROR\n";
$exit = 1;
}
}
sub removeinstalled {
my $fn = $_;
if (-f $fn) {
my $dir = substr($File::Find::dir, index($File::Find::dir, $::dldir)+length($::dldir)+2);
if($fn =~ /.deb$/) {
my($pkg, $ver) = getdebinfo($fn);
if(!defined($pkg) || !defined($ver)) {
print "Could not get info for: $dir/$fn\n";
} else {
if($curpkgs{$pkg} and dcmpvers($ver, "le", $curpkgs{$pkg})) {
print "deleting: $dir/$fn\n";
unlink $fn;
} else {
print "leaving: $dir/$fn\n";
}
}
} else {
print "non-debian: $dir/$fn\n";
}
}
}
# remove .debs that have been installed (query user)
# first need to reprocess status file
if(yesno("y", "\nDo you wish to delete the installed package (.deb) files?")) {
print "Removing installed files...\n";
%curpkgs = ();
procstatus();
find(\&removeinstalled, "$::dldir");
}
# remove whole ./debian directory if user wants to
if(yesno("n", "\nDo you want to remove $::dldir directory?")) {
rmtree("$::dldir");
}
exit $exit;