#!/usr/bin/perl -w
##############################################
# autoupdate                                 #
# Gerald Teschl <Gerald.Teschl@univie.ac.at> #
# Copyright: GPL                             #
# Version: 3.7.4                             #
##############################################


use Net::FTP;
use Getopt::Long;
use File::Copy;
use IO::File;
use DB_File;
use POSIX;
use lib '/usr/lib/perl5/site_perl';
use autoupdate;

use strict;
use vars qw($showhelp $showversion $showlatest $StoreDir $CheckLocal $Host $Port $User 
	$Pass $Protocol $LWPua $DldResult @SubDirs @DldDirs @DldConfig @Exclude @Include
	@DldDataBase @UpdExclude @UpdInclude @AllRPMs @SelectedRPMs
	@SelectedKernelRPMs %NewRPMs @UpdatedRPMs @DownloadedRPMs @DepList @GetList
	@RemovedKernel @RPMOutput %Provides);

# Make sure we understand the errors of rpm
$ENV{'LANG'}= "en_US";
# Needed when run from cron on older versions 
autoflush STDOUT;
autoflush STDERR;

# Default behavior
my $DoLock = 1;
my $DoDld = 0;
my $DoGet = 0;
my $DldAll = 0;
my $DldRecursive = 0;
my $DldMatch = 0;
my $BestMatch = 1;
my $RemoveBad = 1;
my $DefaultPassive = 1;
my $Passive = $DefaultPassive;
my $DefaultFTPRetry = 0;
my $FTPRetry = $DefaultFTPRetry;
my $DefaultFTPWait = 10;
my $FTPWait = $DefaultFTPWait;
my $Firewall = "";
my $DefaultUseLWP = 0;
my $UseLWP = $DefaultUseLWP;
my $LWPProtocols = "http,https";
my $DldGetInstalled = 0;
my $DldUseDB = 1;
my $DoAddToDB = 0;
my $MergeDB = "";
my $CreateDB = "";
my $DoUpdate = 0;
my $DoInstall = 0;
my $DoKernel = 0;
my $KernelExt = "smp,enterprise,bigmem,debug";
my $DoInitRD = 1;
my $DoBoot = 1;
my $CheckSig = 0;
my $CheckGPG = 0;
my $Repackage = 0;
my $QueryHeaders = 0;
my $QueryDatabase = 0;
my $Resolve = 1;
my $Recursive = 0;
my $MaxRecursive = 100;
my $MaxRedirect = 10;
my $PostUpdateScript = "";
my $PostDldScript = "";
my $DistVersion = "";
my $DistArch = "";
my $CleanUp = 0;
my $CleanUpKernel = 1;
my $BootManager = "";
my $BootScript = "";
my $BootConf = "";
my $BootAddAsNew = 0;
my $BootTag= "";
my $UseBootTag= 1;
my $DoMerge = 0;
my $MergeAll = 0;
my $MergeMatch = 0;
my $DoPurge = 0;
my $DefaultUser = "anonymous";
my $DefaultPass = "autoupdate\@localhost";
my $DataBaseName = "autoprovides.db";
my $DoLog = 0;
my $HTTPExt= "html,htm,php,asp";
my $DefaultHTTPSendHost = 0;
my $HTTPSendHost= $DefaultHTTPSendHost;
my $ShellEscapes = 1;
my $Test = 0;
my $Verbose = 0;
my $Quiet = 0;
my $Warnings = 1;
my $DefaultRPMNameWarnings = $Warnings;
my $RPMNameWarnings = $DefaultRPMNameWarnings;
my $DefaultFixRPMNames = 1;
my $FixRPMNames = $DefaultFixRPMNames;
my $Debug = 0;
my $DebugHTTP = 0;
my $DebugLWP = 0;
my $DebugFTP = 0;
my $DebugAUPM = 0;
my $TmpFile = "autoupdate_$$.tmp";
if ($ENV{'TMP'}) {
	$TmpFile = "$ENV{'TMP'}/$TmpFile";
} else {
	$TmpFile = "/tmp/$TmpFile";
}
my $StunnelPort="9090";
my $StunnelPid="0";

# Default file locations
my $URL = "";
my $UpdateDir = "/var/spool/autoupdate";
my $InstallDir = "";
my $RPMDir = "";
my $ConfigDir = "/etc/autoupdate.d";
my $DldConfigDir = $ConfigDir;
my $ConfigFile = "$ConfigDir/autoupdate.conf";
my $DataBase= "";
my $LogFile= "/var/log/autoupdate.log";
my $LiloConf = "/etc/lilo.conf";
my $BootMnt = "";
my $GrubConf = "/boot/grub/grub.conf";
my $BootDir = "/boot";
my $StunnelPidFile="/var/run/stunnel";
my $LockFile="/var/lock/autoupdate.pid";

# Path to helper applications
my $RPM = "/bin/rpm";
my $MKINITRD = "/sbin/mkinitrd";
my $LILO = "/sbin/lilo";
my $GRUB = "/sbin/grub";
my $DF = "/bin/df";
my $PS = "/bin/ps";
my $STUNNEL = "/usr/sbin/stunnel";
my @StunnelArgs= ("-c", "-d", $StunnelPort, "-r");

# Internal variables (do not change).
my $Version = "3.7.4";
my $USAGE = "Usage:  auto(dld|get|upd|ins|mrg|prg) [ options ], where options are:

  --(no)dld                      Download new rpms.
  --(no)get                      Get new rpms.
  --url [user\@]host[/dir]       Download new rpms from this url.
  --(no) uselwp                  Use LWP for downloads.
  --dldconfig file1[,file2[,..]] Download configuration files to include.
  --dldall                       Download all new rpms from remote site.
  --(no)dldrecursive             Recursively descent through all directories at url.
  --(no)dldmatch                 Apply Include/Exclude match during download.
  --(no)dldusedb                 Use provides database from dld site.
  --(no)bestmatch                Only download the rpm which matches best.
  --(no)removebad                Remove bad rpms from updates.
  --(no)passive                  Use (don't use) passive ftp.
  --(no)httpsendhost             Send host name as part of http requests.
  --(no)addtodb                  Add rpms to provides database.
  --mergedb file                 Merge given database with provides database.
  --createdb file                Create given database.
  --(no)update                   Update all rpms.
  --(no)install                  Install new rpms.
  --(no)kernel                   Install new kernel rpms.
  --(no)initrd                   Create initrd for new kernels if needed.
  --(no)boot                     Update boot manager.
  --(no)checksig                 Check rpm signature.
  --(no)checkgpg                 Check gpg signature.
  --(no)repackage                Use the repackage option of rpm.
  --(no)queryheaders             Query the rpm header for local packages
  --(no)querydatabase            Query the rpm database for local packages
  --(no)resolve                  Try to resolve dependencies.
  --(no)recursive                Recursively descent through all local subdirectories.
  --updatedir                    Directory containing the rpms to be updated.
  --installdir                   Directory containing the rpms to be installed.
  --rpmdir                       Directory containing the distribution rpms.
  --postupdatescript             Script to execute after upgrading rpms.
  --postdldscript                Script to execute after downloading rpms.
  --distversion                  Distribution version.
  --distarch                     Distribution architecture.
  --database                     Provides database to use.
  --(no)lock                     Create a lock file.
  --(no)log                      Log updated rpms.
  --logfile                      Log file to use.
  --(no)cleanup                  Remove rpms after upgrade.
  --(no)cleanupkernel            Remove old kernel rpms.
  --(no)bootaddasnew             Add kernel images as new (rename the current to old)
  --include regularexpr          Include rpms matching regularexpr for upgrade.
  --exclude regularexpr          Exclude rpms matching regularexpr for upgrade.
  --(no)merge                    Merge all new rpms into the distribution.
  --(no)mergematch               Apply Include/Exclude match during merge.
  --(no)mergeall                 Merge even if no old version exists.
  --(no)purge                    Remove old versions from updates.
  --arch                         Architecture to use.
  --config file                  Configuration file.
  --compare ver1,ver2            Compare two version strings.
  --whatprovides                 Query the provides database.
  --test                         Test mode.
  --(no)verbose                  Be verbose.
  --(no)quiet                    Be quiet.
  --(no)warnings                 Display warnings.
  --(no)rpmnamewarnings          Display warnings about bad rpm names.
  --(no)fixrpmnames              Fix rpm names after donwloading.
  --debug 1|2|3                  Show debugging info.
  --(no)debughttp                Show debugging info for http actions.
  --(no)debugftp                 Show debugging info for ftp actions.
  --(no)debuglwp                 Show debugging info for lwp actions.
  --(no)debugaupm                Show debugging info for rpm selection.
  --showlatest                   Select and display latest rpms
  --checklocal 0|1|2             Check against local rpms during showlatest 
  --version                      Print version and exit.
See autoupdate(8) for more information.
";
my $LastTry = 0;
my $BufSize = 4096;
my $ChangedBoot = 0;
my $Kernel=0; # Number of kernels upgraded
my $cwd = "";
my $Compare = "";
my $WhatProvides = "";
my $CurrentKernel= "";
my @NoWarnDirs= ();

######################
# Strips unwanted stuff from a directory
######################

sub Strip_Dir
{
my $old;
do {
	$old=$_[0];
	$_[0] =~ s/\/[^\/]+\/\.\.$//g;
	$_[0] =~ s/^[^\/]+\/\.\.$/./g;
	$_[0] =~ s/\/[^\/]+\/\.\.\//\//g;
	$_[0] =~ s/\/\.?\//\//g;
	$_[0] =~ s/^\/\.\.\//\//g;
} while ($_[0] ne $old);
# Strip / from end if second argument is present
if ($_[1] and $_[0] =~ /^(.*)\/\.?$/) {
	$_[0] = $1;
}

}

######################
# Checks if an rpm is excluded
######################

sub IsExcluded
{
	my $item= $_[0]->rpmname;
	my $exclude = 0; # Default is to include
	my $rex;
	
	if (@Include) {
		$exclude=1;
	}

	foreach $rex ( @Include ) {
		if ( $item =~/$rex/ ) {	
			$exclude=0;
			last;
		}
	}
	foreach $rex ( @Exclude ) {
		if ( $item =~/$rex/ ) {	
			$exclude=1;
			last;
		}
	}
return $exclude;
}

######################
# Get RPM Header objects for rpms
######################

sub GetRPMHeaders
{
	my ($item, $tmp);
	my $type = "";
	if ($_[0] =~ /^[flr]$/) {
		$type= shift;
	}
	my @rpms= ();
	foreach $item ( @_ ) {
		$tmp= Header->new($item,$type);
		unless ($tmp) {
			print STDERR "Warning: Illegal rpm name: $item\n" if $RPMNameWarnings;
			next;
		}
		if ( IsExcluded($tmp) ) {
			print "Excluded: " . $tmp->rpmname . "\n" if $Debug;
			next;
		}
		push(@rpms, $tmp);
	}
	return @rpms;
}


######################
# Check RPM signature
######################

sub Check_Sig
{
return 0 unless ($CheckSig or $CheckGPG);
my $item = shift;
my @flags = ("--checksig");
push(@flags, "--nogpg") unless ($CheckGPG);

if ( CallRPM(@flags, $item->filename) != 0) {
	print "Bad signature: ". $item->filename ."\n";
	unlink ($item) if ($RemoveBad);
	return 1;
}

if ( $CheckGPG and $RPMOutput[0] !~ /gpg/ ) {
    print "No gpg signature: ". $item->filename ."\n";
    return 1;
}

print "  Good signature: ". $item->filename . "\n" if ($Debug >1);
return 0;
}

######################
# Query rpms for other rpms they require.
######################

sub Get_Requirements
{
my ($item, $rpm);
my @requirements =();

print "  Getting requirements:\n" if ($Debug >1);
foreach $rpm (@_) {
	print "    " . $rpm->rpmname ."\n" if ($Debug >2);

	unless ( CallRPM("-qp", "--requires", $rpm->filename) ==0 ) {
		print STDERR "Error: Failed to query: $rpm\n";
		next;
	}
	foreach $item (@RPMOutput) {
		chomp($item);
		next if ($item =~ /^\s*warning:/i);
		next if ($item =~ /^\s*error:/i);
		$item =~ s/^(\S+)\s+.*/$1/; #Remove version
		if ( $Provides{$item} ) {
			$item= $Provides{$item};
		}
		next if ($item =~ /^\//); #File
		next if ($item =~ /^rpmlib\(.*\)/); #Rpm stuff
		next if ($item =~ /\.so(\.\d+)*(\(.*\))?$/); #Library
		next if CheckLocal($item); #Already have item
		next if (IsElement($item, @requirements));
		AddLocalRPMs("r", "$item-0.0-0.noarch.rpm"); #Pretend we have an older version
		push(@requirements, $item) ;
	}
}
print "  Requirements: `@requirements`\n" if ($Debug >1);

return @requirements;
}

######################
# Query rpms for what they provide.
######################

sub Get_Provides
{
my ($rpm, $item, $file);
my $local=0;
my $flags = "-qp";
unless (@_) { # Use installed rpms if no rpms given
	$local=1;
	$flags = "-q";
	open(RPM,"$RPM -qa --queryformat \"%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm\\n\"|") || die "Could not query local rpms ($!).";
	@_= <RPM>;
	close(RPM);	
}

print "  Getting provides:\n" if ($Debug >1);
foreach $rpm (@_) {
	print "    " . $rpm->rpmname . "\n" if ($Debug >1);
	if ($local) {
		chomp($rpm);
		$rpm= Header->new($rpm, "f");
		next unless ($rpm);
		$file= $rpm->name;
	} else 	{
		$file= $rpm->filename;
	}
	unless ( CallRPM($flags, "--provides", $file) ==0 ) {
		print STDERR "Error: Failed to query: " . $rpm->rpmname . "\n";
		next;
	}
	foreach $item (@RPMOutput) {
		chomp($item);
		next if ($item =~ /^\s*warning:/i);
		next if ($item =~ /^\s*error:/i);
		if ( $item =~ /^(\S+)/ ) {
			$item= $1;
			if ($item eq "kernel" and $rpm->name =~ "^kernel-") {
				$Provides{$item}= "kernel";
			} elsif ($rpm->name =~ /-debug(-.+)?$/ and $Provides{$item}) {
				# don't overwrite glibc with glibc-debug
			} else {
				$Provides{$item}= $rpm->name;
			}
		} else {
			print STDERR "Warning: Unknown provides: '$item'" if $Warnings;
		}
	}
	# Add executables as well
	unless ( CallRPM($flags, "--list", $file) ==0 ) {
		print STDERR "Error: Failed to query: " . $rpm->rpmname . "\n";
		next;
	}
	foreach $item (@RPMOutput) {
		chomp($item);
		next unless ($item =~ /^\/./);
		next unless ($item =~ /\/s?bin\/./ or $item =~ /^\/usr\/games\/[^\/]+$/);
		$Provides{$item}= $rpm->name;
	}
}

return 0;
}

######################
# Calls RPM and saves all output
######################

sub CallRPM
{

# I redirect the rpm output to a file rather than a pipe.
# If a daemon gets restarted in the post script, the pipe
# doesn't get closed until the daemon exists under some
# older versions of rpm. 

#pipe(R, W);

my $pid = fork;
die "Could not fork for RPM: $!\n" unless defined($pid);
if(!$pid) {
	# close(R);
	# Just to be sure
	if (-e $TmpFile) {
		unlink($TmpFile) || die("Could not remove $TmpFile");
	}
	sysopen(W, $TmpFile, O_WRONLY|O_CREAT|O_EXCL, 00600) || die "Could not create: $TmpFile ($!)";
	open(STDERR, ">&W") || die("Could not dup STDERR ($!)");
	open(STDOUT, ">&W") || die("Could not dup STDOUT ($!)");
	exec($RPM, @_) || die("Could not exec $RPM.");
	close(W);
	exit 1;
} else {
	#close(W);
	waitpid($pid,0);
	my $retval= $? >> 8;
	open(R,"< $TmpFile") || die("Could not open: $TmpFile ($!)");
	@RPMOutput= <R>;
	close(R);
	unlink($TmpFile);
	chomp(@RPMOutput);
	return $retval;
}

}

######################
# Execute post script
######################

sub Do_PostScript
{

return 0 unless $_[0];

my @script = split(/\s+/,$_[0]);
print "Executing post script:" if $Verbose;
print " @script:" if $Debug;
print "\n" if $Verbose;
unless ( system(@script)==0 ) {
	print STDERR "Warning: $script[0] failed: $!\n" if $Warnings;
	return 1;
	}

return 0
}

######################
# Log messages
######################

sub Do_Log
{

return 0 unless (@_);
return 0 if ($Test);

my $now= localtime();
if ($now =~ /^\S+\s+(.*)\s+\S+$/) {
	$now= $1;
}
print "Writing log file: $LogFile ($now)\n" if ($Debug);
if ($LogFile eq "syslog" ) {
	Sys::Syslog::setlogsock('unix');
	unless (openlog('autoupdate', 'cons,pid', 'user')) {
		print STDERR "Error: Could not open syslog: $!\n";
		return 1;
	}
	for my $item (@_) {
		syslog('info', $item);
	}
	closelog();
} else {
	unless (open(LOGFILE,">>$LogFile")) {
		print STDERR "Error: Could not append to $LogFile: $!\n";
		return 1;
	}
	for my $item (@_) {
		print LOGFILE "$now: $item\n";
	}
	close(LOGFILE);
}

return 0
}

######################
# Get local RPM list
# Store it in %LocalVersion
######################

sub Get_Local_List
{
my ($item);

if ( $_[0] ) {
	for $item (@_) {
		next unless ($item);
		print "Getting local rpms for comparison: $item\n" if $Debug;
		chdir($item) || die("Could not cd to: $item ($!)");
		AddLocalRPMs("f", Get_RPMs($item) );
	}
} else {
	if ($QueryDatabase) {
		print "Binding to rpm database.\n" if $Debug;
		if ( SetQueryRpmDatabase(1) == 1 ) {
			# Work around rpm-perl bug for kernel
			if ($DoKernel) {
				my @tmp=qw(kernel);
				foreach $item ( split(/,/,$KernelExt) ) {
					push(@tmp,"kernel-$item");
				}		
				open(RPM,"$RPM -q --queryformat \"%{NAME}-%{SERIAL}:%{VERSION}-%{RELEASE}.%{ARCH}.rpm\\n\" @tmp|") || die "Could not query local rpms ($!).";
				foreach $item ( <RPM> ) {
					chomp($item);
					next if ($item =~ /is not installed$/);
					# Set the type to "r" since querying the db might give wrong results
					AddLocalRPMs("r", $item);
				}
				close(RPM);
			}
			return;
		}
	}
	print "Getting installed rpms for comparison.\n" if $Debug;
	open(RPM,"$RPM -qa --queryformat \"%{NAME}-%{SERIAL}:%{VERSION}-%{RELEASE}.%{ARCH}.rpm\\n\"|") || die "Could not query local rpms ($!).";
	foreach $item ( <RPM> ) {
		chomp($item);
		AddLocalRPMs("l", $item);
	}
	close(RPM);
}

}

######################
# Get a list of all local rpms
######################

sub Get_RPMs
{
my @dirs = @_;
my ($dir, $item);
my @rpms = ();

print "  Getting list of local rpms:\n" if ($Debug>1);

foreach $dir (@dirs) {
	print "    $dir\n" if ($Debug>1);
	unless ( -d $dir ) {
		my $warn= 1;
		foreach $item (@NoWarnDirs) {
			if ($dir eq $item) {
				$warn= 0;
				last;
			}
		}
		print STDERR "Error: No such directory: $dir\n" if ($Debug or $warn);
		next;
	}
	Strip_Dir($dir, "1");
	if ($Recursive) {
		# Recursively descent through all of the sub-directories
		foreach $item (glob("$dir/*")) {
			if ( -d $item ) {
				if ( -l $item ) {
					$item= readlink($item);
					$item= "$dir/$item" unless ($item =~ /^\//);
					Strip_Dir($item,1);
				}
				# Count the number of subdirs
				if ( $item =~ tr@/@@ > $MaxRecursive ) {
					print STDERR "Warning: Maximum number of subdirs exceeded for: $item\n" if $Warnings;
				} elsif (! IsElement($item, @dirs) ) {
					push(@dirs, $item);
				}
			} elsif ( $item =~ /\.rpm$/ ) {
				push (@rpms,$item);
			} elsif ( $item =~ /autoprovides[^\/]*-[^\/\-]+\.db$/ ) {
				push (@DldDataBase,$item);
			}			
		}
	} else {
		push(@rpms,glob("$dir/*.rpm"));
		push(@DldDataBase,glob("$dir/autoprovides*.db"));
	}
}
	
return @rpms;
}

######################
# Get a list of all remote rpms (ftp)
######################

sub Get_RPMs_ftp
{

my $ftp = shift;
my @dirs = @_;
my ($dir, $item);
my @rpms = ();


foreach $item (@dirs) {
	if ($item =~ /^\.(\/.*)$/) {
		$item= $ftp->pwd() . $1;
	}
	if ($item eq ".") {
		$item= $ftp->pwd();
	}
}		

print "  Getting list of remote rpms (ftp):\n" if $Debug;

foreach $dir (@dirs) {
	print "    $dir\n" if $Debug;
	unless ( $ftp->cwd($dir) ) {
		my $warn= 1;
		foreach $item (@NoWarnDirs) {
			if ($dir eq $item) {
				$warn= 0;
				last;
			}
		}
		next unless ($Debug or $warn);
		print STDERR "Error: Failed to check directory at $Host: ";
		if ($ftp->message !~ /\Q$dir/) {
			print STDERR "$dir: ";
		}
		if ($ftp->message) {
			print STDERR $ftp->message;
		} else {
			print STDERR "Timeout (?)\n";
		}
		next;
	}
	if ($dir =~ /^(.*)\/$/) {
		$dir=$1;
	}
	if ($DldRecursive) {
		# Recursively descent through all of the sub-directories
		foreach $item ( $ftp->dir() ) {
			# |Attributes|HLs|Owner|Group|Size|Date|Name|
			my ($attr,$filename) = (split(/\s+/,$item))[0,-1];
			next if ($filename eq "." or $filename eq "..");
			if ($filename =~ /^autoprovides[^\/]*-[^\/\-]+\.db$/ and $attr !~ /^d/) {
				# database
				push(@DldDataBase, "$dir/$filename");
				next;
			}
			if ($filename =~ /\.rpm$/ and $attr !~ /^d/) {
				# RPM file
				push(@rpms, "$dir/$filename");
				next;
			}
			if ( $attr =~ /^d/ ) {
				# Directory
			} elsif ( $attr =~ /^l/ ) {
				# Link
				next unless ($ftp->cwd($filename));
				$ftp->cwd($dir);
			} else {
				next;
			}
			$filename="$dir/$filename";
			# Count the number of subdirs
			if ( $filename =~ tr@/@@ > $MaxRecursive ) {
				print STDERR "Warning: Maximum number of ftp subdirs exceeded for: $item\n" if $Warnings;
			} else {
				push(@dirs,$filename);
			}
		}
	} else {
		foreach $item ( $ftp->ls() ) {
			if ($item =~ /^autoprovides[^\/]*-[^\/\-]+\.db$/) {
				# database
				push(@DldDataBase, "$dir/$item");
			} elsif ($item and $item =~ /\.rpm$/) {
				push(@rpms, "$dir/$item");
			}	
		}
	}
}

return @rpms;
}

######################
# Get a list of all remote rpms (http/lwp)
######################

sub Get_RPMs_http
{

my @files = @_;
my ($dir, $file, $rfile, $item, @filelist);
my @rpms = ();

print "  Getting list of remote rpms ($Protocol):\n" if $Debug;

foreach $file (@files) {
	$file="/$file" unless ($file =~ /^\//);
	print "    $file\n" if $Debug;
	if ($UseLWP) {
		@filelist= Get_LWP($file, "p");
	} else {
		@filelist= Get_HTTP($file, "p");
	}
	$rfile= shift(@filelist);
	unless ($rfile) {
		my $warn= 1;
		foreach $item (@NoWarnDirs) {
			if ($file eq $item) {
				$warn= 0;
				last;
			}
		}
		print STDERR "Error: Failed to check $file at $Host: $DldResult\n" if ($Debug or $warn);
		next;	
	}
	$file= $rfile;
	if ($file =~ /^(.*\/)[^\/]*$/) {
		$dir=$1;
	}
	for $item (@filelist) {
		next unless ($item);
		$item =~ s/^$Protocol:\/\/$Host//;
		next if ($item =~ /^\w+:\/\//);
		next if ($item =~ "^mailto:");	
		$item= "$dir$item" unless ($item =~ /^\//);
		$item =~ s/#.*$//; #Remove comments
		$item =~ s/\/\?.*$/\//; #Remove queries
		Strip_Dir($item);
		if ($item =~ /autoprovides[^\/]*-[^\/\-]+\.db$/) {
			push(@DldDataBase,$item) unless (IsElement($item, @DldDataBase));
			next;
		}
		if ($item =~ /\.rpm$/) {
			push(@rpms,$item) unless (IsElement($item, @rpms));
			next;
		}
		next unless $DldRecursive;
		next if ($item !~ /^$dir/);
		my $maxrecursive = $MaxRecursive + ($dir =~ tr@/@@);
		if ($item =~ /\/$/) {
			# Count the number of subdirs
			if ( $item =~ tr@/@@ > $maxrecursive ) {
				print STDERR "Warning: Maximum number of subdirs exceeded for: $item\n" if $Warnings;
			} else {
				push(@files, $item) unless (IsElement($item, @files));
			}
			next;
		}
		foreach my $ext (split(/,/,$HTTPExt)) {
			if ($item =~ /\.$ext$/) {
				if ( $item =~ tr@/@@ > $maxrecursive ) {
					print STDERR "Warning: Maximum number of subdirs exceeded for: $item\n" if $Warnings;
				} else {
					push(@files, $item) unless (IsElement($item, @files));
				}
				last;
			}					
		}
	}
}

return @rpms;
}

######################
# Get a file via LWP
# Saves a file to disk resp. parses html files for links
######################

sub Get_LWP
{
my $location= shift;
my $action= shift; # s=save p=parse
my ($file, $res, $url);
$DldResult= "";
unless ($location =~ /^\//) {
	$location= "/" . $location;
}

$url = "$Protocol://$Host:$Port$location";

print "LWP: Getting url: $url\n" if ($DebugLWP);
my $req = HTTP::Request->new( GET => $url );
if ($User) {
	$req->authorization_basic($User, $Pass);
}

if ($action eq "s") {  # Store rpms
	return undef unless ($location =~ /([^\/]+)$/);
	$file = $1;
	print "\nLWP: Saving data: $file\n" if ($DebugLWP);
	$res = $LWPua->request( $req, $file );
} else {
	$file= undef;
	$req->header(Accept => "text/html");
	$res = $LWPua->request( $req );
}

if( $res->is_success ) {
	print "LWP: Received url: $url\n" if ($DebugLWP);
	return $file if (defined $file);
} else {
	print "LWP: $url: " . $res->status_line . "\n" if ($DebugLWP);
	$DldResult= $res->status_line;
	return undef;
}

# Parse data
print "\nLWP: Parsing data:\n" if ($DebugLWP);
   
$file = $res->base->as_string;
$file =~ s/^[a-z]+:\/\/[^\/]+(\/.*)$/$1/i;
my @files = ($file);
my($data, $hrefMask) = $res->content;
   
foreach $hrefMask ('<a [^>]*href="([^"]+)"', '<a [^>]*href=([^" >]+)' ) {
	while( $data =~ /$hrefMask/ig ) {
		push @files, $1;
		print "LWP: HREF: $1\n" if ($DebugLWP);
	}
}  
print "LWP: Found " . (scalar(@files)-1) . " links.\n" if ($DebugLWP);

return @files;
}

######################
# Get a file via http
# Saves an rpm to disk resp. parses html files for links
######################

sub Get_HTTP
{
my $location= shift;
my $action= shift; # s=save p=parse
my ($file, $result, $host, $port, $header, $type, $length, $socket, $line, $buf, $buff, $n);
my $maxredirect= $MaxRedirect;
my $EOL = "\015\012";
$DldResult= "";

if ($Protocol eq "http") {
	$host=$Host;
	$port=$Port;
} else { #https
	$host="localhost";
	$port=$StunnelPort;
}

do { 
	$file= $location;
	$socket = IO::Socket::INET->new(PeerAddr => $host, 
			    PeerPort => $port,
			    Proto    => 'tcp');
	unless ($socket) {
		print STDERR "Error: Failed to connect to $host.\n";
		$@ =~ s/IO::Socket::INET/HTTP errror/;
		print STDERR "$@\n";
		return undef;
	}
	$socket->autoflush(1);
	#$socket->sockopt(MSG_WAITALL,1);

	# Send header
	$header= "GET $file HTTP/1.0" . $EOL;
	if ($HTTPSendHost) {
		$header= $header . "Host: $host" . $EOL;
	}
	$header= $header . $EOL;
	$n= length($header);
	unless (syswrite($socket,$header,$n) == $n) {
		close($socket);
		print STDERR "Error: Sending http request: $file ($!)\n";
		return undef;
	}

	# Receive data
	unless ( sysread($socket,$buf,$BufSize) ) {
		close($socket);
		print STDERR "Error: Reading http data: $file ($!)\n";
		return undef;
	}

	if ($buf =~ /^HTTP\S+\s+(\d+[^\n]*)\r\n(.*)$/s ) {
		$result= $1;
		$buf= $2;
	} else {
		close($socket);
		print STDERR "Error: Malformed http header: $file\n";
		return undef;
	}

	if ($result !~ "^(200|30)") {
		close($socket);
		$DldResult= $result;
		print "HTTP: $Protocol://$host$file: $result\n"  if ($DebugHTTP);
		return undef;
	}

	print "HTTP:Result: $result\n" if ($DebugHTTP);
	my $max= 100;
	my $done= 0;
	do {
		$max--;
		if ($buf =~ /^([^\n]*)\r\n(.*)$/s) {
			$line= $1;
			$buf= $2;
			print "HTTP:Head: $line\n" if ($line and $DebugHTTP);
			if ($line =~ /^Content-Type:\s+(\S+)/) {
				$type=$1;
			}
			if ($line =~ /^Content-Length:\s+(\d+)/) {
				$length=$1;
			}
			if ($line =~ /^Location:\s+(\S+)/) {
				$location=$1;
			}
			unless ($buf) {
				$max=0 unless (sysread($socket,$buff,$BufSize));
				$buf= $buf . $buff;
			}
			$done= 1 unless ($line);
		} else {
			$max =0;
		}
	} until ($done or $max<=0);

	if ($max <=0) {
		print STDERR "Error: Reading http header: $file\n";
		$@ =~ s/IO::Socket::INET/HTTP errror/;
		print STDERR "$@\n" if $@;
		close($socket);
		return undef;
	}
	unless ($type) {
		print STDERR "Warning: No content type: $file\n" if $Warnings;
		$type="unkown";
	}

	if ($result =~ "^30") {
		close($socket);
		$maxredirect--;
		unless ($maxredirect >= 0) {
			print STDERR "Warning: Maximum number of redirects exceeded at $host\n";
			return undef;	
		}
		if ($location !~ /^$Protocol:\/\/$Host[^\/]*(\/.*)$/) {
			print STDERR "Warning: Not following redirect: $location\n";
			return undef;
		}
		$location=$1;
		print "HTTP: Redirect: $Host:$Port$location\n" if ($DebugHTTP);		
	} else {
		$location= "";
	}

} until (! $location);

if ($action eq "s") {
	# Store rpms
	print "\nHTTP:Saving data: $file\n" if ($DebugHTTP);
	$file= $1;
	if ($type =~ /^text/) {
		print "Error: Wrong content type: $type\n";
		close($socket);
		return undef;
	}
	unless (open(FILE,">$file")) {
		print STDERR "Error: Opening file ($!)\n";
		close($socket);
		return undef;
	}
	my $received= length($buf);
	print FILE $buf;
	do {
		$n= sysread($socket,$buf,$BufSize);
		unless (defined($n)) {
			print STDERR "Error: Reading http data ($!).\n";
			close(FILE);
			close($socket);
			return undef;
		}
		$received+= $n;
		print FILE $buf;
	} until ($n == 0);
	close(FILE);
	close($socket);
	print "HTTP: Received $received bytes of data.\n" if ($DebugHTTP);
	if (defined $length and $length ne $received) {
		print STDERR "Warning: Received bytes ($received) does not match content length ($length)!\n" if $Warnings;
	}
	return $file;
}

# Parse data

print "\nHTTP:Parsing data:\n" if ($DebugHTTP);
if ($type !~ /^text\/html/) {
	print "Error: Wrong content type: $type\n";
	close($socket);
	return undef;
}

my $received= length($buf);
my @files= ($file);
do {
	# We look only for <a href="...">
	if ($buf =~ /(<A\s+.*)/si) {
		$buf = $1;
	} elsif ($buf =~ /(<A|<)$/i ) {
		$buf = $1
	} else {
		$buf ="";
	}
	if ($buf =~ /^<A\s+HREF="([^"]+)"(.*)$/is) {
		push(@files, $1);
		$buf= $2;
		$buf= " " unless ($buf);
		print "HTTP:HREF:$1\n" if ($DebugHTTP);
	} else {
		$n= sysread($socket,$buff,$BufSize);
		if (defined($n)) {
			if ($n>0) {
				$received+= $n;
				$buf= $buf . $buff;
			} else {
				$buf= "";
				print "HTTP: Finished reading data.\n" if ($DebugHTTP);

			}
		} else {
			print "Error: Reading http data ($!).\n";
			close($socket);
			return undef;
		}
	}
} until (! $buf);

close($socket);
print "Received $received bytes of data.\n" if ($DebugHTTP);
print "HTTP: Found " . (scalar(@files)-1) . " links.\n" if ($DebugHTTP);

if (defined $length and $length ne $received) {
	print STDERR "Warning: Received bytes ($received) does not match content length ($length)!\n" if $Warnings;
}

return @files;
}

######################
# Read in configuration.
######################

sub Read_Config
{
my ($val, $var);
my $file = shift;
@DldConfig=();

return 0 unless $file;
print "Reading config file: $file\n" if ($Debug >1);

$val=(stat($file))[2];
if ( $val and ($val >> 1)%2 ) {
	die("Fatal: Configuration file '$file' is world writeable!");
	return 1;
}

unless (open(FILE,"<$file")) {
  print STDERR "Error: Could not open: $file ($!)\n";
  return 1;
}

while (<FILE>) {
	chomp;
	next if ( $_ =~ /^#/ );
	next unless ($_ =~/\s*(\w+)\s*=\s*(.*)/);
	($var, $val)= ($1, $2);
	$var =~ s/DoFTP/DoDld/; # Old syntax
	$var =~ s/FTPConfig/DldConfig/; # Old syntax
	$var =~ s/FTPGetInstalled/DldGetInstalled/; # Old syntax
	$var =~ s/PostFTPScript/PostDldScript/; # Old syntax
	$var =~ s/MatchFTP/DldMatch/; # Old syntax
	$var =~ s/MatchMerge/MergeMatch/; # Old syntax
	$var =~ s/Recurse/Recursive/; # Old syntax
	$var =~ s/(Update|RPM)s/$1/; # Old syntax
	$var =~ s/^Dir(.+)$/$1Dir/; # Old syntax
	$var =~ s/UpdateAll/Install/; # Old syntax
	$var =~ s/UpdateKernel/Kernel/; # Old syntax
	$var =~ s/Lilo/Boot/; # Old syntax
	$val="" unless (defined($val));
	if ($val =~ /^`(.+)`$/) {
		if ($ShellEscapes) {
			chomp($val = `$1`);
		} else {
			print STDERR "Warning: Shell escapes disabled ('$var').\n" if $Warnings;
			next;
		}
	} 
	if ( $var eq "ShellEscapes") {
		$ShellEscapes = 0 unless ($val);
	} elsif ( $var eq "Debug" and $val =~/^[0-9]$/ ) {
		$Debug = $val;
	} elsif ( $var eq "Verbose" ) {
		$Verbose = $val;
	} elsif ( $var eq "Quiet" ) {
		$Quiet = $val;
	} elsif ( $var eq "Warnings" ) {
		$Warnings = $val;
	} elsif ( $var eq "RPMNameWarnings" ) {
		$DefaultRPMNameWarnings = $val;
	} elsif ( $var eq "FixRPMNames" ) {
		$DefaultFixRPMNames = $val;
	} elsif ( $var eq "DoDld" ) {
		$DoDld = $val;
	} elsif ( $var eq "DoUpdate" ) {
		$DoUpdate = $val;
	} elsif ( $var eq "DoInstall" ) {
		$DoInstall = $val;
	} elsif ( $var eq "DoKernel" ) {
		$DoKernel = $val;
	} elsif ( $var eq "DoInitRD" ) {
		$DoInitRD = $val;
	} elsif ( $var eq "DoBoot" ) {
		$DoBoot = $val;
	} elsif ( $var eq "DoMerge" ) {
		$DoMerge = $val;
	} elsif ( $var eq "MergeAll" ) {
		$MergeAll = $val;
	} elsif ( $var eq "DoPurge" ) {
		$DoPurge = $val;
	} elsif ( $var eq "MergeMatch" ) {
		$MergeMatch = $val;
	} elsif ( $var eq "DldMatch" ) {
		$DldMatch = $val;
	} elsif ( $var eq "HTTPExt" and $val =~ /^\w+(,\w+)*$/) {
		$HTTPExt = $val;
	} elsif ( $var eq "QueryHeaders" ) {
		$QueryHeaders = $val;
	} elsif ( $var eq "QueryDatabase" ) {
		$QueryDatabase = $val;
	} elsif ( $var eq "CleanUp" ) {
		$CleanUp = $val;
	} elsif ( $var eq "CleanUpKernel" ) {
		$CleanUpKernel = $val;
	} elsif ( $var eq "BootManager" ) {
		$BootManager = $val;
	} elsif ( $var eq "BootConf" ) {
		$BootConf = $val;
	} elsif ( $var eq "BootScript" ) {
		$BootScript = $val;
	} elsif ( $var eq "BootAddAsNew" ) {
		$BootAddAsNew = $val;
	} elsif ( $var eq "BootTag" ) {
		$BootTag = $val;
	} elsif ( $var eq "UseBootTag" ) {
		$UseBootTag = $val;
	} elsif ( $var eq "KernelExt" and $val =~ /^\w+(,\w+)*$/) {
		$KernelExt = $val;
	} elsif ( $var eq "RemoveBad" ) {
		$RemoveBad = $val;
	} elsif ( $var eq "BestMatch" ) {
		$BestMatch = $val;
	} elsif ( $var eq "CheckSig" ) {
		$CheckSig = $val;
	} elsif ( $var eq "Repackage" ) {
		$Repackage = $val;
	} elsif ( $var eq "CheckGPG" ) {
		$CheckGPG = $val;
	} elsif ( $var eq "Resolve" ) {
		$Resolve = $val;
	} elsif ( $var eq "DataBase" ) {
		$DataBase = $val;
	} elsif ( $var eq "DataBaseName" ) {
		$DataBaseName = $val;
	} elsif ( $var eq "DoLog" ) {
		$DoLog = $val;
	} elsif ( $var eq "DoLock" ) {
		$DoLock = $val;
	} elsif ( $var eq "LogFile" ) {
		$LogFile = $val;
	} elsif ( $var eq "UpdateDir" ) {
		$UpdateDir = $val;
	} elsif ( $var eq "InstallDir" ) {
		$InstallDir = $val;
	} elsif ( $var eq "RPMDir" ) {
		$RPMDir = $val;
	} elsif ( $var eq "Recursive") {
		$Recursive = $val;
	} elsif ( $var eq "PostUpdateScript" ) {
		$PostUpdateScript = $val;
	} elsif ( $var eq "PostDldScript" ) {
		$PostDldScript = $val;
	} elsif ( $var eq "DistVersion" ) {
		$DistVersion = $val;
	} elsif ( $var eq "DistArch" ) {
		SetDistArch($val);
	} elsif ( $var eq "DefaultArch" ) {
		SetArch($val);
	} elsif ( $var eq "DefaultUser" ) {
		$DefaultUser = $val;
	} elsif ( $var eq "DefaultPass" ) {
		$DefaultPass = $val;
	} elsif ( $var eq "UseLWP" ) {
		$DefaultUseLWP = $val;
	} elsif ( $var eq "LWPProtocols" ) {
		$LWPProtocols = $val;
	} elsif ( $var eq "HTTPSendHost" ) {
		$DefaultHTTPSendHost = $val;
	} elsif ( $var eq "Passive" ) {
		$DefaultPassive = $val;
	} elsif ( $var eq "FTPRetry" and $val =~/^[0-9]+$/) {
		$DefaultFTPRetry = $val;
	} elsif ( $var eq "FTPWait" and $val =~/^[0-9]+$/) {
		$DefaultFTPWait = $val;
	} elsif ( $var eq "FTPFirewall") {
		$Firewall = $val;
	} elsif ( $var eq "DldGetInstalled" ) {
		$DldGetInstalled = $val;
	} elsif ( $var eq "DldUseDB" ) {
		$DldUseDB = $val;
	} elsif ( $var eq "DldConfigDir" ) {
		$DldConfigDir = $val;
	} elsif ( $var eq "DldConfig" ) {
		push(@DldConfig, $val) if $val;
	} elsif ( $var eq "Exclude" ) {
		push(@UpdExclude, $val) if $val;
	} elsif ( $var eq "Include" ) {
		push(@UpdInclude, $val) if $val;
	} else {
		print STDERR "Warning: Unknown variable '$var' or bad value '$val' in $file.\n" if $Warnings;
	}
}
close (FILE);

return 0;
}

######################
# Read in dld configuration.
######################

sub Read_Dld_Config
{
my ($val, $var, $item);
my $BaseDir= "";
my $file = shift;
my @AllowedArch;
my $Arch= &GetDistArch;
if ($BestMatch) {
	@AllowedArch= &GetAllowedArch;
} else {
	@AllowedArch= &GetAllowedDistArch;
}

if (-d $ConfigDir) {
	chdir($ConfigDir) || die("Fatal: Could not cd to: $ConfigDir ($!)");
}
if ( -f $file ) {
	# fine
} elsif ( -f "$file.dld") {
	$file = "$file.dld";
} elsif ( -f "$file.get") {
	$file = "$file.get";
} elsif ( -f "$file.ftp") {
	$file = "$file.ftp";
} elsif ( $cwd and -f "$cwd/$file") {
	$file = "$cwd/$file";
}

$Host= "";
$User= "";
$Pass= "";
$Protocol= "ftp";
$DldAll= 0;
$DldRecursive= 0;
$FTPRetry= $DefaultFTPRetry;
$FTPWait= $DefaultFTPWait;
$HTTPSendHost= $DefaultHTTPSendHost;
$UseLWP= $DefaultUseLWP;
$Passive= $DefaultPassive;
$StoreDir= $UpdateDir;
$RPMNameWarnings= $DefaultRPMNameWarnings;
$FixRPMNames= $DefaultFixRPMNames;
@DldDirs= ();
@NoWarnDirs= ();
if ( $DldMatch ) {
	@Exclude= @UpdExclude;
	@Include= @UpdInclude;
} else {
	@Exclude= ();
	@Include= ();
}

print "\nReading dld config file: $file\n" if $Debug;

$val=(stat($file))[2];
if ( $val and ($val >> 1)%2 ) {
	print STDERR "Warning: Skipping world writeable configuration file: $file\n" if $Warnings;
	return 1;
}

unless (open(FILE,"<$file")) {
  print STDERR "Error: Could not open: $file ($!)\n";
  return 1;
}

while (<FILE>) {
	chomp;
	next if ( $_ =~ /^#/ );
	next unless ($_ =~/\s*(\w+)\s*=\s*(.*)/);
	($var, $val)= ($1, $2);
	$var =~ s/FTPAll/DldAll/; # Old syntax
	$var =~ s/FTPRecurse/DldRecursive/; # Old syntax
	$var =~ s/DldRecurse/DldRecursive/; # Old syntax
	$var =~ s/^Dir(.+)$/$1Dir/; # Old syntax
	$val="" unless (defined($val));
	if ($val =~ /^`(.+)`$/) {
		if ($ShellEscapes) {
			chomp($val = `$1`);
		} else {
			print STDERR "Warning: Shell escapes disabled ('$var') in $file.\n" if $Warnings;
			next;
		}
	}
	if ( $var eq "Host" ) {
		$Host = $val;
	} elsif ( $var eq "Protocol" ) {
		$Protocol = lc($val);
	} elsif ( $var eq "User" ) {
		$User = $val;
	} elsif ( $var eq "Pass" ) {
		$Pass = $val;
	} elsif ( $var eq "Passive" ) {
		$Passive = $val;
	} elsif ( $var eq "RPMNameWarnings" ) {
		$RPMNameWarnings = $val;
	} elsif ( $var eq "FixRPMNames" ) {
		$FixRPMNames = $val;
	} elsif ( $var eq "DldAll" and $val =~/^[0-2]$/) {
		$DldAll = $val;
 	} elsif ( $var eq "DldRecursive" ) {
 		$DldRecursive = $val;
	} elsif ( $var eq "UseLWP") {
		$UseLWP = $val;
	} elsif ( $var eq "HTTPSendHost") {
		$HTTPSendHost = $val;
	} elsif ( $var eq "FTPRetry" and $val =~/^[0-9]+$/) {
		$FTPRetry = $val;
	} elsif ( $var eq "FTPWait" and $val =~/^[0-9]+$/) {
		$FTPWait = $val;
	} elsif ( $var eq "StoreDir" ) {
		$StoreDir = $val if $val;
		$StoreDir=~ s/#DistVersion#/$DistVersion/g if ($DistVersion);
		$StoreDir=~ s/#DistArch#/$DistArch/g if ($DistArch);
	} elsif ( $var eq "BaseDir" ) {
		if ($val =~ /^(.*)\/$/) {
			$BaseDir = $1;
		} else {
			$BaseDir= $val;
		}
	} elsif ( $var eq "Dir" ) {
		if ($BaseDir and $val !~ /^\//) {
			$val= "$BaseDir/$val";
		}
		if ( $Protocol eq "ftp" and $val=~ /[^\/]\/$/ ) { #Old syntax
			print STDERR "Warning: Please use \"dir//\" instead of \"dir/\" to check subdirs in $file.\n" if $Warnings; #FIXME
			$val= $val . "/";
		}
		$val=~ s/#DistVersion#/$DistVersion/g if ($DistVersion);
		$val=~ s/#DistArch#/$DistArch/g if ($DistArch);
		if ( $val=~ /^(.+\/)\/$/ ) {
			$val=$1;
			for $item (@AllowedArch) {
				push(@NoWarnDirs, "$val$item/") unless ($item eq $Arch);
				push(@DldDirs, "$val$item/");
			}
		} else {
			push(@DldDirs, $val);
		}
	} elsif ( $var eq "Exclude" ) {
		push(@Exclude, $val) if $val;
	} elsif ( $var eq "Include" ) {
		push(@Include, $val) if $val;
	} else {
		print STDERR "Warning: Unknown variable '$var' or bad value '$val' in $file.\n" if $Warnings;
	}
}
close (FILE);
$RPMNameWarnings =1 if $Debug;

unless ($Host) {
	print STDERR "Error: No host given in $file.\n";
	return 1;
}
unless (@DldDirs) {
	print STDERR "Error: No dld directories given in $file.\n";
	return 1;
}

return 0;
}

######################
# Remove bad RPMs from a directory.
######################

sub Remove_Bad
{
my ($item, $rpm);
my $retval= 0;
my @badrpms= ();
my @goodrpms= ();

print "\nRemoving bad rpms.\n" if $Debug;

foreach $item (@AllRPMs) {
	next if ( IsElement($item, @badrpms) );
	next if ( CallRPM("--checksig", "--nogpg", "--nopgp", $item->filename) ==0 );
	my $tmp= $item->name . "-" . $item->version;
	foreach $rpm (@AllRPMs) {
		next if ( IsElement($rpm, @badrpms) );
		if ($tmp eq $rpm->name . "-" . $rpm->version) {
			push(@badrpms, $rpm);
		}
	}
}

return 0 unless (@badrpms);

print "Removing bad rpms:\n" unless $Quiet;
foreach $item (@AllRPMs) {
	unless (IsElement($item, @badrpms)) {
		push(@goodrpms,$item);
		next;
	} 
	print "  ". $item ->rpmname."\n" unless $Quiet;
	unless ($Test) {
		unless (unlink($item->filename)) {
			print STDERR "Error: Failed to remove: " . $item->filename . " ($!)\n";
			$retval++;
		}
	}
}

@AllRPMs= @goodrpms;

return $retval;
}


######################
# Purge old RPMs from updates.
######################

sub Purge_RPMs
{
my ($item, @newrpms, @oldrpms);
my $retval=0;
my $cl= $CheckLocal;
$cl= 2 if ($CleanUp and !$cl);

print "\nPurging rpms.\n" if $Debug;

SetLocalRPMs();
if ($cl) {
	# Get a list of rpms we already have
	if ($RPMDir) {
		Get_Local_List($RPMDir);
	} else {
		Get_Local_List();
	}
}

@newrpms= SelectRPMs(\@AllRPMs, $BestMatch, $cl);
@oldrpms= ();

foreach $item (@AllRPMs) {
	push(@oldrpms,$item) unless (IsElement($item, @newrpms));
}
return 0 unless (@oldrpms);

print "Purging rpms from $UpdateDir:\n" unless $Quiet;
chdir($UpdateDir) || die("Fatal: Could not cd to: $UpdateDir ($!)");
foreach $item (@oldrpms) {
	print "  ". $item->rpmname . "\n" unless $Quiet;
	unless ($Test) {
		unless (unlink($item->filename)) {
			print STDERR "Error: Failed to remove: " . $item->filename . "($!)\n";
			$retval++;
		}
	}
}

@AllRPMs=@newrpms;

return $retval;
}

######################
# Merge new RPMs into dist.
######################

sub Merge_RPMs
{
my ($item, $name, $cl, @newrpms, @oldrpms);
my $retval=0;

print "\nMerging rpms:\n" if $Debug;

unless ( $RPMDir ) {
	print STDERR "Error: No distribution directory.\n";
	return 1;
	}
unless ( -d $RPMDir ) { die("Fatal: No such directory: $RPMDir"); }
if ($UpdateDir eq $RPMDir) {
	print STDERR "Error: Update and distribution directory are equal!\n";
	return 1;
}

if ( $MergeMatch ) {
	@Exclude= @UpdExclude;
	@Include= @UpdInclude;
} else {
	@Exclude= ();
	@Include= ();
}

unless ($CleanUp or ($DoPurge and $CheckLocal)) { #Already have it from purge if CleanUp
	# Get a list of rpms we already have
	SetLocalRPMs();
	Get_Local_List($RPMDir);
}

if ($MergeAll) {
        $cl= 0;
} else {
        $cl= 1;
}

@newrpms= ();
foreach $item ( SelectRPMs(\@AllRPMs, 0, $cl) ) {
	push(@newrpms, $item) unless IsExcluded($item);
}

return 0 unless (@newrpms);

print "Merging rpms into $RPMDir:\n" unless $Quiet;
chdir($UpdateDir) || die("Fatal: Could not cd to: $UpdateDir ($!)");
foreach $item ( @newrpms ) {
	print "  " . $item->rpmname . "\n" unless $Quiet;
	unless (Check_Sig($item)==0) {
		$retval++;
		next;
	}
	next if ($Test);
	$name=$item->name;
	if (CheckLocal($name)) {
		unlink glob("$RPMDir/$name-" . CheckLocal($name) . ".*.rpm");
	}
	if ($CleanUp) {
		unless (move($item->filename, $RPMDir)) {
			print STDERR "Error: Failed to move: " . $item->filename . " ($!)\n";
			$retval++;
		}
	} else {
		unless (copy($item->filename, $RPMDir)) {
			print STDERR "Error: Failed to copy: " . $item->filename . " ($!)\n";
			$retval++;
		}
	}
}

if ($CleanUp) {
	@oldrpms= ();
	foreach $item (@AllRPMs) {
		push(@oldrpms,$item) unless (IsElement($item, @newrpms));
	}
	@AllRPMs= @oldrpms
}

return $retval;
}

######################
# Get RPMs from remote site
######################

sub Get_Remote_RPMs
{
my ($ftp, $dir, $subdir, $item, $file, $tmp, @AllDldRPMs, @DldRPMs);
my @requirements= ();
my $retval=0;

if ($Protocol eq "file") {
	$Port= 0;
} else {
	$Port= getservbyname($Protocol,"tcp");
}

# Check for port
unless ($Port) { #Just to be sure
	$Port= 0;
	$Port= 80 if ($Protocol eq "http");
	$Port= 443 if ($Protocol eq "https");
	$Port= 21 if ($Protocol eq "ftp");
}
if ( $Host =~ /^(.*):(\d+)$/ ) {
	$Host=$1;
	$Port=$2;
}

#Get all new rpms from the dld site
if ($User) {
	print "Fetching rpms from '$Protocol://$User\@$Host'.\n" if $Debug;
} else {
	print "Fetching rpms from '$Protocol://$Host'.\n" if $Debug;
	if ($Protocol eq "ftp") {
		($User, $Pass) = ($DefaultUser, $DefaultPass);
		print "User='$User' Pass='$Pass'.\n" if ($Debug >1);
	}
}
print "Using port: $Port. Passive=$Passive.\n" if ($Debug >1);

@DldDataBase= ();

Strip_Dir($StoreDir);
print "Will store them in '$StoreDir'.\n" if ($Debug >1);
chdir($StoreDir) || die("Fatal: Could not cd to: $StoreDir ($!)");
unless ( ($StoreDir eq $UpdateDir) or ($Recursive and $StoreDir !~ /^$UpdateDir/) ) {
	Get_Local_List($StoreDir);
}

unless ( IsElement($Protocol, split(/,/,$LWPProtocols)) ) {
	$UseLWP =0;
}


if ($UseLWP) {
	# Set up LWP
	unless ($LWPua) {
		require LWP::UserAgent;
		$LWPua = LWP::UserAgent->new;
		$LWPua->env_proxy();        
	}
	@AllDldRPMs= GetRPMHeaders("r", Get_RPMs_http(@DldDirs) );
} elsif ($Protocol eq "file") {
	$Host="localhost";
	$Port="0";
	my $RecursiveSave= $Recursive;
	$Recursive= $DldRecursive;
	@AllDldRPMs= GetRPMHeaders("f", Get_RPMs( @DldDirs) );
	$Recursive= $RecursiveSave;
} elsif ($Protocol eq "ftp") {
	unless ($FTPRetry >= 0) {
		$FTPRetry= 0;
	}
	$FTPRetry++;
	while ( ! ($ftp = Net::FTP->new($Host, Debug => $DebugFTP, Passive => $Passive, Firewall => $Firewall, Port => $Port)) ) {
		$FTPRetry--;
		if ($FTPRetry <= 0) {
			print STDERR "Error: Failed to connect to $Host.\n";
			$@ =~ s/Net::FTP/FTP errror/;
			print STDERR "$@\n";
			return 1;
		} else {
			print "Could not connect to $Host. Will try ". $FTPRetry ." more time(s).\n" if $Verbose;
			$@ =~ s/Net::FTP/FTP errror/;
			print "$@\n" if $Debug;
			sleep $FTPWait;
		}
	}

	while ( ! $ftp->login($User,$Pass) ) {
		$FTPRetry--;
		if ($FTPRetry <= 0) {
			print STDERR "Error: Failed to login at $Host: ", $ftp->message;
			return 1;
		} else {
			print "Could not login to $Host. Will try ". $FTPRetry ." more time(s).\n" if $Verbose;
			print "FTP message: ", $ftp->message if $Debug;
			sleep $FTPWait;
		}
	}

	$ftp->binary();
	@AllDldRPMs= GetRPMHeaders("r", Get_RPMs_ftp($ftp,@DldDirs) );
} elsif ( $Protocol eq "http" ) {
	@AllDldRPMs= GetRPMHeaders("r", Get_RPMs_http(@DldDirs) );
} elsif ( $Protocol eq "https" ) {
	# We use stunnel
	unless ( -x $STUNNEL ) {
		print STDERR "Error: stunnel is needed for https ($STUNNEL)\n";
		return 1;
	}
	$StunnelPid=0;
	my @args= @StunnelArgs;
	push(@args, "$Host:$Port");

	unless ( system($STUNNEL, @args) ==0 ) {
		print STDERR "Error: Could not execute $STUNNEL ($!)\n";
		return 1;
	}
	print STDERR "Waiting for stunnel.\n" if ($Debug>1);
	my $n= 10;
	my $file="$Host.$Port.pid";
	$file=~ s/-/./;
	$file="$StunnelPidFile.$file";
	while ( $n >0 ) {
		$n--;
		if ( -f $file ) {
			$n=0;
			if ( open(FILE,"<$file") ) {
				$StunnelPid=<FILE>;
				close(FILE);
			} else {
				print STDERR "Error: Could not read $file\n";
			}
		} else {
			sleep 1;
		}
	}
	if ($StunnelPid) {
		print STDERR "Started stunnel (pid=$StunnelPid,port=$StunnelPort)\n" if ($Debug>1);
	} else {
		print "Error: Failed to start stunnel for $Host!\n";
		return 1;
	}
	@AllDldRPMs= GetRPMHeaders("r", Get_RPMs_http(@DldDirs) );
} else {
	print STDERR "Error: Unsupported protocol: $Protocol://$Host\n";
	return 1;
}

# select new provides databases
if ($DldUseDB and @DldDataBase) {
	print "Selecting provides databases:\n" if $Debug;
	my @db= ();
	for $MergeDB (@DldDataBase) {
		print "  $MergeDB " if ($Debug>1);
		unless ( $MergeDB =~ /^(.*autoprovides[^\/]*)-([^\/\-]+)\.db$/) {
			print "(ignored)\n" if ($Debug>1);
			next;
		}
		my ($name, $ver) = ($1, $2);
		$name= "$Host:$Port:$name";
		my $dbver= $Provides{"##$name##"};
		unless ($dbver){
			print "(new)\n" if ($Debug>1);
			push(@db,$MergeDB);
			next;
		}
		my $tmp1= Header->new("foo-$ver-1.noarch.rpm", "r");
		my $tmp2= Header->new("foo-$dbver-1.noarch.rpm", "r");
		if ( $tmp1->compare($tmp2) ==1 ){
			print "(new)\n" if ($Debug>1);
			push(@db,$MergeDB);
		} else {
			print "(old)\n" if ($Debug>1);
		}
	}
	@DldDataBase= @db;
} else {
	@DldDataBase= ();
}


if (@DldDataBase) {
	untie(%Provides);
	if (tie(%Provides, 'DB_File', $DataBase, O_RDWR, 0)) {
		my %NewProvides;
		print "Merging provides database:\n" if $Verbose;
		for $MergeDB (@DldDataBase) {
			print "  $MergeDB\n" unless $Quiet;
			next if ($Test);
			if ($UseLWP) {
				unless (Get_LWP($MergeDB, "s")) {
					print STDERR "Error: Failed to get: $MergeDB ($DldResult)\n";
					$retval+=1;
				}
			} elsif ($Protocol eq "file") {
				unless (copy($MergeDB, ".")) {
					print STDERR "Error: Failed to copy: $MergeDB ($?)\n";
					$retval+=1;
				}
			} elsif ($Protocol eq "ftp") {
				unless ($ftp->get($MergeDB)) {
					print STDERR "Error: Failed to ftp: ";
					if ($ftp->message !~ /\Q$MergeDB/) {
						print STDERR "$MergeDB: ";
					}
					if ($ftp->message) {
						print STDERR $ftp->message;
					} else {
						print STDERR "Timeout (?)\n";
					}
					$retval+=1;
				}
			} elsif ($Protocol eq "http" or $Protocol eq "https") {
				unless (Get_HTTP($MergeDB, "s")) {
					print STDERR "Error: Failed to get: $MergeDB ($DldResult)\n";
					$retval+=1;
				}
			}
			next unless ( $MergeDB =~ /^(.*autoprovides[^\/]*)-([^\/\-]+)\.db$/);
			my ($name, $ver) = ($1, $2);
			$name= "$Host:$Port:$name";
			if ($MergeDB =~ /(autoprovides[^\/]*-[^\/\-]+\.db)$/) {
				$MergeDB= $1;
			}
			next unless (-f $MergeDB );
			if (tie(%NewProvides, 'DB_File', $MergeDB, O_RDONLY, 0)) {
				$Provides{"##$name##"}= $ver;
				for $item (keys %NewProvides) {
					$Provides{$item}= $NewProvides{$item};
				}
				untie(%NewProvides);
				unlink($MergeDB);
			} else {
				print STDERR "Error: Could not open $MergeDB: $!\n";
			}
		}
		untie(%Provides);
	} else {
		print STDERR "Error: Could not add to $DataBase: $!\n";
	}
	#Open database ro again
	unless (tie(%Provides, 'DB_File', $DataBase, O_RDONLY, 0)) {
		print STDERR "Error: Could not open $DataBase: $!\n";
	}
}

# Download new rpms
$DldAll=0 if (!$Resolve and $DldAll<=1);
my $cl= 1;
if ($DldAll) {
	$cl= 2;
}

do {
  @DldRPMs=();
  for $item (SelectRPMs(\@AllDldRPMs, $BestMatch, $cl)) {
	unless ($DldAll >1 or CheckLocal($item->name)) {
		# Only download rpm if not in provides database
		if ($Provides{$item->name}) {
			print "Not downloading " . $item->rpmname . " (is in provides database).\n" if $Debug;
			next;
		}
	}
	# Version comparison for remote rpms might give wrong results
	# since we cannot query the rpm header, so make sure we do not
	# download such rpms again
	next if ( -f $item->rpmname );
	push(@DldRPMs,$item)
  }

  if (!@requirements) {
	if (@DldRPMs) {
		print "New rpms from $Host:\n" unless $Quiet;
	} else {
		print "Found no new rpms at $Host.\n" if $Verbose;
	}
  }
  @requirements=();
  foreach $item (@DldRPMs) {
	$file= $item->filename;
	if ($file =~ /\/([^\/]+)$/ ) {
		$file = $1;
	}
	print "  $file\n" unless $Quiet;
	next if ($Test);
	if ($UseLWP) {
		unless (Get_LWP($item->filename, "s")) {
			print STDERR "Error: Failed to get: " . $item->filename. " ($DldResult)\n";
			$retval+=1;
		}
	} elsif ($Protocol eq "file") {
		unless (copy($item->filename, ".")) {
			print STDERR "Error: Failed to copy: " . $item->filename. " ($?)\n";
			$retval+=1;
		}
	} elsif ($Protocol eq "ftp") {
		unless ($ftp->get($item->filename)) {
			print STDERR "Error: Failed to ftp: ";
			if ($ftp->message !~ /\Q$file/) {
				print STDERR $item->filename . ": ";
			}
			if ($ftp->message) {
				print STDERR $ftp->message;
			} else {
				print STDERR "Timeout (?)\n";
			}
			$retval+=1;
		}
		if ( CallRPM("--checksig", "--nogpg", "--nopgp", $file) != 0) {
			print "Warning: $file: bad signature (will try again)\n" if ($Warnings);
			$ftp->get($item->filename)
		}
	} elsif ($Protocol eq "http" or $Protocol eq "https") {
		unless (Get_HTTP($item->filename, "s")) {
			print STDERR "Error: Failed to get: " . $item->filename. " ($DldResult)\n";
			$retval+=1;
		}
	}
	if (-f $file ) {
		if ( CallRPM("--checksig", "--nogpg", "--nopgp", $file) != 0) {
			print "Warning: $file: bad signature\n" if ($Warnings);
			unlink($file) if ($RemoveBad);
		}
		if ( CallRPM("-qp", "--queryformat", "%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm\\n", $file) !=0 ) {
			print "Warning: Could not query $file\n" if ($Warnings);
			unlink($file) if ($RemoveBad);
		} else {
			my $tmp= $RPMOutput[0];
			chomp($tmp);
			if ($tmp ne $file) {
				if ( ! $FixRPMNames ) {
					print STDERR "Warning: File name for $file should be $tmp.\n" if $Warnings;
					$tmp= $file;
				} elsif ( -f $tmp ) {
					print STDERR "Warning: New rpm $file is identical to old rpm $tmp.\n" if $Warnings;
					unlink ($file);
					$tmp= "";
				} else {
					print STDERR "Warning: Renaming $file to $tmp\n" if $Warnings;
					unless (rename($file, $tmp)) {
						print STDERR "Error: Failed to rename: $file to $tmp ($?)\n";
						$retval+=1;
						$tmp= "";
					}
				}
			}
			if ($tmp) {
				$tmp= "$StoreDir/$tmp";
				AddLocalRPMs("f", $tmp);
				$tmp= Header->new($tmp, "f");
			}
			if ($tmp) {
				push(@DownloadedRPMs, $tmp);
				push(@requirements, Get_Requirements($tmp)) if $Resolve;
			}
		}
	}
  }
  print "  Searching for: @requirements\n" if ($Debug and @requirements);
} until (! @requirements);

if ($UseLWP) {
	# do nothing
} elsif ($Protocol eq "ftp") {
	$ftp->quit;
} elsif ($Protocol eq "https") {
	kill(1,$StunnelPid);
	$StunnelPid= "0";
}

return $retval;
}

######################
# Update given RPMs and resolve dependencies
######################

sub Do_Update
{
my ($AddedRPM, $item, $rpm, $name, $reqmnt);
my @flags = ("-U");
my @RPMs = @_;
my @ResolveRPMs = ();
my @AllDepList = ();

push(@flags,"--repackage") if ($Repackage);
push(@flags,"--test") if ($Test);

do { # until we succeed or can't find rpms to resolve deps
   my @rpms= ();
   print "Trying to upgrade:" if $Debug;
   foreach $item (@RPMs, @ResolveRPMs) {
	push (@rpms, $item->filename);
   	print " " . $item->rpmname if $Debug;
   }			 
   print "\n" if $Debug;
   TieRpmDatabase(0);
   if ( CallRPM(@flags, @rpms) ==0 ) {
	push(@UpdatedRPMs, @ResolveRPMs);
	push(@UpdatedRPMs, @RPMs);
	return 0 if ($Quiet);
	foreach $rpm (@ResolveRPMs) {
		print "  " . $rpm->rpmname . " (new)\n";
	}
	foreach $rpm (@RPMs) {
		print "  " . $rpm->rpmname . "\n";
	}
	if (@RPMOutput) {
		print "  " . join("\n  ", @RPMOutput) . "\n";
	}
	return 0;
   }

   # Couldn't upgrade. Lets find out what we need
   print join("\n", @RPMOutput) . "\n" if (@RPMOutput and $Debug >1);
   $AddedRPM=0; # So far we havn't added anything to satisfy deps
   if ($Resolve) {
      foreach $item (@RPMOutput) {
		next if ( $item =~ /^\s*$/); # Don't care about blank lines.
		next if ( $item =~ /^error:/); # Don't care about this line.
		# Try to figure out what we need
		$reqmnt="";
		if ($item =~ /^\s*file .* from install of\s+\S+\s+conflicts with file from package\s+(\S+)-[^-]+-[^-]+\s*$/) {
			$reqmnt= $1;
		} elsif ($item =~ /^\s+(\S+)\s+.*conflicts with/) {
			$reqmnt= $1;
		} elsif ($item =~ /^\s+(\S+)\s+.*is needed by/) {
			$reqmnt= $1;
			if ($item =~ /^\s+(\S+)\s+is needed by\s+(\S+)-([^-]+-[^-]+)\s*$/) {
				if (CheckLocal($2) eq $3 ) {
					$reqmnt= $2;
			   	}
			}
			if ($item =~ /^\s+(\S+)\s+\S+\s+(\S+)\s+is needed by\s+(\S+)-[^-]+-[^-]+\s*/) {
	 			if (substr(CheckLocal($1), 0, length($2)) eq $2) {
					$reqmnt= $3;
	   			}
			}
		} else {
			print "Warning: Unknown line: \"$item\"\n" if ($Debug >1);
			next;
		}
		$name= $Provides{$reqmnt};
		unless ($name) {
			print "Warning: Requirement \"$reqmnt\" is not in provides database.\n" if $Debug;
			# Let's try our best
			if ($reqmnt =~ /lib\/(.+)$/) { #Strip path from libraries
				$reqmnt= $1;
			} elsif ($reqmnt =~ /^\//) { #File
				next;
			} elsif ($reqmnt =~ /^rpmlib\(.*\)/) { #RPM stuff
				next;
			}
			if ($Provides{$reqmnt}) {
				$name=$Provides{$reqmnt};
			} elsif ($reqmnt =~ /^(.+)\.so(\.\d+)*(\(.*\))?$/) { #Library
				$name = $1;
			} else  {
				$name = $reqmnt;
			}
		}
		if (IsElement($name, @AllDepList)) {
			print "Requirement: $reqmnt ($name)\n" if ($Debug > 1);
			next;
		}
		print "Requirement: $reqmnt ($name)\n" if $Debug;
		push(@AllDepList, $name);
		# Next search for the corresponding rpm
		# Add all rpms with the same base name
		my $base= $name;
		if ( $base =~ /^([^-]+-)/ ) {
			$base= $1;
		}
		my @tmp= ();
		my $found= 0;
		for $rpm (@SelectedRPMs) {
	   		if ( substr($rpm->name, 0, length($base)) eq $base ) {
				next if (IsElement($rpm, @RPMs));
				next if (IsElement($rpm, @UpdatedRPMs));
				push(@tmp, $rpm);
				$found= 1 if ($name eq $rpm->name);
			}
		}
		if ($found) {
			for $rpm (@tmp) {
				print "Adding " . $rpm->rpmname . ".\n" if $Debug;
				push(@RPMs, $rpm);
			}
			$AddedRPM=1;
			next;
		}
		unless ($NewRPMs{$name}) {
			print "Warning: $name not available to satisfy dependencies.\n" if $Debug;
			push(@DepList, $name);
			next;
		}
		$rpm= (SelectRPMs($NewRPMs{$name},1,2))[0];
		next if (IsElement($rpm, @ResolveRPMs));
		next if (IsElement($rpm, @UpdatedRPMs));
		$NewRPMs{$name} = [ $rpm ];
		next unless (Check_Sig($rpm)==0 );
		print "Adding " . $rpm->rpmname . " to satisfy dependencies.\n" if $Debug;
		push(@ResolveRPMs, $rpm);
		$AddedRPM=1;
      }
   }
} until (! $AddedRPM );

if ($Debug or $LastTry) {
	print "Failed to upgrade:";
	foreach $item (@RPMs) {
		print " " . $item->rpmname;
	}
	print "\n";
	if (@ResolveRPMs and ! $Quiet) {
		print "Tried to add:";
		foreach $item (@ResolveRPMs) {
			print " " . $item->rpmname;
		}
		print "\n";
	}
	print "Would need: @DepList\n" if (@DepList and ! $Quiet);
	print "RPM Output:\n" unless $Quiet;
	print join("\n", @RPMOutput) . "\n" unless $Quiet;
}

return 1;
}

######################
# Update all RPMs
######################

sub Update_RPMs
{

my ($item, $rpm, $name, $version, $oldversion, @rpms);
my $Ret = 0;
%NewRPMs = ();
@DepList = ();
@SelectedRPMs = ();
@SelectedKernelRPMs = ();

print "\nUpdating rpms:\n" if $Debug;

# Get a list of all installed rpms
SetLocalRPMs();
Get_Local_List();

if ($DoGet) {
	for $item (@GetList) {
		if (CheckLocal($item)) {
			print STDERR "Warning: $item is already installed.\n" if $Warnings;
		} else {
			AddLocalRPMs("r", "$item-0.0-0.noarch.rpm");
		}
	}
}

# Set include/exclude patterns
@Exclude= @UpdExclude;
@Include= @UpdInclude;

# List of latest version of all available rpms
# except those already installed
if ($DoGet and $RPMDir) {
	print "Also adding rpms from $RPMDir.\n" if $Debug;
	foreach $rpm ( GetRPMHeaders("f", Get_RPMs($RPMDir)) ) {
		$name= $rpm->name;
		foreach $item (@GetList) {
			next unless ($name eq $item);
			push(@AllRPMs, $rpm);
		} 
	}
}

my @UpdateRPMs= ();

foreach $item (SelectRPMs(\@AllRPMs, 1, 2)) {
	if (IsExcluded($item)) {
		print "Excluded: " . $item->rpmname . "\n" if $Debug;
	} else {
		push(@UpdateRPMs, $item);
	}
}
unless ($DoInstall) {
	print "Selecting new rpms for upgrade.\n" if $Debug;
	@rpms=@UpdateRPMs;
	@UpdateRPMs=SelectRPMs(\@rpms, 1, 1);
	if ($Resolve) {
		foreach $item (@rpms) {
			next if (IsElement($item, @UpdateRPMs));
			if ($NewRPMs{$item->name}) {
				push(@{$NewRPMs{$item->name}},$item);
			} else {
				$NewRPMs{$item->name}= [ $item ];
			}
		}
	}
}
@rpms= ();

unless (@UpdateRPMs) {
	if ($DoInstall or $DoGet) {
		print "Found no rpms to install.\n" if $Verbose;
	} else {
		print "Found no rpms to upgrade.\n" if $Verbose;
	}
	return $Ret;
}

# Check rpms and split off kernel rpms.
print "Checking selected rpms.\n" if $Debug;
@Include= ();
@Exclude= "^kernel-\\d";
foreach $item (split(/,/,$KernelExt)) {
	push(@Exclude,"^kernel-$item-\\d");
}
foreach $item ( @UpdateRPMs ) {
	next unless ( Check_Sig($item)==0 );
	if (IsExcluded($item)) {
		push(@SelectedKernelRPMs, $item);
	} else {
		push(@SelectedRPMs, $item);
	}
}
undef(@UpdateRPMs);
@Include= @UpdInclude;
push(@Exclude, @UpdExclude);

# Now upgrade selected rpms
if ( @SelectedRPMs ) {
	if ($Resolve and $RPMDir) {
		print "Getting rpms for resolving dependencies: $RPMDir\n" if $Debug;
		my @rpms=GetRPMHeaders("f", Get_RPMs($RPMDir) );
		foreach $item (@rpms) {
			if ($NewRPMs{$item->name}) {
				push(@{$NewRPMs{$item->name}},$item);
			} else {
				$NewRPMs{$item->name}= [ $item ];
			}
		}
	}
	if ($DoInstall or $DoGet) {
		print "Installing rpms:\n" unless $Quiet;
	} else {
		print "Upgrading rpms:\n" unless $Quiet;
	}
	# Sort rpms by name
	@SelectedRPMs = sort {$a->name cmp $b->name} @SelectedRPMs;
	my $base;
	my $len= scalar(@SelectedRPMs) -1;
	for (my $i = 0; $i <= $len; $i++) {
 		$rpm=$SelectedRPMs[$i];
		next if (IsElement($rpm, @UpdatedRPMs));
		@rpms = ($rpm);
		# Select all rpms with the same basename (e.g., foo-1.0-1.arch.rpm
		# and foo-devel-1.0-1.arch.rpm) since they usually require each other.
		$base= $rpm->name;
		if ( $base =~ /^([^-]+-)/ ) {
			$base= $1;
		}
		my $item= $SelectedRPMs[$i+1];
	   	while ( $i < $len and substr($item->name, 0, length($base)) eq $base ) {
			push(@rpms,$item) unless (IsElement($item, @UpdatedRPMs));
			$i++;
			$item= $SelectedRPMs[$i+1];
		}
		$LastTry=1 if (@rpms eq @SelectedRPMs);
		$Ret= Do_Update(@rpms);
	}

	# This is our status right now.
	my @FailedRPMs= ();
	foreach $rpm ( @SelectedRPMs ) {
		push(@FailedRPMs, $rpm) unless (IsElement($rpm, @UpdatedRPMs));
	}

	if (!$LastTry and @FailedRPMs) {
		# Finally, pass all rpms which could not be upgraded to rpm.
		# This should not help, but I guess it doesn't hurt.
		print "Upgrading remaining rpms (all at once).\n" if $Debug;

		$LastTry=1;
		$Ret= Do_Update(@FailedRPMs);
	}
} else {
	if ($DoInstall or $DoGet) {
		print "Found no rpms to install.\n" if $Verbose;
	} else {
		print "Found no rpms to upgrade.\n" if $Verbose;
	}
}

return $Ret unless $DoKernel;

# Finally, upgrade selected kernel rpms
unless ( @SelectedKernelRPMs ) {
	print "Found no kernel rpms to install.\n" if ($Verbose and !$DoGet);
	return $Ret;
}

print "Installing kernel rpms:\n" unless $Quiet;

if ($DoBoot) {
	unless ($BootManager) {
		if ( -x $LILO and -f $LiloConf) {
			$BootManager= "lilo";
		} elsif ( -x $GRUB and -f $GrubConf) {
			$BootManager= "grub";
		}
	} else {
		$BootManager= lc($BootManager);
	}
	if ($BootManager eq "lilo") {
		# Determine config file
		unless ($BootConf) {
			$BootConf= $LiloConf;
		}
		print "Using lilo as boot manager ($BootConf).\n" if $Debug;
	} elsif ($BootManager eq "grub") {
		# Determine config file
		unless ($BootConf) {
			$BootConf= $GrubConf;
		}
		print "Using grub as boot manager ($BootConf).\n" if $Debug;
                # Is BootDir on a boot partition?
                if (-d $BootDir and ! $BootMnt) {
                        if (open(DF, "$DF $BootDir |") ) {
                                for my $line (<DF>) {
                                        if ($line =~ /^\/dev\/\S+(\s+\S+){4}\s+(\/\S*)$/) {
                                                $BootMnt= $2;
                                                $BootMnt= "" if ($BootMnt eq "/");
                                                last;
                                        }
                                }
                                close(DF);
                        } else {
                                print STDERR "Warning: Could not determine boot partition ($!).\n" if $Warnings;
                        }
                }
		print "Boot directory $BootDir mounted on $BootMnt.\n" if ($Debug and $BootMnt);
	} else {
		print STDERR "Warning: No supported boot manager found!\n" if $Warnings;
		$DoBoot= 0;
	}
	unless ( -f $BootConf ) {
		print STDERR "Error: Configuration file '$BootConf' does not exist!?\n";
		$DoBoot= 0;
	}
	print STDERR "Warning: Boot manager part disabled.\n" if ($Warnings and ! $DoBoot);
}

foreach $item (@SelectedKernelRPMs) {
	$name=$item->name;
	$version=$item->version;
	$oldversion= CheckLocal($name);
	$oldversion= "0.0-0" unless ($oldversion);
	if ($name =~ /^kernel-(\w+)$/) {
		$oldversion=$oldversion . $1;
		$version=$version . $1;
	}
	$Ret+= Do_KernelUpdate($oldversion,$version,$item);
}

return $Ret unless ($DoBoot and $ChangedBoot);

print "Installing $BootManager.\n" if $Verbose;
$CurrentKernel=(uname())[2];
print "Current kernel: $CurrentKernel\n" if ($Debug>1);

unless ($Test) {
	unless ( Boot_Manager()==0 ) {
		$ChangedBoot= 0;
		print STDERR "Error: Installation of $BootManager failed.\n";
		print STDERR "Restoring old configuration.\n";
		print STDERR "Saving new one as $BootConf.test.\n";
		rename($BootConf,"$BootConf.test");
		rename("$BootConf.bak",$BootConf);
		unless ( Boot_Manager()==0 ) {
			print STDERR "Failed to restore old configuration.\n";
		}
		$Ret+=1;
		return $Ret;
	} else {
		print "Please check the contents of $BootConf before rebooting!\n" if $Verbose;
	}
}

return $Ret unless ($CleanUpKernel and @RemovedKernel);

my $image;
my @flags=("-e");
push(@flags,"--repackage") if ($Repackage);
push(@flags,"--test") if ($Test);

print "Uninstalling old kernels:\n" if $Verbose;

foreach $image (@RemovedKernel) {
	if ($CurrentKernel eq $image) {
		print STDERR "Error: Cannot remove running kernel.\n";
		$Ret+=1;
		next;
	}
	unless (CallRPM("-qf","$BootDir/vmlinuz-$image")==0 and $RPMOutput[0] =~ /^kernel-/) {
		print STDERR "Error: Could not determine rpm owning $BootDir/vmlinuz-$image.\n";
		$Ret+=1;
		next;
	}
	$name=$RPMOutput[0];
	if (CallRPM(@flags,$name)==0) {
		print "  $name\n" if $Verbose;
		if (-f "$BootDir/initrd-$image.img") {
			unlink("$BootDir/initrd-$image.img");
		}
	} else {
		print STDERR "Error: Failed to uninstall $name.\n";
		print STDERR "@RPMOutput";
		$Ret+=1;
	}
}

return $Ret;

}

######################
# Install Boot Manager
######################

sub Boot_Manager
{

if ( $BootScript ) {
	if ( -x $BootScript ) {
		print "Calling boot script ($BootScript):\n" if $Debug;
		return system($BootScript);
	} else {
		print STDERR "Error: Cannot execute: $BootScript\n";
	}
} elsif ( $BootManager eq "lilo" ) {
	if ( -x $LILO ) {
		print "Calling lilo ($LILO):\n" if $Debug;
		return system($LILO);
	} else {
		print STDERR "Error: Cannot execute lilo: $LILO\n";
	}
} elsif ( $BootManager eq "grub" ) {
	# No need to reinstall grub.
	return 0;
}

return 1;
}

#####################
# Upgrade the kernel
#####################

sub Do_KernelUpdate {

my $OldVersion= shift;
my $NewVersion= shift;
my $RPM= shift;
my @flags=("-i");
my ($line, $item, $entry, $newlabel);
my @Conf =();
my @NewConf =();
my @Entries =();
my @Image =();
my @Label =();
my %Entry =();
my $InitRD;
my $link="";

print "Upgrading kernel: $OldVersion -> $NewVersion\n" if $Debug;

push(@flags,"--test") if ($Test);

# Set default for BootTag
unless ($BootTag) {
	if ($BootAddAsNew) {
		$BootTag="-new";
	} else {
		$BootTag="-old";
	}
}

# Check link
if ( -l "$BootDir/vmlinuz" ) {
	if ( readlink("$BootDir/vmlinuz") =~ /vmlinuz(.+)$/ ) {
		$link=$1;
		print "  Link 'vmlinuz' points to 'vmlinuz$link'.\n" if ($Debug >1);
	}
}

# Install the new kernel.
TieRpmDatabase(0);
if ( CallRPM(@flags, $RPM->filename) ==0 ) {
	$Kernel+=1;
	push(@UpdatedRPMs, $RPM);
	print "  " unless $Quiet;
	print join("\n  ", @RPMOutput) . "\n  " if (@RPMOutput and ! $Quiet);
	print $RPM->rpmname . "\n" unless $Quiet;
} else {
	print "Failed to install new kernel: " . $RPM->rpmname . "\n";
	print "  " . join("\n  ", @RPMOutput) . "\n" if (@RPMOutput and ! $Quiet);
	return 1;
}


$InitRD= "$BootDir/initrd-$NewVersion.img";
if (($DoBoot or $DoInitRD) and ! -f $InitRD) {
	print "Creating initial ram disk: $InitRD\n" if $Verbose;
	unless ( -x $MKINITRD ) {
		print STDERR "Error: Cannot execute mkinitrd ($MKINITRD).\n";
		return 1;
	}
	unless ($Test) {
		unless ( system($MKINITRD, "--ifneeded", $InitRD, $NewVersion) ==0 ) {
			print STDERR "Error: Failed to create $InitRD.\n";
			return 1;
		}
	}
}

return 0 unless $DoBoot;

if ($OldVersion =~ /^0.0-0/) {
	print STDERR "Warning: Cannot change boot manager since no old version is present!\n" if $Warnings;	
	return 0;
}

# Parse boot manager configuration

unless (open(FILE,"<$BootConf")) {
  print STDERR "Error: Could not open: $BootConf ($!)\n";
  return 1;
}

print "Parsing $BootConf.\n" if $Debug;
$entry= 0;
if ($BootManager eq "lilo") {
	my $nr= -1;
	foreach $line ( <FILE> ) {
		if ( $link and $line =~ /^\s*image\s*=.*\/vmlinuz\s*$/) {
			print "Changing 'vmlinuz' to 'vmlinuz$link'!\n" if $Debug;
			$line =~ s/vmlinuz\s*$/vmlinuz$link\n/;
		}
		if ($line =~ /^\s*image\s*=.*\/vmlinuz-(\S+)/) {
			print "  Found lilo entry: $1.\n" if ($Debug >1);
			$entry= 1;
			$nr++;
			$Image[$nr]=$1;
			$Entries[$nr]= [];
			$Label[$nr]= "";
			push(@Conf,"--entry=$nr\n");
		} elsif ($line =~ /^\s*image\s*=/) {
			$entry = 0;
		} elsif ($line =~ /^\s*other\s*=/) {
			$entry = 0;
		}
		if ($entry) {
			if ($line =~ /^\s*label\s*=\s*(\S.*)$/) {
				if ($Label[$nr]) {
					print STDERR "Error: Kernel '$Image[$nr]' has more than one label!?\n";
					return 1;
				}
				$Label[$nr]=$1;
			}

			push(@{$Entries[$nr]}, $line);
		} else {
			push(@Conf,$line);
		}
	}
} elsif ($BootManager eq "grub") {
	my $nr= -1;
	foreach $line ( <FILE> ) {
		if ($line =~ /^\s*title\s+(.+)$/) {
			print "  Found grub entry: $1.\n" if ($Debug >1);
			$entry= 1;
			$nr++;
			$Image[$nr]= "";
			$Entries[$nr]= [];
			$Label[$nr]= $1;
			push(@Conf,"--entry=$nr\n");
		}
		if ($entry) {
			if ( $link and $line =~ /^\s*kernel\s.*\/vmlinuz(\s+|$)/) {
				print "Changing 'vmlinuz' to 'vmlinuz$link'!\n" if $Debug;
				$line =~ s/\/vmlinuz(\s+|$)/\/vmlinuz$link$1/;
			}
			if ($line =~ /^\s*kernel\s.*\/vmlinuz-(\S+)/) {
				if ( $Image[$nr] ) {
					print STDERR "Error: Entry '$Label[$nr]' has more than one kernel!?.\n";
					return 1;
				}
				$Image[$nr]= $1;
			}
			push(@{$Entries[$nr]}, $line);
		} else {
			push(@Conf,$line);
		}
	}
}
close(FILE);

# Investigate entries
$entry= 0;
$InitRD= "";
my $default= "";
my $newdefault= "";

for $line (@Conf) {
	unless ($line =~ /^--entry=(\d+)$/) {
		if ($line =~ /^\s*default\s*=\s*(\S+)\s*$/) {
			$default = $1;
			}
		push(@NewConf,$line);
		next;
	}
	my $i= $1;
	if ($Image[$i] eq $NewVersion) {
		print STDERR "Warning: New image '$NewVersion' already present in $BootConf!\n";
		return 0;
	}
	unless ($Image[$i] eq $OldVersion) {
		if ($Label[$i] =~ /$BootTag$/) {
			# Remove old versions
			if ($Image[$i] eq $CurrentKernel) {
				print STDERR "Warning: Cannot remove running kernel from boot manager ($Label[$i]).\n" if $Warnings;
				if ($Entry{$Label[$i]}) {
					print STDERR "Error: Label '$Label[$i]' appears more than once!\n";
					return 1;
				}
				$Entry{$Label[$i]}= 1;
			} else {
				print "Removing boot entry: '$Label[$i]' ($Image[$i])\n" if $Verbose;
				push(@RemovedKernel,$Image[$i]);
				next;
			}
		}
		push(@NewConf,@{$Entries[$i]});
		next;
	}
	$entry=1;
	my $label= $Label[$i];
	my $oldlabel=$label;
	if ( ! $BootAddAsNew and ($UseBootTag or $oldlabel !~ /$OldVersion/) ) {
		$oldlabel="$label$BootTag";
	}
	if ($Entry{$oldlabel}) {
		print STDERR "Error: Label '$oldlabel' appears more than once!?\n";
		return 1;
	}
	@{$Entry{$oldlabel}}= @{$Entries[$i]};
	if ($oldlabel ne $label) {
		# Need to rewrite old entry
		for $item (@{$Entry{$oldlabel}}) {
			if ($item =~ /^\s*label\s*=/ or $item =~ /^\s*title\s+/) {
				$item =~ s/\Q$label/$oldlabel/;
			} elsif ($item =~ /^(\s*alias\s*=\s*)(\S+)/) {
				my $tmp1 = $1;
				my $tmp2 = $2;
				if ( ! $BootAddAsNew and ($UseBootTag or $tmp2 !~ /$OldVersion/) ) {
					$item="$tmp1$tmp2$BootTag";
				}
			}
		}
	}
	$newlabel=$label;
	$newlabel =~ s/$OldVersion/$NewVersion/;
	if ($BootAddAsNew and ($UseBootTag or $newlabel !~ /$NewVersion/) ) {
		$newlabel= "$newlabel$BootTag" unless ($newlabel =~ /$BootTag$/);
	}
	if ($newlabel eq $oldlabel) {
		if ($Image[$i] eq $CurrentKernel) {
			print STDERR "Error: Cannot remove running kernel from boot manager ($Label[$i])!\n" if $Warnings;
			return 1;
		}
		print "Removing boot entry: '$Label[$i]' ($Image[$i])\n" if $Verbose;
		push(@RemovedKernel,$Image[$i]);
		$oldlabel= "";
	} else {
		if ($Entry{$newlabel}) {
			print STDERR "Error: Label '$newlabel' appears more than once!?\n";
			return 1;
		}
	}
	if ($default and $default eq $label) {
		$newdefault=$newlabel;
	}
	@{$Entry{$newlabel}}= @{$Entries[$i]};
	for $item (@{$Entry{$newlabel}}) {
		$item =~ s/$OldVersion/$NewVersion/;
		if ($item =~ /^(\s*initrd\s*[=\s]\s*)(\S+)/) {
				$InitRD=$2;
				unless ($InitRD =~ /$NewVersion/) {
					$InitRD= "$BootDir/initrd-$NewVersion.img";
					$item = "$1$InitRD\n";
				}
		} elsif ($item =~ /^\s*label\s*=/ or $item =~ /^\s*title\s+/) {
			$item =~ s/$label/$newlabel/;
		} elsif ($item =~ /^(\s*alias\s*=\s*)(\S+)/) {
			my $tmp1 = $1;
			my $tmp2 = $2;
			if ( $BootAddAsNew and ($UseBootTag or $tmp2 !~ /$NewVersion/) ) {
					$item="$tmp1$tmp2$BootTag";
			}
		} 
	}
	if ($BootAddAsNew) {
		push(@NewConf,"--entry=$oldlabel\n") if $oldlabel; 
		push(@NewConf,"--entry=$newlabel\n");
	} else {
		push(@NewConf,"--entry=$newlabel\n");
		push(@NewConf,"--entry=$oldlabel\n") if $oldlabel;
	}
}

unless ($entry) {
	print STDERR "Warning: Old image '$OldVersion' not present in $BootConf!\n" if $Warnings;
	return 0;
}

# Change default entry
if ($newdefault) {
	for $line (@NewConf) {
		if ($line =~ /^(\s*default\s*=\s*)\S+(\s*)$/) {
			$line = "$1$newdefault$2";
			}
	}
}

# Create initial ram disk if needed

if ($InitRD and ! -f $InitRD) {
	print "Creating initial ram disk: $InitRD\n" if $Verbose;
	unless ( -x $MKINITRD ) {
		print STDERR "Error: Cannot execute mkinitrd ($MKINITRD).\n";
		return 1;
	}
	unless ($Test) {
		unless ( system($MKINITRD, $InitRD, $NewVersion) ==0 ) {
			print STDERR "Error: Failed to create $InitRD.\n";
			return 1;
		}
	}
}

return 0 unless ($DoBoot);

if ( (! $InitRD) and -f "$BootDir/initrd-$NewVersion.img" ) {
	# mkinitrd thinks we need one, so lets add it
	print STDERR "Warning: Adding initrd to new boot image.\n" if $Warnings;
	$InitRD= "$BootDir/initrd-$NewVersion.img";
	if ($BootManager eq "lilo") {
		push(@{$Entry{$newlabel}},"\tinitrd=$InitRD\n");
	} elsif ($BootManager eq "grub") {
		if ($BootMnt) {
			$InitRD =~ s/^$BootMnt//;
		}
		push(@{$Entry{$newlabel}},"\tinitrd $InitRD\n");
	}
}

# Backup boot manager config

my $mode;
unless ( $mode=(stat($BootConf))[2] ) {
	$mode= "0600";
}

unless ($ChangedBoot) {
	print "Writing backup: $BootConf.bak.\n" if $Debug;
	unless ( copy($BootConf, "$BootConf.bak") ) {
		print STDERR "Error: Failed to backup $BootConf ($!)\n";
		return 1;
	}
	chmod($mode,"$BootConf.bak");
}

# Write new boot manager config

if ($Test and ($BootConf !~ /\.test$/)) {
	$BootConf= $BootConf . ".test";
}

print "Writing new $BootConf.\n" if $Verbose;
unless (open(FILE,">$BootConf")) {
  print STDERR "Error: Could not open: $BootConf ($!)\n";
  return 1;
}
chmod($mode,"$BootConf");
$ChangedBoot=1;

foreach $line ( @NewConf ) {
	if ($line =~ /^--entry=(.+)$/) {
		print FILE @{$Entry{$1}};
	} else {
		print FILE $line;
	}
}
close(FILE);

return 0;
}


######################
# Main
######################

my $item;

if ( -d $ConfigDir ) {
	if ( ((stat($ConfigDir))[2] >> 1)%2 ) {
		die("Fatal: Configuration directory '$ConfigDir' is world writeable!");
	}
} else {
	print STDERR "Warning: No configuration directory: $ConfigDir\n" if $Warnings;
}

Getopt::Long::Configure('pass_through');
GetOptions('config=s' => \$ConfigFile);
@UpdExclude= ();
@UpdInclude= ();
Read_Config($ConfigFile);

if ( $0 =~ /autoupd$/) {
	$DoDld=0;
	$DoUpdate=1;
	$DoMerge=0;
	$DoPurge=0;
	$RemoveBad=0;
	$CleanUp=0;
	$BestMatch=1;
	$CheckLocal=1;
} elsif ( $0 =~ /autoins$/) {
	$DoDld=0;
	$DoInstall=1;
	$DoMerge=0;
	$DoPurge=0;
	$RemoveBad=0;
	$CleanUp=0;
	$BestMatch=1;
	$CheckLocal=2;
} elsif ( $0 =~ /automrg$/) {
	$DoDld=0;
	$DoUpdate=0;
	$DoInstall=0;
	$DoMerge=1;
	$RemoveBad=2;
} elsif ( $0 =~ /autoprg$/) {
	$DoDld=0;
	$DoUpdate=0;
	$DoInstall=0;
	$DoMerge=0;
	$DoPurge=1;
	$RemoveBad=2;
	$CheckLocal=0;
} elsif ( $0 =~ /autoget$/) {
	$DoGet=1;
	$DoDld=2;
	$DoInstall=0;
	$DoMerge=0;
	$DoPurge=0;
	$RemoveBad=0;
} elsif ( $0 =~ /autodld$/) {
	$DoDld=1
}
@Include= ();
@Exclude= ();
my $SaveUpdateDir=$UpdateDir;

Getopt::Long::Configure('default');
$item= GetOptions('dld!' => \$DoDld,
	'ftp!' => \$DoDld, #Old syntax
	'get!' => \$DoGet,
	'url=s' => \$URL,
	'ftpurl=s' => \$URL, #Old syntax
	'dldconfig=s' => \@DldConfig,
	'ftpconfig=s' => \@DldConfig, #Old syntax
	'dldall+' => \$DldAll,
	'ftpall+' => \$DldAll, #Old syntax
 	'dldrecursive!' => \$DldRecursive,
 	'dldrecurse!' => \$DldRecursive, #Old syntax
 	'ftprecursive!' => \$DldRecursive, #Old syntax
	'dldmatch!' => \$DldMatch,
	'matchftp!' => \$DldMatch, #Old syntax
	'bestmatch!' => \$BestMatch,
	'removebad' => sub { $RemoveBad=2 },
	'noremovebad' => sub { $RemoveBad=0 },
	'passive!' => \$DefaultPassive,
	'httpsendhost!' => \$DefaultHTTPSendHost,
	'addtodb!' => \$DoAddToDB,
	'mergedb=s' => \$MergeDB,
	'createdb=s' => \$CreateDB,
	'dldusedb!' => \$DldUseDB,
	'update!' => \$DoUpdate,
	'install!' => \$DoInstall,
	'updateall!' => \$DoInstall, #Old syntax
	'kernel!' => \$DoKernel,
	'updatekernel!' => \$DoKernel, #Old syntax
	'initrd!' => \$DoInitRD,
	'lilo!' => \$DoBoot, #Old syntax
	'boot!' => \$DoBoot,
	'checksig!' => \$CheckSig,
	'checkgpg!' => \$CheckGPG,
	'repackage!' => \$Repackage,
	'resolve!' => \$Resolve,
	'recursive!' => \$Recursive,
 	'recurse!' => \$Recursive, #Old syntax
	'updatedir=s' => \$UpdateDir,
	'updatealldir=s' => \$InstallDir, #Old syntax
	'installdir=s' => \$InstallDir,
	'rpmdir=s' => \$RPMDir,
	'postupdatescript=s' => \$PostUpdateScript,
	'postupdldscript=s' => \$PostDldScript,
	'distversion=s' => \$DistVersion,
	'distarch=s' => sub { SetDistArch($_[1]) },
	'database=s' => \$DataBase,
	'log!' => \$DoLog,
	'lock!' => \$DoLock,
	'logfile=s' => \$LogFile,
	'queryheaders!' => \$QueryHeaders,
	'querydatabase!' => \$QueryDatabase,
	'cleanup!' => \$CleanUp,
	'cleanupkernel!' => \$CleanUpKernel,
	'liloaddasnew!' => \$BootAddAsNew,
	'bootaddasnew!' => \$BootAddAsNew,
	'include=s' => \@Include,
	'exclude=s' => \@Exclude,
	'merge!' => \$DoMerge,
	'mergeall!' => \$MergeAll,
	'mergematch!' => \$MergeMatch,
	'matchmerge!' => \$MergeMatch, #Old syntax
	'purge!' => \$DoPurge,
	'client' => sub { $DoDld=0; $DoUpdate=1; $DoMerge=0; $DoPurge=0; $RemoveBad=0; $CleanUp=0; }, #Old Syntax
	'arch=s' => sub { SetArch($_[1]) },
	'compare=s' => \$Compare,
	'whatprovides=s' => \$WhatProvides,
	'test!' => \$Test,
	'verbose!' => \$Verbose,
	'quiet!' => \$Quiet,
	'warnings!' => \$Warnings,
	'rpmnamewarnings!' => \$DefaultRPMNameWarnings,
	'fixrpmnames!' => \$DefaultFixRPMNames,
	'uselwp' => \$DefaultUseLWP,
	'debug=i' => \$Debug,
	'debugftp!' => \$DebugFTP,
	'debuglwp!' => \$DebugLWP,
	'debughttp!' => \$DebugHTTP,
	'debugaupm!' => \$DebugAUPM,
	'showlatest!' => \$showlatest,
	'checklocal=i' => \$CheckLocal,
	'nochecklocal' => sub { $CheckLocal=0 },
	'version!' => \$showversion,
	'help!' => \$showhelp );

unless ($item) {
    print STDERR "Try '$0 --help'.\n";
    exit 1;
}

if ($showhelp or $showversion)
{
	print "autoupdate version $Version <autoupdate\@mat.univie.ac.at>.\n";
    print $USAGE if $showhelp;
    exit 0;
}

if ($DebugAUPM) {
	$autoupdate::DEBUG= $DebugAUPM;
}
if (($DebugFTP or $DebugHTTP or $DebugLWP or $DebugAUPM) and !$Debug) {
	$Debug= 1;
}

print "autoupdate version $Version\n" if $Debug;

if (@Include or @Exclude) {
	# Use in-/exclude patterns from command line
	@UpdInclude= @Include;
	@UpdExclude= @Exclude;
}

# Set up defaults

$Verbose =1 if ($Debug or $Test);
if ($Debug) {
	$DefaultRPMNameWarnings =1;
	$Warnings =1;
	$Quiet =0;
}
$Verbose =0 if $Quiet;
#$Warnings =1 if $Verbose;
$DefaultRPMNameWarnings =0 unless $Warnings;
$RPMNameWarnings =$DefaultRPMNameWarnings;

unless (SetQueryRpmHeaders($QueryHeaders) == $QueryHeaders) {
	#We dont have the RPM module, so don't try again
	$QueryHeaders= 0;
	$QueryDatabase= 0;
}

if ($CleanUp) {
	$RemoveBad = 1;
}
if ($DoInstall) {
	$DoUpdate = 1;
}
if ($DoGet) {
	$DoInstall = 0;
	$DoDld = 1 unless ($DoUpdate);
}
if (@DldConfig or $URL) {
	$DoDld = 1;
}

# Compare version strings
if ($Compare)
{
	my $vera="";
	my $verb="";
	if( $Compare =~ /^([^,]*),([^,]*)$/ ) {
		$vera=$1;
		$verb=$2;
	} else {
		print STDERR "Usage: --compare 1.1-1,1.0-1\n";
    	print STDERR "Try '$0 --help'.\n";
		exit 1;
	}
	if ($vera =~ /^(.+)\.\w+\.rpm$/) {
		$vera = $1;
	}
	if ($vera =~ /^(.*-)?([^-]+-[^-]+)$/) {
		$vera = $2;
	}
	if ($verb =~ /^(.+)\.\w+\.rpm$/) {
		$verb = $1;
	}
	if ($verb =~ /^(.*-)?([^-]+-[^-]+)$/) {
		$verb = $2;
	}
	print "Comparing: \"$vera\" and \"$verb\"\n" if $Debug;
	$vera= Header->new("foo-$vera.noarch.rpm", "r");
	$verb= Header->new("foo-$verb.noarch.rpm", "r");
	if ($vera and $verb) {
		print $vera->compare($verb)."\n";
		exit 0;
	} else {
		print STDERR "Error: Illegal version string\n";
		exit 1;
	}
} 

&TestArch;
print "Using architecture: " . &GetArch . "\n" if $Debug;
$DistArch= &GetDistArch;

# Make sure all paths are absolute
$cwd = getcwd();
$cwd = "" if ($cwd eq "/");
$RPMDir= "$cwd/$RPMDir" if ($RPMDir and $RPMDir !~ /^\//);
$UpdateDir= "$cwd/$UpdateDir" if ($UpdateDir and $UpdateDir !~ /^\//);

# Substitute DistVersion and DistArch
if ($DistVersion) {
	$RPMDir=~ s/#DistVersion#/$DistVersion/g;
	$UpdateDir=~ s/#DistVersion#/$DistVersion/g;
	$SaveUpdateDir=~ s/#DistVersion#/$DistVersion/g;
	$InstallDir=~ s/#DistVersion#/$DistVersion/g;
	$DataBase=~ s/#DistVersion#/$DistVersion/g;
}
if ($DistArch) {
	$RPMDir=~ s/#DistArch#/$DistArch/g;
	$UpdateDir=~ s/#DistArch#/$DistArch/g;
	$SaveUpdateDir=~ s/#DistArch#/$DistArch/g;
	$InstallDir=~ s/#DistArch#/$DistArch/g;
	$DataBase=~ s/#DistArch#/$DistArch/g;
}
Strip_Dir($RPMDir,"1");
Strip_Dir($UpdateDir,"1");
Strip_Dir($SaveUpdateDir,"1");
Strip_Dir($InstallDir,"1");

#Show latest version
if ($showlatest) {
	if ($CheckLocal) {
		SetLocalRPMs();
		if ($DoDld or $DoGet) {
			Get_Local_List($RPMDir) if ($RPMDir);
			Get_Local_List($UpdateDir) if ($UpdateDir);
		} else {
			Get_Local_List();
		}
	}
	@Include= @UpdInclude;
	@Exclude= @UpdExclude;
	@AllRPMs= GetRPMHeaders( @ARGV );
	@ARGV= ();
	foreach $item (SelectRPMs(\@AllRPMs, $BestMatch, $CheckLocal)) {
		print $item->filename . "\n";
	}
	exit 0;
}

$DoLog= 0 unless ($LogFile);
if ($DoLog and $LogFile eq "syslog") {
	eval "use Sys::Syslog";
	if ($@) { 
		warn('Failed to load Sys::Syslog');
		$DoLog=0;
	}
}

# Set up default for provides database
# Use database in update/config dir if available
unless ($DataBase) {
	if ( -f "$ConfigDir/$DataBaseName" ) {
		$DataBase="$ConfigDir/$DataBaseName";
	} elsif ( -f "$UpdateDir/$DataBaseName" ) {
		$DataBase="$UpdateDir/$DataBaseName";
	} elsif ( -f "$SaveUpdateDir/$DataBaseName" ) {
		$DataBase="$SaveUpdateDir/$DataBaseName";
	} else {
		$DataBase="$UpdateDir/$DataBaseName";
	}
}

if ($WhatProvides) {
	print "Opening provides database: $DataBase\n" if $Debug;
	unless (tie(%Provides, 'DB_File', $DataBase, O_RDONLY, 0)) {
		print STDERR "Error: Could not open $DataBase: $!\n";
 		exit 1;
	}
	if ($Provides{$WhatProvides}) {
		print "$Provides{$WhatProvides}\n";
	}
	untie(%Provides);
	exit 0;
}

die ("Cannot execute RPM ($RPM)") unless ( -x $RPM );

# Use InstallDir for DoInstall
if ($DoInstall and $InstallDir) {
	$UpdateDir=$InstallDir;
}

# Make sure the gpg keyring is found if run from cron
if ($CheckGPG and $ENV{'HOME'} and ! -d "$ENV{'HOME'}/.gnupg/") {
	$ENV{'HOME'}="/root" if (-d "/root/.gnupg/");
}

#
# Create a lock file
#

if ($DoLock) {
	if ( -e $LockFile ) {
		open(LOCK,"<$LockFile") || die("Could not read the lock file: $LockFile ($!).");
		my $pid= <LOCK>;
		close(LOCK);
		die("Illegal lock file ($LockFile)!?") unless ($pid =~ /^(\d+)$/);
		$pid =$1;
		die ("Cannot execute PS ($PS)") unless ( -x $PS );
		if ( system("$PS --no-heading $pid >/dev/null") == 0 ) {
			print STDERR "Error: autoupdate already running under pid $pid!\n";
			exit(1);
		}
		print "Warning: Found stale lock file.\n" if ($Warnings);
		$RemoveBad= 2 if ($RemoveBad);
	}

	open(LOCK,">$LockFile") || die("Could not create the lock file: $LockFile  ($!)");
	print LOCK $$;
	close(LOCK);
}

#
# Determine all available rpms
#

@AllRPMs= ();
@Include= ();
@Exclude= ();
if ($DoGet) {
	print "Getting new rpms.\n" if $Debug;
	if (@ARGV) {
		@GetList= @ARGV;
		@ARGV= ();
	} else {
		print "Warning: No rpms given to get. Searching for updates only.\n" if $Warnings;
	}
}
if (@ARGV) {
	print "Using rpms from the command line.\n" if $Debug;
	@AllRPMs= GetRPMHeaders( @ARGV );
	@ARGV= ();
	$cwd="." unless $cwd;
	$UpdateDir=$cwd;
	if ($DoDld) {
		print STDERR "Warning: RPMs given on the command line. Download part disabled.\n" if $Warnings;
		$DoDld=0;
	}
} else {
	print "Using rpms from $UpdateDir.\n" if $Debug;
	@AllRPMs= GetRPMHeaders("f", Get_RPMs($UpdateDir) );
}


#
# Creat new provides database
#

if ($CreateDB) {
	print "Creating provides database: $CreateDB\n" if $Verbose;
	if (tie(%Provides, 'DB_File', $CreateDB, O_CREAT|O_RDWR, 0644)) {
		my @rpms= SelectRPMs(\@AllRPMs, 1, 0);
		Get_Provides(@rpms) if (@rpms);
		untie(%Provides);
	} else {
		print STDERR "Error: Could not create $CreateDB: $!\n";
		unlink($LockFile) if ($DoLock);
		exit(1);
	}
	unlink($LockFile) if ($DoLock);
	exit(0);
}


#
# Remove bad rpms
#

Remove_Bad($UpdateDir) if ($RemoveBad>1);

#
# Provides database
#

if ($Resolve or $DoAddToDB or $MergeDB) {
	# Make sure the database exists
	unless ( -f $DataBase ) {
		print "Creating provides database: $DataBase\n" if $Verbose;
		if (tie(%Provides, 'DB_File', $DataBase, O_CREAT|O_RDWR, 0644)) {
			my @rpms= ();
			if ($RPMDir) {
				print "Adding $RPMDir to provides database.\n" if $Debug;
				@rpms= GetRPMHeaders("f", Get_RPMs($RPMDir) );
			} else {
				print "Adding installed rpms to provides database.\n" if $Debug;
				Get_Provides();
			}
			print "Adding $UpdateDir to provides database.\n" if $Debug;
			push(@rpms, GetRPMHeaders("f", Get_RPMs($UpdateDir) ));
			@rpms= SelectRPMs(\@rpms, 1, 0);
			Get_Provides(@rpms) if (@rpms and ! $Test);
			untie(%Provides);
		} else {
				print STDERR "Error: Could not create $DataBase: $!\n";
		}
	}
	if ($DoAddToDB) {
		print "Adding to provides database: $DataBase\n" if $Verbose;
		if (tie(%Provides, 'DB_File', $DataBase, O_RDWR, 0)) {
			my @rpms= SelectRPMs(\@AllRPMs, 1, 0);
			Get_Provides(@rpms) if (@rpms and ! $Test);
			untie(%Provides);
		} else {
			print STDERR "Error: Could not add to $DataBase: $!\n";
		}
	}
	if ($MergeDB) {
		print "Merging provides database: $MergeDB\n" if $Verbose;
		if (tie(%Provides, 'DB_File', $DataBase, O_RDWR, 0)) {
			my %NewProvides;
			if (tie(%NewProvides, 'DB_File', $MergeDB, O_RDONLY, 0)) {
				for $item (keys %NewProvides) {
					$Provides{$item}= $NewProvides{$item} unless ($Test);
				}
				untie(%NewProvides);
			} else {
				print STDERR "Error: Could not open $MergeDB: $!\n";
			}

			untie(%Provides);
		} else {
			print STDERR "Error: Could not add to $DataBase: $!\n";
		}
	}
	if ($DoGet or $DoDld or $DoUpdate) {
		print "Opening provides database: $DataBase\n" if $Debug;
		unless (tie(%Provides, 'DB_File', $DataBase, O_RDONLY, 0)) {
			print STDERR "Error: Could not open $DataBase: $!\n";
		}
	}
	if ($DoGet) {
		my @tmp=();
		foreach $item (@GetList) {
			if ( $Provides{$item} ) {
				$item= $Provides{$item};
			}
			push(@tmp, $item) unless (IsElement($item, @tmp));
		}
		@GetList=@tmp;
	}
}


#
# Dld Part
#

my $RetDld = 0;

if ($DoDld or $DoGet) {
  # Get a list of rpms we already have
  SetLocalRPMs();
  if ( $RPMDir ) {
	Get_Local_List($RPMDir);
  }
  if ( $DldGetInstalled or ! $RPMDir) {
	Get_Local_List();
  }
  Get_Local_List($UpdateDir);

  if ($DoGet) {
	print "Setting up fake local list for get mode.\n" if $Debug;
	my $haveall= 1;
	for $item (@GetList) {
		if (CheckLocal($item)) {
			print "  Already have $item.\n" if $Debug;
		} else {
			$haveall=0;
			AddLocalRPMs("r", "$item-0.0-0.noarch.rpm");
		}
	}
	$DoDld= 0 if ($haveall and $DoDld == 2);
  }
}

if ($DoDld) {
  print "\nDoing download part.\n" if $Debug;

  # See if the rpms we have already require some new ones
  if ($Resolve) {
	print "Checking requirements for old rpms.\n" if $Debug;
	Get_Requirements(SelectRPMs(\@AllRPMs, 1, 0));
  }
  if ($URL) {
	print "\nFetching rpms from URL passed on the command line:\n" if $Debug;
	my $dir;
	$Protocol="ftp"; #default
	if ($URL=~ /^(\w+):\/\/(.*)$/) {
		$Protocol=lc($1);
		$URL=$2;
	} elsif ($URL=~ /^file:(.*)$/i) {
		$Protocol="file";
		$URL=$1;
	}
	if ($Protocol eq "file") {
		$Host= "localhost";
		$dir= $URL;
	} else {
		($Host, $dir)= ($URL=~/([^\/]+)(.*)/);
		if ($Host=~/^(.*)\@(.+)$/) {
			($User, $Host)= ($1, $2);
		}
		if ($User and $User=~/^(\w*):(.*)$/) {
			($User, $Pass)= ($1, $2);
		}
	}
	$StoreDir= $UpdateDir;
	$Passive= $DefaultPassive;
	$HTTPSendHost= $DefaultHTTPSendHost;
	$UseLWP= $DefaultUseLWP;
	$dir= "." unless $dir;
	if ($Host) {
		@DldDirs= ();
		if ( $dir=~ /^(.+\/)\/$/ ) {
			$dir=$1;
			my @AllowedArch;
			my $Arch= &GetDistArch;
			if ($BestMatch) {
				@AllowedArch= &GetAllowedArch;
			} else {
				@AllowedArch= &GetAllowedDistArch;
			}
			@NoWarnDirs= ();
			for $item (@AllowedArch) {
				push(@NoWarnDirs, "$dir$item/") unless ($item eq $Arch);
				push(@DldDirs, "$dir$item/");
			}
		} else {
			@DldDirs= ($dir);
		}
		$RetDld= Get_Remote_RPMs();
	} else {
		$RetDld= 1;
		print STDERR "Error: No host given!\n";
	}
  } else {
	print "\nProcessing download config files:\n" if $Debug;
	my $file;
	unless ($DldConfigDir) {
		$DldConfigDir = $ConfigDir;
	}
	unless (@DldConfig) {
		if ( -d $DldConfigDir ) {
			if ($DoGet) {
				@DldConfig= glob("$DldConfigDir/*.get");
			} else {
				@DldConfig= glob("$DldConfigDir/*.dld $DldConfigDir/*.ftp");
			}
		} else {
			print STDERR "Error: No dld configuration directory: $DldConfigDir\n";
		}
	}
	unless (@DldConfig) {
		print STDERR "Warning: No download configuration files found.\n" if $Warnings;
	}
	foreach $file ( @DldConfig ) {
		if ( Read_Dld_Config($file)==0 ) {
			$RetDld+=Get_Remote_RPMs();
		}
	}
  }

  @NoWarnDirs= ();

  for $item (@GetList) {
	next unless (CheckLocal($item) eq "0.0-0");
	print STDERR "Warning: $item not found during download.\n" if $Warnings;
  }
  
  my $total = @DownloadedRPMs;

  #Update provides db
  if ($Resolve) {
	print "Updating provides database: $DataBase\n" if $Debug;
	my @rpms= ();
	# See if there are rpms we do not know about
	for $item ( @AllRPMs ) {
		push(@rpms,$item) unless ($Provides{$item->name});
	}
	push(@rpms,@DownloadedRPMs) if ($total);
	if (@rpms) {
		if (tie(%Provides, 'DB_File', $DataBase, O_RDWR, 0)) {
			Get_Provides(@rpms);
			untie(%Provides);
		} else {
			print STDERR "Error: Could not update $DataBase: $!\n";
		}
		#Open database ro again
		unless (tie(%Provides, 'DB_File', $DataBase, O_RDONLY, 0)) {
			print STDERR "Error: Could not open $DataBase: $!\n";
		}
	}
  }
  
  if ($total) {
	push(@AllRPMs, @DownloadedRPMs);
	print "Downloaded total of $total rpms.\n" if ($Verbose and $total>1);
	Do_PostScript($PostDldScript);
  }

  if ($DoLog) {
	my @log= ();
	push(@log, "sum: Downloaded total of $total rpm(s).");
	for $item (@DownloadedRPMs) {
		push(@log, "dld: " . $item->rpmname);
	}
	Do_Log(@log);
  }

@DownloadedRPMs= ();
}

#
# Purge part
#

@Exclude= ();
@Include= ();
Purge_RPMs() if ($DoPurge or $CleanUp);

#
# Update part
#

my $RetUpd = 0;
@UpdatedRPMs= ();
@RemovedKernel= ();

if ($DoUpdate) {
	print "\nDoing update/install part.\n" if $Debug;
	# Upgrade all rpms
	$RetUpd+=Update_RPMs();
	Do_PostScript($PostUpdateScript) if (@UpdatedRPMs);
	untie(%Provides);
	my $total = @UpdatedRPMs;
	my $status = "Upgraded";
	$status = "Installed" if ($DoInstall or $DoGet);
	print "$status total of $total rpms.\n" if ($Verbose and $total>1);
	if ($DoLog) {
		my @log= ();
		push(@log, "sum: $status total of $total rpm(s).");
			push(@log, "sum: Installed boot manager.") unless (! $ChangedBoot);
			my $tmp;
			for $item (@UpdatedRPMs) {
				$tmp= CheckLocal($item->name);
				if (!$tmp or $tmp eq "0.0-0") {
					$status="ins";
				} else {
					$status="upd";
				}
				push(@log, "$status: " . $item->rpmname);
			}
		Do_Log(@log);
	}
}


#
# Merge part
#

Merge_RPMs() if ($DoMerge and $RetUpd==0);

#
# Finally clean up
#

if ($CleanUp and @UpdatedRPMs) {
	print "\n" if $Debug;
	print "Removing updated rpms:\n" if $Verbose;
	foreach $item (@UpdatedRPMs) {
		next unless ( -e $item->filename); # Maybe removed during merge
		next unless (IsElement($item,@AllRPMs)); #Maybe added during get from rpmdir
		print "  " . $item->rpmname . "\n" if $Verbose;
		next if ($Test);
		unlink($item->filename) || print STDERR "Error: Failed to remove: ". $item->filename . " ($!).\n";
	}
}

unlink($LockFile) if ($DoLock); 
exit ($RetDld+$RetUpd);

