#!/usr/bin/perl
##############################################################################
# $Id: autorpm.pl,v 1.45 1998/06/12 17:01:29 kirk Exp $
##############################################################################
# Created by Kirk Bauer <kirk@kaybee.org>
# http://www.kaybee.org/~kirk
#
# Most current version can always be found at:
# ftp://ftp.kaybee.org/pub/redhat/RPMS
##############################################################################

use Getopt::Long;
use Net::FTP;
use File::Copy;

##############################################################################
# Variables
##############################################################################

# $Apply tells us if we are in --apply mode...
my $Apply = 0;

# Default config file
my $ConfigFile = '/etc/autorpm.conf';

# Default tmp directory
my $TempDir = '/var/spool/autorpm/';

# Default RPM location
my $RPMLocation = '/bin/rpm';

# Dialog or Whiptail program
my $MenuProg = '/usr/bin/dialog';
if (-x '/usr/bin/whiptail') {
    $MenuProg = '/usr/bin/whiptail --fullbuttons';
}
my $DialogProg = '/usr/bin/dialog';

my $Version = '1.6.1';
my $VDate = '06/11/98';
my $CurrFTPSite = '';

umask(0007);

# Don't wait for carriage return to display STDOUT...
$| = 1;

##############################################################################
# Small functions
##############################################################################

sub Usage {
    print "\nUsage: $0 [--apply] [--ftp <ftp-site> ]\n";
    print "[--dir <dir-name>] [--config <filename>] [--check]\n";
    print "   --config <filename> which config file to use.  The default is\n";
    print "                       /etc/autorpm.conf.\n";
    print "   --ftp <ftp-site>    Compares the remote FTP site to the locally\n";
    print "                       installed RPMs and prompts for actions.\n";
    print "   --dir <dir-name>    Compares the directory to the locally installed\n";
    print "                       RPMs and prompts for actions.\n";
    print "   --apply or\n";
    print "       --interactive   applies interactive RPM installs/updates.\n";
    print "   --debug             Turn on debug mode.\n";
    print "   --print             Force output to screen regardless of what\n";
    print "                       the config file says to do...\n";
    print "   --help or --usage   Displays this help message.\n";
    print "   --version           Displays the version of AutoRPM.\n\n";
    exit (255);
}

sub WaitEnter($) {
    print $_[0] . "\n";
    until (<STDIN> eq "\n") {
    }
}

sub Check_Dir($) {
    my $ThisDir = $_[0];
    # Add / to $ThisDir
    unless ($ThisDir =~ m=/$=) {
	$ThisDir = $ThisDir . '/';
    }
    # Create directory if necessary
    unless (-d $ThisDir) {
	#mkdir ($ThisDir,0770) or die "ERROR: Can't create directory " . $ThisDir . "\n";
	mkdir ($ThisDir,0770);
	Report ("\nMaking directory: " . $ThisDir . "\n");
    }
    return ($ThisDir);
}

##############################################################################
# RPM Version comparison code
##############################################################################

# rpm_split_name - split an RPM name into the base name and the version
# Arguments:
#    $_[0]	RPM file name (with or without directory prefix)
# Returns:	RPM base name, RPM version string, and Arch or undef on error
sub RPM_Split_Name($) {
    if ($_[0] =~ m#(.*/)?([^/]*)-([^-]*-[^-]*)\.(.*)\.rpm$#) {
	return ($2,$3,$4);
    }
    Report ("Bad RPM name: $_[0] - skipping it" . "\n");
    return undef;
}

sub BaseName($) {
    my $Temp = $_[0];
    $Temp =~ s#(.*/)?([^/]*-[^-]*-[^-]*)\..*\.rpm$#$2#;
    return ($Temp);
}

sub StripVersion($) {
    my $Temp = $_[0];
    $Temp =~ s#^([^/]*)-[^-]*-[^-]*$#$1#;
    return ($Temp);
}

# This version comparison code was sent in by Robert Mitchell and, although
# not yet perfect, is better than the original one I had. He took the code
# from freshrpms and did some mods to it. Further mods by Simon Liddington
# <sjl96v@ecs.soton.ac.uk>.
#
# Splits string into minors on . and change from numeric to non-numeric
# characters. Minors are compared from the beginning of the string. If the
# minors are both numeric then they are numerically compared. If both minors
# are non-numeric and a single character they are alphabetically compared, if
# they are not a single character they are checked to be the same if the are not
# the result is unknown (currently we say the first is newer so that we have
# a choice to upgrade). If one minor is numeric and one non-numeric then the
# numeric one is newer as it has a longer version string.
sub cmp_vers_part($$) {
  my($va, $vb) = @_;
  my(@va_dots, @vb_dots);
  my($a, $b);
  my($i);

  @va_dots = split(/\./, $va);
  @vb_dots = split(/\./, $vb);

  $a = shift(@va_dots);
  $b = shift(@vb_dots);
  while ((defined($a) && $a ne '') || (defined($b) && $b ne '')) {
    # compare each minor from left to right
    if ($a eq '') { return -1; }        # the longer version is newer
    if ($b eq '') { return 1; }
    if ($a =~ /^\d+$/ && $b =~ /^\d+$/) {
      # numeric compare
      if ($a != $b) { return $a <=> $b; }
    }
    elsif ($a =~ /^\D+$/ && $b =~ /^\D+$/) {
      # string compare
      if (length($a) == 1 && length($b) == 1) {
	# only minors with one letter seem to be useful for versioning
	if ($a ne $b) { return $a cmp $b; }
      }
      elsif (($a cmp $b) != 0) {
	# otherwise we should at least check they are the same and if not say unknown
	# say newer for now so at least we get choice whether to upgrade or not
	return -1;
      }
    }
    elsif ( ($a =~ /^\D+$/ && $b =~ /^\d+$/) || ($a =~ /^\d+$/ && $b =~ /^\D+$/) ) 
    {
      # if we get a number in one and a word in another the one with a number
      # has a longer version string
      if ($a =~ /^\d+$/) { return 1; }
      if ($b =~ /^\d+$/) { return -1; }
    }
    else {
      # minor needs splitting
      $a =~ /\d+/ || $a =~ /\D+/;
      # split the $a minor into numbers and non-numbers
      my @va_bits = ($`, $&, $');
      $b =~ /\d+/ || $b =~ /\D+/;
      # split the $b minor into numbers and non-numbers
      my @vb_bits = ($`, $&, $');

      for ($j=2; $j >= 0; $j--) {
	if ($va_bits[$j] ne '') { unshift(@va_dots,$va_bits[$j]); }
	if ($vb_bits[$j] ne '') { unshift(@vb_dots,$vb_bits[$j]); }
      }
    }
    $a = shift(@va_dots);
    $b = shift(@vb_dots);
  }
  return 0;
}

# RPM_Compare_Version - compare RPM version strings
# Arguments:
#    $_[0]		version string of "a"
#    $_[1]		version string of "b"
# Returns:		"a" <=> "b"
#        -1 = a < b, 0 = a==b, 1 = a > b
sub RPM_Compare_Version($$) {
    my ($Version1,$Release1,$Version2,$Release2,$Result);
    ($Version1,$Release1) = split (/-/,$_[0]);
    ($Version2,$Release2) = split (/-/,$_[1]);
    $Result = cmp_vers_part ($Version1,$Version2);
    if ($Result) {
	return ($Result);
    }
    return (cmp_vers_part ($Release1,$Release2));
}

##############################################################################
# Process_Local
##############################################################################

# Process_Local gets a list of local files ready and then calls the appropriate
# Source-Files function...
sub Process_Local {
    unless (@AutoIgnore) {
	Read_Auto_Ignore();
    }
    if ( ( (not(@{$Data->{'compare_to_dir'}} )) and
	 (not(@{$Data->{'recursive_compare_to_dir'}}) ) ) or
	 ( (@{$Data->{'compare_to_installed'}}) and ($Data->{'compare_to_installed'}->[0]) ) ) {
	# We are comparing to the installed RPMs.
	Inform ("Comparing to locally installed RPMs\n");
	@LocalFiles = `$RPMLocation -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}.rpm\n'`;
	chomp(@LocalFiles);
    }
    if ( (@{$Data->{'compare_to_dir'}} ) or
	 (@{$Data->{'recursive_compare_to_dir'}} )
	 ) {
	# We are comparing to one or more local directories
	foreach $ThisDir ( @{$Data->{'compare_to_dir'}},
			   @{$Data->{'recursive_compare_to_dir'}} ) {

	    Inform ("\nComparing to directory: " . $ThisDir . "\n");

	    # Add / to end of directory
	    unless ($ThisDir =~ m=/$=) {
		$ThisDir = $ThisDir . '/';
	    }

	    # Create Directory...
	    unless (-d $ThisDir) {
		mkdir ($ThisDir,0770) or die "ERROR: Can't create directory " . $ThisDir . "\n";
		Report ("\nMaking directory: " . $ThisDir . "\n");
	    }

	    opendir (LOCALDIR,$ThisDir) or die "ERROR: Can't open local directory " . $ThisDir . "\n";
	    while (defined($ThisFile = readdir(LOCALDIR))) {
		unless (-d $ThisDir . $ThisFile) {
		    if ($ThisFile =~ m/\.rpm$/) {
			push @LocalFiles, $ThisDir . $ThisFile;
		    }
		}
	    }
	    closedir(LOCALDIR);
	}
    }
    if ($Debug) {
	Report ('DEBUG: Local Files' . "\n");
	foreach $DebugTemp (@LocalFiles) {
	    Report ('   DEBUG: ' . $DebugTemp . "\n");
	}
    }
    # Okay, local file-list is done, now lets get the remote one...
    Process_Remote();
    # Blank line
    Inform ("\n");
}

##############################################################################
# Do_Actions (process the Action Block)
##############################################################################

sub Do_Actions($$$) {
    my ($ActionType, $SourceFile, $LocalFile) = @_;
    my ($Msg,$ReportCommand,$i,$ThisAction,$Delete,$QueueFile,$Cached,$CopyTo,$bn,$ForceInstallChange);
    if ( $LocalFile =~ m/^\// ) {
	$Msg = ' local file ';
    }
    else {
	$Msg = ' installed RPM ';
    }
    if ( keys %{$Data->{$ActionType}} ) {
	$ReportCommand = 1;
	if ( ( @{$Data->{$ActionType}->{'report'}} ) and
	     ( $Data->{$ActionType}->{'report'}->[0] == 0 ) ) {
	    $ReportCommand = 0;
	}
	$ForceInstallChange = 0;
	# This next block used for both pgp_require and install
	$Cached = '';
	if ($FTP) {
	    $QueueFile = $SourceFile;
	    $QueueFile =~ s=^.*/([^/]+)$=$1=;
	    $QueueFile = $TempDir . $QueueFile;
       if ($FTPUser eq 'anonymous') {
	     $Cached = 'ftp://' . $FTPSiteName . $SourceFile;
       }
       else {
	      $Cached = 'ftp://' . $FTPUser . ':' . $FTPPasswd . '@' . $FTPSiteName . $SourceFile;
       }
	}
	else {
	    $QueueFile = $SourceFile;
	}
	foreach $ThisAction ( keys %{$Data->{$ActionType}} ) {
	    if ($ThisAction eq 'recursive_store') {
		for $i ( 0 .. $#{$Data->{$ActionType}->{'recursive_store'}} ) {
		    # Create Directory...
		    unless (-d $Data->{$ActionType}->{'recursive_store'}->[$i]) {
			if ($ReportCommand) {
			    mkdir ($Data->{$ActionType}->{'recursive_store'}->[$i],0770) or die "ERROR: Can't create directory " . $Data->{$ActionType}->{'recursive_store'}->[$i] . "\n";
			}
			Report ("\nMaking directory: " . $Data->{$ActionType}->{'recursive_store'}->[$i] . "\n\n");
		    }
		    unless ( $Data->{$ActionType}->{'recursive_store'}->[$i] =~ m=/$= ) {
			$Data->{$ActionType}->{'recursive_store'}->[$i] .= '/';
		    }
		    if ($ReportCommand) {
			Report ('   Storing ' . $SourceFile . "\n      into " . $Data->{$ActionType}->{'recursive_store'}->[$i] . '... ');
		    }
		    if (VFS_Store($SourceFile,$Data->{$ActionType}->{'recursive_store'}->[$i])) {
			if ($ReportCommand) {
			    Report ("Done.\n");
			}
			if ($FTP) {
			    # Queue File is now wherever this was just stored...
			    $QueueFile = $SourceFile;
			    $QueueFile =~ s=^.*/([^/]+)$=$1=;
			    $QueueFile = $Data->{$ActionType}->{'recursive_store'}->[$i] . '/' . $QueueFile;
			}
		    }
		    else {
			Report ("Error.\n");
			print STDERR "Couldn't place " . $SourceFile . ' into ' . $Data->{$ActionType}->{'recursive_store'}->[$i] . "!\n";
		    }
		}
	    }
	    if ($ThisAction eq 'store') {
		for $i ( 0 .. $#{$Data->{$ActionType}->{'store'}} ) {
		    # Create Directory...
		    unless (-d $Data->{$ActionType}->{'store'}->[$i]) {
			mkdir ($Data->{$ActionType}->{'store'}->[$i],0770) or die "ERROR: Can't create directory " . $Data->{$ActionType}->{'store'}->[$i] . "\n";
			if ($ReportCommand) {
			    Report ("\nMaking directory: " . $Data->{$ActionType}->{'store'}->[$i] . "\n\n");
			}
		    }
		    if ($ReportCommand) {
			Report ('   Storing ' . $SourceFile . "\n      into " . $Data->{$ActionType}->{'store'}->[$i] . '... ');
		    }
		    if (VFS_Store($SourceFile,$Data->{$ActionType}->{'store'}->[$i])) {
			if ($ReportCommand) {
			    Report ("Done.\n");
			}
			if ($FTP) {
			    # Queue File is now wherever this was just stored...
			    $QueueFile = $SourceFile;
			    $QueueFile =~ s=^.*/([^/]+)$=$1=;
			    $QueueFile = $Data->{$ActionType}->{'store'}->[$i] . '/' . $QueueFile;
			}
		    }
		    else {
			Report ("Error.\n");
			print STDERR "Couldn't place " . $SourceFile . ' into ' . $Data->{$ActionType}->{'store'}->[$i] . "!\n";
		    }
		}
	    }
	    if (($ThisAction eq 'delete_old_version') and
		($Data->{$ActionType}->{$ThisAction}->[0] == 1) and
		($ActionType eq 'updated') ) {
		if (unlink($LocalFile)) {
		    if ($ReportCommand) {
			Report ('   Deleted old local version: ' . $LocalFile . "\n");
		    }
		}
		else {
		    print STDERR "Couldn't remove " . $LocalFile . "!\n";
		}
	    }
	    if (($ThisAction eq 'pgp_require') and
		($Data->{$ActionType}->{'install'}->[0] != 0) and
      ($Data->{$ActionType}->{'pgp_require'}->[0] ne "0") ) {
		# Only applies for Auto-Installs or Interactive-Installs...
		$bn = BaseName($QueueFile);
      Inform ('   Checking PGP Signature for ' . $bn . '... ');
		unless (@{$Data->{$ActionType}->{'pgp_fail_install'}} ) {
		    $Data->{$ActionType}->{'pgp_fail_install'}->[0] = 1;
		}
		Get_Remote_File ($Cached,$QueueFile);
		@Output = `$RPMLocation --checksig $QueueFile 2>&1`;
		chomp (@Output);
		foreach $i (@Output) {
		    if ( $i =~ /^\Q$QueueFile\E: size .*pgp.* md5 OK.*$/ ) {
         Inform ("Good Signature.\n");
          }
          else {
			Report ("\n   PGP Check Failed for " . $bn . ", switching from Auto to Interactive Install mode.\n");
			$ForceInstallChange = 1;
		    }
		}
		if ( ($ForceInstallChange) and ($Data->{$ActionType}->{'pgp_fail_install'}->[0] == 0) and ($QueueFile)) {
			Report ('   ' . $bn . " won't be installed, deleting file cached from FTP site.\n");
		}
	    }
	    if ( ($ThisAction eq 'install') and
		 ($Data->{$ActionType}->{$ThisAction}->[0] != 0) and
		 ( ($ActionType eq 'new') or ($ActionType eq 'updated') ) and
		 ( not ( ($ForceInstallChange) and ($Data->{$ActionType}->{'pgp_fail_install'}->[0] == 0) ))) {
		$Delete = 0;
		$CopyTo = '';
		if ($ReportCommand) {
		    if ($ActionType eq 'new') {
			Report ('   ' . BaseName($SourceFile) . " is a new RPM and could be installed.\n");
		    }
		    else {
			Report ('   ' . BaseName($SourceFile) . " is an updated RPM and could be upgraded.\n");
		    }
		}
		if ( ( ( @{$Data->{$ActionType}->{'delete_after_install'}} ) and
		     ($Data->{$ActionType}->{'delete_after_install'}->[0] == 1) ) or
		     (($FTP) and ( not ( (@{$Data->{$ActionType}->{'delete_after_install'}}) and ( $Data->{$ActionType}->{'delete_after_install'}->[0] == 0 )) ) ) ) {
		    $Delete = 1;
		}
		if ( @{$Data->{$ActionType}->{'copy_after_install'}} ) {
		    foreach $i ( @{$Data->{$ActionType}->{'copy_after_install'}} ) {
			if ($CopyTo) {
			    $CopyTo .= ':';
			}
			$CopyTo .= $Data->{$ActionType}->{'copy_after_install'}->[$i];
		    }
		}
		if ( @{$Data->{$ActionType}->{'recursive_copy_after_install'}} ) {
		    foreach $i ( @{$Data->{$ActionType}->{'recursive_copy_after_install'}} ) {
			if ($CopyTo) {
			    $CopyTo .= ':';
			}
			$CopyTo .= $Data->{$ActionType}->{'recursive_copy_after_install'}->[$i];
		    }
		}
		if (($Data->{$ActionType}->{'install'}->[0] == 1) or
		    ( ($ForceInstallChange) and ($Data->{$ActionType}->{'pgp_fail_install'}->[0] == 1) ) ) {
		    # Interactive install...
		    open (QUEUE,'>>' . $TempDir . 'interactive.queue') or
			die "ERROR: Can't open " . $TempDir . "interactive.queue\n";
		}
		else {
		    open (QUEUE,'>>' . $TempDir . 'auto.queue') or
			die "ERROR: Can't open " . $TempDir . "auto.queue\n";
		}
		unless (@{$Data->{$ActionType}->{'allow_delete'}}) {
		    if ($FTP) {
			$Data->{$ActionType}->{'allow_delete'}->[0] = 1;
		    }
		    else {
			if ( ( @{$Data->{$ActionType}->{'delete_after_install'}} ) and
			     ($Data->{$ActionType}->{'delete_after_install'}->[0] == 1) ) {
			    $Data->{$ActionType}->{'allow_delete'}->[0] = 1;
			}
			else {
			    $Data->{$ActionType}->{'allow_delete'}->[0] = 0;
			}
		    }
		}
		print QUEUE '!' . $QueueFile . ';' . $Delete . ';' . $Cached . ';0;' . $LocalFile . ';' . $Data->{$ActionType}->{'allow_delete'}->[0] . ';' . $CopyTo . "\n";
		close (QUEUE);
	    }
	}
    }
    else {
	if ( $ActionType eq 'new' ) {
	    Report ( 'Source File ' . $SourceFile . " is brand new.\n" );
	    Report ( "   But nothing done as no action block was defined.\n");
	}
	elsif ( $ActionType eq 'updated' ) {
	    Report ( 'Source File ' . $SourceFile . ' is newer than' . $Msg . $LocalFile . "\n");
	    Report ( "   But nothing done as no action block was defined.\n");
	}
    }
}

##############################################################################
# Process_Remote and Process_RPM_File
##############################################################################

sub Process_Remote {
    my (@DirList,$ThisDir,$ThisRegex,$Okay,$SubDir,$Temp,$ThisAction,$MySourceLocation);
    VFS_Open();
    while ($ThisFile = VFS_Read()) {
	if ( $ThisFile =~ m=/$= ) {
	    # The file is a directory...
	    unless ( ($ThisFile =~ m=\./$=) or ($ThisFile =~ m=\.\./$=) ) {
		push @DirList, $ThisFile;
	    }
	}
	elsif ($ThisFile =~ /\.rpm$/ ) {
	    # Okay, now we have a "Remote" RPM... lets figure out if we care about it
	    Process_RPM_File ($ThisFile);
	}
    }
    # Process recursion here...
    if ( (@DirList) and
	 ( @{$Data->{'recursive'}} ) and
	 ( $Data->{'recursive'}->[0] = 1) ) {
	unless ($SourceLocation =~ m=/$=) {
	    $SourceLocation .= '/';
	}
	foreach $ThisDir (@DirList) {
	    $Okay = 1;
	    $SubDir = $ThisDir;
	    $MySourceLocation = $SourceLocation;
	    if ($FTP) {
		$MySourceLocation =~ s=^ftp://[^/]+==;
	    }
	    $SubDir =~ s=^$MySourceLocation==;
	    if ( @{$Data->{'regex_dir_ignore'}} ) {
		# Apply Regex_Dir_Ignores...
		foreach $ThisRegex (@{$Data->{'regex_dir_ignore'}}) {
		    if ( $SubDir =~ m/$ThisRegex/ ) {
			$Okay = 0;
		    }
		}
	    }
	    if (( @{$Data->{'regex_dir_accept'}} ) and
		( $Okay ) ) {
		# Apply Regex_Dir_Accepts only if dir hasn't been denied already...
		# By specifying any Regex_Dir_Accepts, every dir must match at least
		# one of them...
		$Okay = 0;
		foreach $ThisRegex (@{$Data->{'regex_dir_accept'}}) {
		    if ( $SubDir =~ m/$ThisRegex/ ) {
			$Okay = 1;
		    }
		}
	    }
	    if ($Okay) {
		# Looks like we have to recurse into this directory...
		$SourceLocation .= $SubDir;
		if ( @{$Data->{'recursive_compare_to_dir'}} ) {
		    for $Temp ( 0 .. $#{@{$Data->{'recursive_compare_to_dir'}}} ) {
			$Data->{'recursive_compare_to_dir'}->[$Temp] .= $SubDir;
		    }
		}
		foreach $ThisAction ( 'new', 'updated', 'old', 'same' ) {
		    if ( @{$Data->{$ThisAction}->{'recursive_store'}} ) {
			for $Temp ( 0 .. $#{@{$Data->{$ThisAction}->{'recursive_store'}}} ) {
			    unless ( $Data->{$ThisAction}->{'recursive_store'}->[$i] =~ m=/$= ) {
				$Data->{$ThisAction}->{'recursive_store'}->[$i] .= '/';
			    }
			    $Data->{$ThisAction}->{'recursive_store'}->[$Temp] .= $SubDir;
			}
		    }
		    if ( @{$Data->{$ThisAction}->{'recursive_copy_after_install'}} ) {
			for $Temp ( 0 .. $#{@{$Data->{$ThisAction}->{'recursive_copy_after_install'}}} ) {
			    unless ( $Data->{$ThisAction}->{'recursive_copy_after_install'}->[$i] =~ m=/$= ) {
				$Data->{$ThisAction}->{'recursive_copy_after_install'}->[$i] .= '/';
			    }
			    $Data->{$ThisAction}->{'recursive_copy_after_install'}->[$Temp] .= $SubDir;
			}
		    }
		}
		@LocalFiles = ();
		Process_Local();
		# Go up a directory on everything now...
		$SourceLocation =~ s/$SubDir$//;
		if ( @{$Data->{'recursive_compare_to_dir'}} ) {
		    for $Temp ( 0 .. $#{@{$Data->{'recursive_compare_to_dir'}}} ) {
			$Data->{'recursive_compare_to_dir'}->[$Temp] =~ s/$SubDir$//;
		    }
		}
		foreach $ThisAction ( 'new', 'updated', 'old', 'same' ) {
		    if ( @{$Data->{$ThisAction}->{'recursive_store'}} ) {
			for $Temp ( 0 .. $#{@{$Data->{$ThisAction}->{'recursive_store'}}} ) {
			    $Data->{$ThisAction}->{'recursive_store'}->[$Temp] =~ s/$SubDir$//;
			}
		    }
		    if ( @{$Data->{$ThisAction}->{'recursive_copy_after_install'}} ) {
			for $Temp ( 0 .. $#{@{$Data->{$ThisAction}->{'recursive_copy_after_install'}}} ) {
			    $Data->{$ThisAction}->{'recursive_copy_after_install'}->[$Temp] =~ s/$SubDir$//;
			}
		    }
		}
	    }
	}
    }
    VFS_Close();
}

sub Process_RPM_File($) {
    my (@LocalMatches, $Temp, $Result, $ThisRegex, $TestArch,
	$SourcePackageName,$SourceVersionString,$SourceArch,
	$LocalPackageName, $LocalVersionString, $LocalArch);
    my $ThisFile = $_[0];
    my $Okay = 1;
    my $bn = BaseName ($ThisFile);
    if (defined(@{$Data->{'regex_ignore'}}) and
	( @{$Data->{'regex_ignore'}} )) {
	# Apply Regex_Ignores...
	foreach $ThisRegex (@{$Data->{'regex_ignore'}}) {
	    if ( $bn =~ m/$ThisRegex/ ) {
		$Okay = 0;
	    }
	}
    }
    if (defined(@{$Data->{'regex_accept'}}) and
	( @{$Data->{'regex_accept'}} ) and
	( $Okay ) ) {
	# Apply Regex_Accepts only if file hasn't been denied already...
	# By specifying any Regex_Accepts, every file must match at least
	# one of them...
	$Okay = 0;
	foreach $ThisRegex (@{$Data->{'regex_accept'}}) {
	    if ( $bn =~ m/$ThisRegex/ ) {
		$Okay = 1;
	    }
	}
    }
    if (defined(@{$Data->{'ignore_arch'}}) and
	( @{$Data->{'ignore_arch'}} ) and
	( $Okay ) ) {
	# If the file is still okay, but there are some archs to ignore
	foreach $ThisArch (@{$Data->{'ignore_arch'}}) {
	    if ( $ThisFile =~ m/$ThisArch\.rpm$/ ) {
		$Okay = 0;
	    }
	}
    }
    if (defined(@{$Data->{'accept_arch'}}) and
	( @{$Data->{'accept_arch'}} ) and
	( $Okay ) ) {
	# Apply Accept_Archs only if file hasn't been denied already...
	# By specifying any Accept_Archs, every file must match at least
	# one of them...
	$Okay = 0;
	foreach $ThisArch (@{$Data->{'accept_arch'}}) {
	    if ( $ThisFile =~ m/$ThisArch\.rpm$/ ) {
		$Okay = 1;
	    }
	}
    }
    if ($Okay) {
	# Well, the file looks good if we are here...
	# Now, we are ready to find out if the file is older, newer, the
	# same, or brand-new, and take the appropriate action.
	if (($SourcePackageName,$SourceVersionString,$SourceArch) =
	    RPM_Split_Name($ThisFile) ) {
	    if ($Debug) {
		Report ('DEBUG: Comparing File ' . $ThisFile . ' (' . $SourcePackageName . ")\n");
	    }
	    # Use grep to pull out any local packages of the same name...
            # If source architecture is noarch accept any architecture in local files
	    if ($SourceArch eq 'noarch') {
		$TestArch = "[^\.]*";
	    }
	    else {
		$TestArch = "(\Q$SourceArch\E|noarch)";
	    }
	    @LocalMatches = grep (/(.*\/|^)\Q$SourcePackageName\E-[^-]+-[^-]+\.$TestArch\.rpm$/, @LocalFiles);
	    if ($Debug) {
		foreach $DebugTemp (@LocalMatches) {
		    Report ('   DEBUG: Local Match: ' . $DebugTemp . "\n");
		}
	    }
	    if (@LocalMatches) {
		# There is at least one local package by that name...
		foreach $Temp (@LocalMatches) {
		    unless (Test_Auto_Ignore($ThisFile,$Temp)) {
			if (($LocalPackageName,$LocalVersionString,$LocalArch) =
			    RPM_Split_Name($Temp) ) {
			    if ($LocalPackageName eq $SourcePackageName) {
				$Result = RPM_Compare_Version ($SourceVersionString,$LocalVersionString);
				if ($Result == -1) {
				    # 'old'
				    Do_Actions('old',$ThisFile,$Temp);
				}
				elsif ($Result == 0) {
				    # 'same'
				    Do_Actions('same',$ThisFile,$Temp);
				}
				elsif ($Result == 1) {
				    # 'updated'
				    Do_Actions('updated',$ThisFile,$Temp);
				}
			    }
			}
		    }
		}
	    }
	    else {
		# There are no local packages by that name...
		# Therefore, remote package is 'new'
		unless (Test_Auto_Ignore($ThisFile,'')) {
		    Do_Actions('new',$ThisFile,'');
		}
	    }
	}
    }
}

##############################################################################
# Report commands
##############################################################################

# There are now 2 commands, on a per-source-block-basis
# Report_To ('email_address') or Report_To ('PRINT')
#    In $Data->{'report_to'}->[0]
#    Any unique email address will have its own file that will be mailed
#    after AutoRPM is done running...
# Report_All (Yes or No)
#    In $Data->{'report_all'}->[0]

# The global variable, $ForcePrint will force the output to the screen.

# Reporting has its own data structure,
#    $Report{'email_address'}, the file...
# Internal Variables:
#    CURRMAILFILE
#    $SendCurrReport
#    $PrintReport
#    $TempMailFile

sub Start_Report {
    $NoReport = 0;
    if (($ForcePrint) or
	(not @{$Data->{'report_to'}} ) or
	($Data->{'report_to'}->[0] eq 'PRINT') ) {
	# Print Report
	$PrintReport = 1;
    }
    elsif ( (@{$Data->{'report_to'}} ) and
	(not $Data->{'report_to'}->[0]) ) {
	# Null Report_To, so don't report...
	$NoReport = 1;
    }
    else {
	# Mail Report
	$PrintReport = 0;
	$SendCurrReport = 0;
	if ( ( @{$Data->{'report_all'}} ) and
	     ( $Data->{'report_all'}->[0] == 1 ) ) {
	    $SendCurrReport = 1;
	}
	$TempMailFile = $TempDir . 'mail.report.tmp';
	open (CURRMAILFILE,'>' . $TempMailFile);
    }
    unless ($NoReport) {
	my $Date = `date`;
	chomp($Date);
	Inform ("\n***********************************************\nAutoRPM $Version on $HostName started $Date\n\n");
    }
    if ($Debug) {
	Report ("\nDEBUG: If you are having problems, send the output of this to <kirk\@kaybee.org>\n");
    }
}

# Submit a message to report and note that this report contains an action
sub Report($) {
    my $Msg = $_[0];
    unless ($NoReport) {
	if ($PrintReport) {
	    print $Msg;
	}
	else {
	    print CURRMAILFILE $Msg;
	    $SendCurrReport = 1;
	}
    }
}

# Submit a message to report but do NOT note that this report contains an action
sub Inform {
    my $Msg = $_[0];
    unless ($NoReport) {
	if ($PrintReport) {
	    print $Msg;
	}
	else {
	    print CURRMAILFILE $Msg;
	}
    }
}

sub End_Report {
    my $Temp = $Data->{'report_to'}->[0];
    my $Temp2;
    my $Date = `date`;
    unless ($NoReport) {
	chomp($Date);
	#Inform ("\nFinished $Date\n*************************************\n\n");
	Inform ("\n*************************************\nFinished $Date\n\n");
	unless ($PrintReport) {
	    if ($SendCurrReport) {
		unless ( $Report{$Temp} ) {
		    # This is the file that the message will be placed into...
		    $Report{$Temp} = $TempDir . $Temp;
		    $Report{$Temp} =~ s/\@/_/g;
		}
		close (CURRMAILFILE);
		$Temp2 = $Report{$Temp};
		`cat $TempMailFile >> $Temp2`;
		unlink ($TempMailFile);
	    }
	}
    }
}

sub Mail_Reports {
    my $Temp;
    foreach my $ThisAddress (keys %Report) {
	$Temp = $Report{$ThisAddress};
	if ( -s $Temp ) {
	    # The file exists and is non-empty, so mail it...
	    `mail -s "AutoRPM on $HostName" $ThisAddress < $Temp`;
	    unlink ($Temp);
	}
    }
}

##############################################################################
# VFS Functions
##############################################################################

sub VFS_Open {
    unless (($DoingCache) or ($FTP)) {
	Inform ('RPM Source: ' . $SourceLocation . "\n\n");
    }
    if ($FTP) {
	%FTPOptions = ();
	if ($FTPPassiveMode) {
	    $FTPOptions{'Passive'} = 1;
	}
	if ($FTPTimeOut) {
	    $FTPOptions{'Timeout'} = $FTPTimeOut;
	}
	if ($FTPFirewall) {
	    $FTPOptions{'Firewall'} = $FTPFirewall;
	}
	if ($FTPPort) {
	    $FTPOptions{'Port'} = $FTPPort;
	}
   unless ( ($FTPUser, $FTPPasswd, $FTPSiteName, $CurrFTPDir) = ($SourceLocation =~ m=^ftp://([^:]+):([^@]+)@([^/]+)(/.+)$=) ) {
      ($FTPSiteName, $CurrFTPDir) = ($SourceLocation =~ m=^ftp://([^/]+)(/.+)$=);
      $FTPUser = 'anonymous';
      $FTPPasswd = 'AutoRPM@' . $HostName;
   }
	# Add / to end of directory
	unless ($CurrFTPDir =~ m=/$=) {
	    $CurrFTPDir = $CurrFTPDir . '/';
	}
	unless ($FTPSiteName eq $CurrFTPSite) {
	    if ($Debug) {
		Report ('Connecting to new FTP site: ' . $FTPSiteName . ', Directory: ' . $CurrFTPDir);
	    }
	    $CurrFTPSite = $FTPSiteName;
	    unless ($DoingCache) {
		Inform ('Connecting to ' . $FTPSiteName . "...\n");
	    }
	    unless ($FTP_Object = Net::FTP->new($FTPSiteName, %FTPOptions)) {
		Report "ERROR: Can't connect to " . $FTPSiteName . "\n";
		$CurrFTPSite = '';
	    }
	    else {
		unless ($DoingCache) {
		    Inform ('Logging in as ' . $FTPUser . "\n");
		}
		unless ($FTP_Object->login($FTPUser,$FTPPasswd)) 
		{
		    Report "ERROR: Can't login as " . $FTPUser . ' on ' . $FTPSiteName . "\n";
		    $CurrFTPSite = '';
		}
	    }
	}
	unless ($DoingCache) {
		$FTP_First = 1;
	}
    }
    else {
	# Add / to end of directory
	unless ($SourceLocation =~ m=/$=) {
	    $SourceLocation = $SourceLocation . '/';
	}
	opendir (SOURCEDIR,$SourceLocation) or Report ("ERROR: Can't open directory " . $SourceLocation . "\n");
    }
}

sub VFS_Store($$) {
    my ($SourceFile, $DestDir) = @_;
    my ($RemoteDir,$RemoteFile,$Temp);
    if ($CurrFTPSite) {
	unless ($DestDir =~ m=/$= ) {
	    $DestDir .= '/';
	}
	if ($FTP) {
	    $FTP_Object->binary();
	    ($RemoteDir,$RemoteFile) = ( $SourceFile =~ m=(^.*/)([^/]+)$=);
	    $FTP_Object->cwd($RemoteDir);
	    $Temp =  $FTP_Object->get($RemoteFile,$DestDir . $RemoteFile);
	    `chmod a+r $DestDir/$RemoteFile`;
	    return ($Temp);
	}
	else {
	    $Temp = copy($SourceFile,$DestDir);
	    ($RemoteDir,$RemoteFile) = ( $SourceFile =~ m=(^.*/)([^/]+)$=);
	    `chmod a+r $DestDir/$RemoteFile`;
	    return ($Temp);
	}
    }
}

sub VFS_Read {
    my ($ThisFile,$Temp,$Temp2,$LinkName,$LinkDest,@DirListing,@TempListing,$IsDir,$FileName);
    # Any directories returned need to end in a /...
    if ($FTP) {
	if ($CurrFTPSite) {
	    if ($FTP_First) {
		@SourceList = ();
		$FTP_First = 0;
		Inform ('Listing Directory: ' . $CurrFTPDir . "\n\n");
		@DirListing = $FTP_Object->dir($CurrFTPDir) or print STDERR "ERROR: Can't get FTP file listing...\n";
		foreach $Temp (@DirListing) {
		    if ( ($LinkName,$LinkDest) = ($Temp =~ /^l[rwxst-]{9}\s+\S+\s+\S+\s+\S+\s+[0123456789]+ ... .. [ ]?..[:]?..\s+(.*) -> (.*)$/ ) ) {
			# Symbolic link...
			unless ($LinkDest =~ m=^/= ) {
			    # Relative Link...
			    $LinkDest = $CurrFTPDir . $LinkDest;
			}
			@TempListing = $FTP_Object->dir($LinkDest) or print STDERR "ERROR: Can't get FTP file listing of " . $LinkDest . "\n";
			$IsDir = 0;
			foreach $Temp2 (@TempListing) {
			    if ( $Temp2 =~ /^d[rwxst-]{9}\s+\S+\s+\S+\s+\S+\s+[0123456789]+ ... .. [ ]?..[:]?.. \.$/ ) {
				# Well, we now know that the sybolic link points to a directory.
				#push @SourceList, $LinkDest . '/';
				push @SourceList, $CurrFTPDir . $LinkName . '/';
				$IsDir = 1;
			    }
			}
			unless ($IsDir) {
			    # Points to a file...
			    push @SourceList, $LinkDest;
			}
		    }
		    elsif ( ($FileName) = ($Temp =~ /^d[rwxst-]{9}\s+\S+\s+\S+\s+\S+\s+[0123456789]+ ... .. [ ]?..[:]?..\s+(.*)$/ ) ) {
			# Directory...
			push @SourceList, $CurrFTPDir . $FileName . '/';
		    }
		    elsif ( ($FileName) = ($Temp =~ /^[rwxst-]{10}\s+\S+\s+\S+\s+\S+\s+[0123456789]+ ... .. [ ]?..[:]?..\s+(.*)$/ ) ) {
			# Regular file...
			push @SourceList, $CurrFTPDir . $FileName;
		    }
		}
		$SourceList = sort (@SourceList);
	    }
	    if (@SourceList) {
		return (shift(@SourceList));
	    }
	    else {
		return ('');
	    }
	}
	else {
	    return ('');
	}
    }
    else {
	if (defined($ThisFile = readdir(SOURCEDIR))) {
	    $ThisFile = $SourceLocation . $ThisFile;
	    if ( -d $ThisFile ) {
		unless ($ThisFile =~ m=/$=) {
		    $ThisFile = $ThisFile . '/';
		}
	    }
	    return ($ThisFile);
	}
	else
	{
	    return ('');
	}
    }
}

sub VFS_Close {
    if ($FTP) {
	$CurrFTPDir = '';
	$CurrFTPSite = '';
	#$FTP_Object->quit;
    }
    else {
	closedir (SOURCEDIR);
    }
}

##############################################################################
# Read_Config
##############################################################################

sub Config_Trim($) {
    my $Line = $_[0];
    $Line =~ s/^([^\#\"]*)#.*$/$1/;
    $Line =~ s/^(\".*\")#.*$/$1/;
    $Line =~ s/^\s+//;
    $Line =~ s/\s+$//;
    chomp ($Line);
    return ($Line);
}

sub Reset_Config_Vars {
    %$Data = ();
    $NeedFuncName = 1;
    $NeedOParen = 0;
    $NeedCParen = 0;
    $NeedSemiColon = 0;
    $NeedOCurly = 0;
    $NeedCCurly = 0;
    $InSourceBlock = 0;
    $InActionBlock = 0;
    $ActionName = 0;
    $ProcessCurr = 0;
    $Dirty = 0;
    $LastDirty = 1;
    $FTP = 0;
    $SourceLocation = 0;
    $LineNum = 0;
    @LocalFiles = ();
}

sub Read_Config {
    # Open the config file
    open (CONFFILE,$ConfigFile) or print STDERR 'ERROR: Cannot open config file: ' . $ConfigFile . "\n";

    Reset_Config_Vars();

    while (defined($ThisLine = <CONFFILE>)) {

	$LineNum++;

	$ThisLine = Config_Trim($ThisLine);

	until ($ThisLine eq '') {

	    # Looks for a new function name...
	    if ($NeedFuncName) {
		$FuncName = $ThisLine;
		$FuncName =~ s/\(.*$//;
		$FuncName = Config_Trim ($FuncName);
		if ($FuncName) {
		    if ($FuncName =~ m/ /) {
			die 'ERROR: Error parsing ' . $ConfigFile . "\nSpace not allowed in function name on line " . $LineNum . "\n";
		    }
		    unless ($FuncName =~ m/[\{\}\(\)\;\#\"]/ ) {
			$FuncName = lc($FuncName);
			$NeedFuncName = 0;
			$Dirty++;
			$LastDirty = $LineNum;
			$NeedOParen = 1;
			unless ($ThisLine =~ s/^.*?\(/\(/ ) {
			    $ThisLine = '';
			}
			$ThisLine = Config_Trim($ThisLine);
		    }
		}
	    }

	    # Looks for an opening parentesis..
	    if ($ThisLine =~ s/^\(//) {
		if ($NeedOParen) {
		    $NeedOParen = 0;
		    $NeedParam = 1;
		    $ThisLine = Config_Trim($ThisLine);
		}
		else {
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', ( not expected, Line ' . $LineNum . "\n";
		}
	    }

	    # Looks for an opening curly-brace...
	    if ($ThisLine =~ s/^\{//) {
		if ($NeedOCurly) {
		    $NeedOCurly = 0;
		    $NeedCCurly = 1;
		    $NeedFuncName = 1;
		    if ($InSourceBlock) {
			if ($InActionBlock) {
			    die 'ERROR: Error Parsing ' . $ConfigFile . ', line ' . $LineNum . "\n";
			}
			else {
			    $InActionBlock = 1;
			}
		    }
		    else {
			$InSourceBlock = 1;
		    }
		    $ThisLine = Config_Trim($ThisLine);
		}
		else {
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', { not expected, Line ' . $LineNum . "\n";
		}
	    }

	    # Looks for an closing curly-brace...
	    if ($ThisLine =~ s/^\}// ) {
		if ( ($NeedCCurly) and
		     ($NeedOParen == 0) and
		     ($NeedCParen == 0) and
		     ($NeedSemiColon == 0) and
		     ($NeedParam == 0)
		     ) {
		    if ($InActionBlock) {
			$InActionBlock = 0;
			$Dirty--;
		    }
		    elsif ($InSourceBlock) {
			unless ($GetTempDirOnly) {
			    Start_Report();
			    Process_Local();
			    End_Report();
			}
			Reset_Config_Vars();
		    }
		    else {
			die 'ERROR: Error Parsing ' . $ConfigFile . ', line ' . $LineNum . "\n";
		    }
		}
		else {
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', } not expected, Line ' . $LineNum . "\n";
		}
	    }

	    # Looks for the actual parameter...
	    if ($NeedParam) {
		$Param = $ThisLine;
		$Param =~ s/\)[^)]*$//;
		$Param = Config_Trim ($Param);
		if ($Param) {
		    # Process Parameter
		    if ($Param =~ s/^\"// ) {
			unless ($Param =~ s/\"$// ) {
			    die 'ERROR: Error Parsing ' . $ConfigFile . "\nNo Closing quote - Line " . $LineNum . "\n";
			}
		    }
		    else {
			$Param = lc ($Param);
			if ($Param eq 'yes') {
			    $Param = 1;
			}
			elsif ($Param eq 'no') {
			    $Param = 0;
			}
			elsif ($Param eq 'interactive') {
			    $Param = 1;
			}
			elsif ($Param eq 'auto') {
			    $Param = 2;
			}
			elsif ($FuncName eq 'action') {
			    # Don't process action's arguments
			}
			else {
			    die 'ERROR: Error Parsing ' . $ConfigFile . "\nInvalid Parameter Value - Line " . $LineNum . "\n";
			}
		    }

		    $NeedParam = 0;
		    $NeedCParen = 1;
		    unless ($ThisLine =~ s/^.*\)/\)/) {
			$ThisLine = '';
		    }
		    $ThisLine = Config_Trim($ThisLine);
		}
	    }

	    # If we have a quote at the beginning of the line at this point, something is wrong.
	    if ($ThisLine =~ /^\"/ ) {
		die 'ERROR: Error Parsing ' . $ConfigFile . ', " not expected, Line ' . $LineNum . "\n";
	    }

	    # Look for closing parenthesis...
	    if ($ThisLine =~ s/^\)//) {
		if ($NeedCParen) {
		    $ThisLine = Config_Trim($ThisLine);
		    $NeedCParen = 0;
		    $NeedSemiColon = 1;
		}
		else {
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', ) not expected, Line ' . $LineNum . "\n";
		}
	    }

	    # Look for semi-colon (signals the end of a function call).
	    if ($ThisLine =~ s/^;//) {
		if ($NeedSemiColon) {
		    $ThisLine = Config_Trim($ThisLine);
		    $NeedSemiColon = 0;
		    $ProcessCurr = 1;
		}
		else {
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', ; not expected, Line ' . $LineNum . "\n";
		}
	    }

	    # Even if we still need a semi-colon, we may be starting a block instead.
	    if ($NeedSemiColon) {
		if ($InSourceBlock) {
		    if ($FuncName eq 'action') {
			$NeedSemiColon = 0;
			$NeedOCurly = 1;
			$LastDirty = $LineNum;
			$ActionName = $Param;
		    }
		}
		else {
		    if ($FuncName eq 'ftp') {
			$NeedSemiColon = 0;
			$NeedOCurly = 1;
			$LastDirty = $LineNum;
         unless ( $Param =~ m=^ftp://=) {
           if ( ($FTPHost, $FTPDir) = ($Param =~ m=^([^:]+):(.+)$= ) ) {
             $Param = 'ftp://' . $FTPHost . $FTPDir;
           }
         }
			$FTP = 1;
			$SourceLocation = $Param;
			}
		    elsif ($FuncName eq 'directory') {
			$NeedSemiColon = 0;
			$NeedOCurly = 1;
			$LastDirty = $LineNum;
			$FTP = 0;
			$SourceLocation = $Param;
		    }
		}
	    }

	    # If we are ready to process the current fuction/parameter pair, let's do it.
	    if ($ProcessCurr) {
		if ($InSourceBlock) {
		    if ($InActionBlock) {
			if (keys %{$Data->{$ActionName}}) {
			    if (@{$Data->{$ActionName}->{$FuncName}}) {
				$Pos = $#{$Data->{$ActionName}->{$FuncName}} + 1;
			    }
			    else {
				$Pos = 0;
			    }
			}
			else {
			    $Pos = 0;
			}
			$Data->{$ActionName}->{$FuncName}->[$Pos] = $Param;
		    }
		    else {
			if (defined($Data->{$FuncName}) and
			    @{$Data->{$FuncName}})
			{
			    $Pos = $#{$Data->{$FuncName}} + 1;
			}
			else {
			    $Pos = 0;
			}
			$Data->{$FuncName}->[$Pos] = $Param;
		    }
		}
		else {
		    if ($FuncName eq 'temp_dir') {
			$TempDir = $Param;
			$TempDir = Check_Dir($TempDir);
		    }
		    elsif ($FuncName eq 'rpm_location') {
			$RPMLocation = $Param;
		    }
		    elsif ($FuncName eq 'ftp_passive') {
			$FTPPassiveMode = $Param;
		    }
		    elsif ($FuncName eq 'ftp_timeout') {
			$FTPTimeOut = $Param;
		    }
		    elsif ($FuncName eq 'ftp_firewall') {
			$FTPFirewall = $Param;
		    }
		    elsif ($FuncName eq 'ftp_port') {
			$FTPPort = $Param;
		    }
		    elsif ($FuncName eq 'debug') {
			$Debug = $Param;
		    }
		    elsif ($FuncName eq 'hostname') {
			$HostName = $Param;
		    }
          elsif ($FuncName eq 'umask') {
         umask($Param);
          }
		    elsif ($FuncName eq 'report_queues_to') {
			$AutoQueueReport = $Param;
		    }
		    else {
			die 'ERROR: ' . $FuncName . ' not expected in Main File on line ' . $LineNum . "\n";
		    }
		}
		$ProcessCurr = 0;
		$NeedFuncName = 1;
		$Dirty--;
	    }
	    
	    $ThisLine = Config_Trim ($ThisLine);

	    # Check for stray characters that may be in the file.
	    if ( ($NeedOParen) or
		 ($NeedCParen) or
		 ($NeedSemiColon) or
		 ($NeedOCurly) ) {
		unless ( $ThisLine =~ m/^[\(\)\{\}\;]/ ) {
		    $Msg = "\n";
		    if ($NeedOParen) {
			$Msg = '\( Expected';
		    }
		    elsif ($NeedCParen) {
			$Msg = '\) Expected';
		    }
		    elsif ($NeedSemiColon) {
			$Msg = '\; Expected';
		    }
		    elsif ($NeedOCurly) {
			$Msg = '\{ Expected';
		    }
		    die 'ERROR: Error Parsing ' . $ConfigFile . ', line ' . $LineNum . "\n" . $Msg;
		}
	    }
	    
	    
	}
	

    }

    close (CONFFILE);
    unlink ($TempConfig);

    # If $Dirty > 0, the file wasn't correct.
    if ($Dirty) {
	if ($NeedParam) {
	    $Msg = 'Parameter Expected for ' . $FuncName;
	}
	elsif ($NeedOParen) {
	    $Msg = '\( Expected';
	}
	elsif ($NeedCParen) {
	    $Msg = '\) Expected';
	}
	elsif ($NeedSemiColon) {
	    $Msg = '\; Expected';
	}
	elsif ($NeedOCurly) {
	    $Msg = '\{ Expected';
	}
	elsif ($NeedCCurly) {
	    $Msg = '\} Expected';
	}
	else {
	    $Msg = 'Parsing Error';
	}
	die 'ERROR: Error parsing ' . $ConfigFile . "\n" . $Msg . ' - Possibly Started on Line ' . $LastDirty . ".\n";
    }

}

##############################################################################
# Setup_Auto_Mode ()
##############################################################################

sub Setup_Auto_Mode {
    if ($AutoFTP) {
	# Set FTP Mode
	$FTP=1;
   if ( ($FTPHost, $FTPDir) = ($AutoFTP =~ m=^([^:]+):(.+)$= ) ) {
     $Param = 'ftp://' . $FTPHost . $FTPDir;
   }
	$SourceLocation = $AutoFTP;
	$Data->{'updated'}->{'delete_after_install'}->[0] = 1;   #Ask to delete source file
	$Data->{'new'}->{'delete_after_install'}->[0] = 1;   #Ask to delete source file
    }
    else {
	# Set Dir Mode
	$FTP=0;
	unless ($AutoDir =~ m=^/=) {
	    # Turn into absolute directory
	    @TArray = `pwd`;
	    chomp (@TArray);
	    $Temp = pop @TArray;
	    $AutoDir = $Temp . '/' . $AutoDir;
	}
	$SourceLocation = $AutoDir;
    }
    $Data->{'display_report'}->[0] = 1;   #Display Report
    $Data->{'updated'}->{'allow_delete'}->[0] = 1;   #Ask to delete source file
    $Data->{'updated'}->{'install'}->[0] = 1;   #Interactive mode
    $Data->{'new'}->{'allow_delete'}->[0] = 1;   #Ask to delete source file
    $Data->{'new'}->{'install'}->[0] = 1;   #Interactive mode
}

##############################################################################
# Check_Queues
##############################################################################

sub Check_Queues {
    # Format:
    # <Source>;<Delete(0/1)>;<OrigSite>;<Flags>
    my ($Source,$Delete,$Orig);
    my ($ThisLine,$InterTotal,%TempArray);

    # First, start a report section for the install queues.
    # Of course, if $ForcePrint is on, this won't matter...
    $Data->{'report_to'}->[0] = $AutoQueueReport;
    # Only report if something happened
    $Data->{'report_all'}->[0] = 0;
    Start_Report;

    Inform ("\nProcessing Install Queues:\n");

    Do_Auto_Queue();

    # Tell about the Interactive Queue
    $InterTotal = 0;
    open (QUEUEFILE,$TempDir . 'interactive.queue');
    while (defined($ThisLine = <QUEUEFILE>)) {
	$ThisLine =~ s/^([^;]+);.*$/$1/;
        unless ($TempArray{$ThisLine}) {
	    $TempArray{$ThisLine}++;
	    $InterTotal++;
	}
    }
    close (QUEUEFILE);
    if ($InterTotal) {
	Inform ("\nInteractive-Install Queue:\n");
	Report ("\n   " . $InterTotal . " RPM(s) waiting to be installed/updated Interactively\n");
	Report ("      To install/upgrade, run 'autorpm --apply' as root...\n");
    }
    End_Report;
}

sub Get_Remote_File($$) {
    my ($Remote, $Local) = @_;
    my ($FTPSiteName,$FTPFile);
    if ($Debug) {
	Report ('DEBUG: Get_Remote_File (' . $Remote . ', ' . $Local . ")\n");
    }
    unless ((-s $Local) or
	    (not $Remote) ) {
	# Only do anything if the original ($Remote) string
	# is non-null (i.e. came from an FTP site).
	$FTP = 1;
   $DoingCache = 1;
	$SourceLocation = $Remote;
	# Strip the filename off the end of the SourceLocation.
	$SourceLocation =~ s=/[^/]+$=/=;
	if ($Debug) {
	    Report ('DEBUG: Inside Get_Remote_File, SourceLocation is now: ' . $SourceLocation . "\n");
	}
	VFS_Open();
   unless ( ($FTPUser, $FTPPasswd, $FTPSiteName, $FTPFile) = ($Remote =~ m=^ftp://([^:]+):([^@]+)@([^/]+)(/.+)$=) ) {
      ($FTPSiteName, $FTPFile) = ($Remote =~ m=^ftp://([^/]+)(/.+)$=);
      $FTPUser = 'anonymous';
      $FTPPasswd = 'AutoRPM@' . $HostName;
   }
	$Local =~ s=^(.*/)[^/]+$=$1= ;
	if ($Apply) {
	    print 'Transfering ' . $Remote . "\n   to " . $Local . '... ';
	}
	else {
	    #Report ('      Caching ' . $Remote . '... ');
	    Report ('Caching... ');
	}
	if (VFS_Store($FTPFile,$Local)) {
	    Report ("Done... ");
	}
	else {
	    Report ("Error.\n");
	    print STDERR "ERROR: Couldn't transfer " . $Remote . ' to ' . $Local . "\n";
	}
    }
   $DoingCache = 0;
}

sub Do_Auto_Queue {
    my ($ThisLine,$ThisPackage,$TFile,$TFile2,$Temp,$NumInstalledThisLoop,$Success,$Error);
    my (@DepsList,$ThisConflict,$ThisOther,@ConflictsFileList,@ConflictsPackageList,@RenameList,@NoRemoveList);
    my ($Source,$Delete,$Orig,$Flags,$Local,$AllowDelete,$CopyTo,$RPMOpts,$WierdError);
    my $Date = `date`;
    chomp ($Date);
    Inform ("\nAuto-Install Queue:\n");
    if ( -s $TempDir . 'auto.queue.backup' ) {
	Report ("AutoInstall didn't complete successfully last time, appending backup queue.\n");
	`cat $TempDir/auto.queue.backup >> $TempDir/auto.queue`;
    }
    if ($ProcessDelayedQueue) {
	if ( -s $TempDir . 'delayed.queue' ) {
	    Report ("Appending RPMs marked (inside Interactive Mode) to be installed later.\n");
	    `cat $TempDir/delayed.queue >> $TempDir/auto.queue`;
	    unlink ($TempDir . 'delayed.queue');
	}
    }
    if ( -s $TempDir . 'auto.queue' ) {
	open (QUEUEFILE,$TempDir . 'auto.queue');
	while (defined($ThisLine = <QUEUEFILE>)) {
	    chomp ($ThisLine);
	    ($Source,$Delete,$Orig,$Flags,$Local,$AllowDelete,$CopyTo) = split (/;/,$ThisLine);
	    #Trim any special first-characters if they are there...
	    $Source =~ s/^\*//;
	    $Source =~ s/^\!//;
	    $bn = BaseName ($Source);
	    $AutoData->{$bn}->{'Active'} = 1;
	    $AutoData->{$bn}->{'CopyTo'} = $CopyTo;
	    $AutoData->{$bn}->{'File'} = $Source;
	    $AutoData->{$bn}->{'Allow_Delete'} = $AllowDelete;
	    if ($AllowDelete) {
		$AutoData->{$bn}->{'Delete'} = $Delete;
	    }
	    else {
		$AutoData->{$bn}->{'Delete'} = 0;
	    }
	    $AutoData->{$bn}->{'Source'} = $Orig;
	    $AutoData->{$bn}->{'Local'} = $Local;
	    $AutoData->{$bn}->{'ErrorString'} = $Flags;
	}
	close (QUEUEFILE);
	`mv $TempDir/auto.queue $TempDir/auto.queue.backup`;
    }
    $NumInstalledThisLoop = 1;
    while ($NumInstalledThisLoop) {
	$NumInstalledThisLoop = 0;
	foreach $ThisPackage (keys %{$AutoData}) {
	    if ($AutoData->{$ThisPackage}->{'Active'}) {
		$TFile = $TempDir . 'rpm.tmp';
		$TFile2 = $TempDir . 'rpmout.tmp';
		$Temp = $AutoData->{$ThisPackage}->{'File'};
		Get_Remote_File ($AutoData->{$ThisPackage}->{'Source'},$AutoData->{$ThisPackage}->{'File'});
		$RPMOpts = '';
		if ($AutoData->{$ThisPackage}->{'ErrorString'} =~ /^1\*/) {
		    $RPMOpts = ' --nodeps ';
		    Report ("   Using --nodeps...\n");
		}
		elsif ($AutoData->{$ThisPackage}->{'ErrorString'} =~ /^2\*/) {
		    $RPMOpts = ' --force ';
		    Report ("   Using --force...\n");
		}
		$Success = '.';
		Report ('   ' . $ThisPackage . '... ');
		@NoRemoveList = ();
		@RenameList = ();
		`$RPMLocation -U $RPMOpts $Temp > $TFile2 2> $TFile`;
		if ( -s $TFile) {
		    # If Success is non-null, the RPM was installed successfully.
		    $Success = '';
		    # There were errors...
		    # If error is set to 1, that means that an error occured and the RPM should be placed in
		    # the interactive queue...
		    $Error = 0;
		    @DepsList = ();
		    @ConflictsFileList = ();
		    @ConflictsPackageList = ();
		    open (RPMRUN, $TFile);
		    while (defined($ThisLine = <RPMRUN>)) {
			chomp ($ThisLine);
			if ($Debug) {
			    Report ('DEBUG: STDERR: ' . $ThisLine . "\n");
			}
			if ($ThisLine =~ /^failed dependencies:/ ) {
			    $Error = 1;
			}
			elsif ($ThisLine =~ /^execution of script failed$/ ) {
			    $Success = ', but script failed.';
			    $Error = 0;
			}
			elsif ($ThisLine =~ /^package .+ \(which is newer\) is already installed$/ ) {
			    Report ("is older than the currently installed one.\n");
			    $AutoData->{$ThisPackage}->{'Active'} = 0;
			    $Error = 0;
			    #Delete File if necessary
			    if ($AutoData->{$ThisPackage}->{'Delete'} ) {
				if (unlink ($AutoData->{$ThisPackage}->{'File'})) {
				    Report ('      Deleted ' . $AutoData->{$ThisPackage}->{'File'} . "\n");
				}
				else {
				    Report ("      Couldn't Delete " . $AutoData->{$ThisPackage}->{'File'} . "\n");
				}
			    }
			}
			elsif ($ThisLine =~ /^package .+ is already installed$/ ) {
			    Report ('is already installed.' . "\n");
			    $AutoData->{$ThisPackage}->{'Active'} = 0;
			    $Error = 0;
			    #Delete File if necessary
			    if ($AutoData->{$ThisPackage}->{'Delete'} ) {
				if (unlink ($AutoData->{$ThisPackage}->{'File'})) {
				    Report ('      Deleted ' . $AutoData->{$ThisPackage}->{'File'} . "\n");
				}
				else {
				    Report ("      Couldn't Delete " . $AutoData->{$ThisPackage}->{'File'} . "\n");
				}
			    }
			}
			elsif ( ($ThisLine =~ /^failed to open/ ) or
				($ThisLine =~ /^error: cannot open \// ) ) {
			    die "\nERROR: RPM can not open RPM database...\n";
			}
			elsif ( $ThisLine =~ s/^error: cannot open file (.+)$/$1/ ) {
			    $Error = 1;
			    print STDERR "\nERROR: RPM can not open " . $ThisLine . "\n";
			}
			elsif ( $ThisLine =~ s/^warning: (.+ saved as .+)$/      $1/) {
			    $Success = ', but file(s) renamed:';
			    push @RenameList, $ThisLine . "\n";
			    $Error = 0;
			}
			elsif ( $ThisLine =~ s/^r[^ ]+ of (.+) failed: No such file or directory$/      $1/) {
			    $Success = ", but file(s) couldn't be removed:";
			    push @NoRemoveList, $ThisLine . "\n";
			    $Error = 0;
			}
			elsif ( $ThisLine =~ s/^cannot remove (.+) - directory not empty$/      $1/) {
			    $Success = ", but file(s) couldn't be removed:";
			    push @NoRemoveList, $ThisLine . "\n";
			    $Error = 0;
			}
			elsif ($ThisLine =~ /^$/ ) {
			    # Blank Line
			}
			elsif ( $ThisLine =~ s/^\s+(.+) is needed by .+$/$1/ ) {
			    push @DepsList,$ThisLine;
			}
			elsif ( ($ThisConflict, $ThisOther) = ($ThisLine =~ /^(.+) conflicts with file from (.+)$/)) {
			    $Error = 1;
			    push @ConflictsFileList,$ThisConflict;
			    push @ConflictsPackageList,$ThisOther;
			}
			elsif ( $ThisLine =~ /^error: .+ cannot be installed$/ ) {
			    # Couldn't be installed, but we don't need to do anything here...
			}
			else {
			    $Error = 1;
			    $WierdError = $ThisLine;
			    print STDERR "\nERROR: Unknown Error Message From RPM:\n" . $ThisLine . "\n";
			}
		    }
		    close (RPMRUN);
		    if ($Error) {
			Report ("failed, but will try again...\n");
			if (@DepsList) {
			    $AutoData->{$ThisPackage}->{'ErrorString'} = '1';
			    foreach $Temp (@DepsList) {
				$AutoData->{$ThisPackage}->{'ErrorString'} .= ':' . $Temp;
			    }
			}
			elsif (@ConflictsFileList) {
			    $AutoData->{$ThisPackage}->{'ErrorString'} = '2';
			    while ($TFile = pop (@ConflictsFileList) ) {
				$Temp = pop (@ConflictsPackageList);
				$AutoData->{$ThisPackage}->{'ErrorString'} .= ':' . $TFile . '(' . $Temp . ')';
			    }
			}
			else {
			    $AutoData->{$ThisPackage}->{'ErrorString'} = '3:' . $WierdError;
			    print STDERR 'Unknown Error for ' . $ThisPackage . "\n";
			}
		    }
		}
		unlink ($TFile);
		if ($Success) {
		    # Installation successful
		    $NumInstalledThisLoop++;
		    $AutoData->{$ThisPackage}->{'Active'} = 0;
		    $AutoData->{$ThisPackage}->{'ErrorString'} = '';
		    if ($AutoData->{$ThisPackage}->{'Local'}) {
			$Temp = 'Upgraded';
		    }
		    else {
			$Temp = 'Installed';
		    }
		    Report ($Temp . ' Successfully' . $Success . "\n");
		    print @RenameList;
		    print @NoRemoveList;
		    if ( -s $TFile2) {
			Report ('   OUTPUT FROM RPM:' . "\n");
			open (OUTPUTFILE,$TFile2);
			while (defined($Temp = <OUTPUTFILE>)) {
			    Report '      ' . $Temp
			    }
			close (OUTPUTFILE);
		    }
		    # Copy file if necessary...
		    if ($AutoData->{$ThisPackage}->{'CopyTo'} ) {
			foreach $TFile (split(/:/,$AutoData->{$ThisPackage}->{'CopyTo'})) {
			    $Temp = $AutoData->{$ThisPackage}->{'File'} . ' ' . $AutoData->{$ThisPackage}->{'CopyTo'};
			    unless (`cp $Temp`) {
	            `chmod a+r $Temp`;
				Report ( '      Copied to ' . $AutoData->{$ThisPackage}->{'CopyTo'} . "\n");
			    }
			    else {
				Report ( "      Couldn't copy to " . $AutoData->{$ThisPackage}->{'CopyTo'} . "\n");
			    }
			}
		    }
		    #Delete File if necessary
		    if ($AutoData->{$ThisPackage}->{'Delete'} ) {
			if (unlink ($AutoData->{$ThisPackage}->{'File'})) {
			    Report ('      Deleted ' . $AutoData->{$ThisPackage}->{'File'} . "\n");
			}
			else {
			    Report ("      Couldn't Delete " . $AutoData->{$ThisPackage}->{'File'} . "\n");
			}
		    }
                    # Log the installation...
 		    open (LOGFILE,'>>' . $TempDir . 'install.log');
  		    if ($AutoData->{$ThisPackage}->{'Local'}) {
  			$Temp = BaseName ($AutoData->{$ThisPackage}->{'Local'});
  			print LOGFILE $Date . ' - ' . $Temp . ' -> ';
  			$Temp = BaseName ($AutoData->{$ThisPackage}->{'File'});
  			print LOGFILE $Temp . "\n";
		    }
		    else {
			$Temp = BaseName ($AutoData->{$ThisPackage}->{'File'});
			print LOGFILE $Date . ' - Installed ' . $Temp . "\n";
		    }
		    close (LOGFILE);
		}
		unlink ($TFile2);
	    }
	}
    }
    open (QUEUEFILE,'>>' . $TempDir . 'interactive.queue');
    foreach $ThisPackage (keys %{$AutoData}) {
	if ($AutoData->{$ThisPackage}->{'Active'}) {
	    print QUEUEFILE $AutoData->{$ThisPackage}->{'File'} . ';' . $AutoData->{$ThisPackage}->{'Delete'} . ';' . $AutoData->{$ThisPackage}->{'Source'} .
		';' . $AutoData->{$ThisPackage}->{'ErrorString'} . ';' . $AutoData->{$ThisPackage}->{'Local'} . ';' . $AutoData->{$ThisPackage}->{'Allow_Delete'} .
		    ';' . $AutoData->{$ThisPackage}->{'CopyTo'} . "\n";
	    if ( ($AutoData->{$ThisPackage}->{'ErrorString'}) =~ /^1:/ ) {
		# Missing Dependencies...
		Report ( '   ' . $ThisPackage . ' is missing dependencies:' . "\n");
		foreach $Temp ( split (/:/, $AutoData->{$ThisPackage}->{'ErrorString'})) {
		    unless ($Temp eq '1') {
			Report ( '      ' . $Temp . "\n");
		    }
		}
	    }
	    elsif ( ($AutoData->{$ThisPackage}->{'ErrorString'}) =~ /^2:/ ) {
		# Conflicts...
		Report ( '   ' . $ThisPackage . ' has conflicting files:' . "\n");
		foreach $Temp ( split (/:/, $AutoData->{$ThisPackage}->{'ErrorString'})) {
		    unless ($Temp eq '2') {
			$Temp =~ s/^(.+)\((.+)\)$/      $1 is owned by $2/;
			Report ($Temp . "\n");
		    }
		}
	    }
	}
    }
    close (QUEUEFILE);
    unlink ($TempDir . 'auto.queue.backup');
}

##############################################################################
# Apply Mode (autorpm --apply)
##############################################################################

sub Apply_Mode {
    my ($StillMore,$Source,$Delete,$Orig,$Flags,$ThisLine,$bn,$Temp,$ThisOne,$CurrItem,$MessageText,@Output,$Local,$AllowDelete);
    my ($SaveChanges,$DoneInstallOnce,$DoneMenuLoop,%LocalPackages,$SubMenuDone,$ShowBox,$DoRPMString,@TempList,$CopyTo);

    # Backup queue in case program abnormally exits...
    if ( -s $TempDir . 'interactive.queue.backup' ) {
	print "\nInteractive Install didn't complete successfully last time\n";
	WaitEnter ('Appending backup queue.  Press ENTER to continue.');
	`cat $TempDir/interactive.queue.backup >> $TempDir/interactive.queue`;
    }
    `cp $TempDir/interactive.queue $TempDir/interactive.queue.backup`;

    # Get installed package list
    print "\nGetting list of installed packages...";
    @TempList = `$RPMLocation -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}\n'`;
    die ('ERROR: Error executing ' . $RPMLocation . "\n") unless ($? == 0);
    chomp(@TempList);
    foreach $Temp (@TempList) {
	$bn = StripVersion ($Temp);
	$LocalPackages{$bn} = $Temp;
    }
    print " Done.\n\n";

    # Loop while there is more to do...
    $SaveChanges = 1;
    $StillMore = 1;
    $DoneInstallOnce = 0;
    $TryAuto = 1;
    while ($StillMore) {
	open (QUEUEFILE,$TempDir . 'interactive.queue');
	while (defined($ThisLine = <QUEUEFILE>)) {
	    chomp ($ThisLine);
	    ($Source,$Delete,$Orig,$Flags,$Local,$AllowDelete,$CopyTo) = split (/;/,$ThisLine);
	    $bn = BaseName ($Source);
	    print 'Processing queue file: ' . $bn . "\n";
	    # These strip off any special characters off the front
	    if ( $Source =~ s/^\*// ) {
		$InterData->{$bn}->{'Install'} = 1;
	    }
	    else {
		$InterData->{$bn}->{'Install'} = 0;
	    }
	    if ( $Source =~ s/^\!// ) {
		$InterData->{$bn}->{'FirstTime'} = 1;
	    }
	    else {
		$InterData->{$bn}->{'FirstTime'} = 0;
	    }
	    # If a local file, make sure it exists first...
	    if ( (-s $Source) or ($Orig) ) {
		$InterData->{$bn}->{'Active'} = 1;
		$InterData->{$bn}->{'CopyTo'} = $CopyTo;
		$InterData->{$bn}->{'Queue_Later'} = 0;
		$InterData->{$bn}->{'File'} = $Source;
		$InterData->{$bn}->{'Allow_Delete'} = $AllowDelete;
		$InterData->{$bn}->{'Delete'} = $Delete;
		$InterData->{$bn}->{'Source'} = $Orig;
		$InterData->{$bn}->{'Local'} = $Local;
		$Temp = $Flags;
		$Temp =~ s/^([0123456789]\**):.*$/$1/;
		$InterData->{$bn}->{'Error_Code'} = $Temp;
		$Temp = $Flags;
		$Temp =~ s/^[0123456789]\**:(.*)$/$1/;
		$InterData->{$bn}->{'Error_Data'} = $Temp;
		unless ($InterData->{$bn}->{'Error_Data'}) {
		    $InterData->{$bn}->{'Error_Data'} = '';
		}
		$Temp = StripVersion ($bn);
		@{$InterData->{$bn}->{'Local_Installed'}} = ();
		foreach $ThisOne (keys %LocalPackages) {
		    if ($Temp eq $ThisOne) {
			push @{$InterData->{$bn}->{'Local_Installed'}}, $LocalPackages{$ThisOne};
		    }
		}
	    }
	    else {
		$InterData->{$bn}->{'Active'} = 1;
	    }
	}
	close (QUEUEFILE);
	unlink ($TempDir . 'interactive.queue');

	$DoneMenuLoop = 0;
	unless ($DoneMenuLoop) {
	    # This is the loop to do the UI menu stuff
	    # on exit, these need to be set:
	    # $StillMore > 0 if you want to re-enter the menu...
	    # $TryAuto > 0 if you want to install progs markes as such...
	    # $SaveChanges > 0 if you want to save changes.  NOTE: This should
	    #                  only be zero if $DoneInstallOnce = 0 too...
	    # This loop will be re-entered *only* if $StillMore > 0 and if there
	    # are still some RPMs that need to be installed or if there were errors.

	    $MenuText = '';
	    #print "\nGenerating Menu...\n";
	    foreach $ThisOne (sort keys %{$InterData}) {
		if ($InterData->{$ThisOne}->{'Active'}) {
		    if (@{$InterData->{$ThisOne}->{'Local_Installed'}}) {
			# Upgrading...
			if ($InterData->{$ThisOne}->{'Install'}) {
			    if ($InterData->{$ThisOne}->{'Delete'}) {
				$Temp = 'Upgrade Now and Delete';
			    }
			    else {
				$Temp = 'Upgrade Now';
			    }
			}
			elsif ($InterData->{$ThisOne}->{'Queue_Later'}) {
			    if ($InterData->{$ThisOne}->{'Delete'}) {
				$Temp = 'Upgrade Later and Delete';
			    }
			    else {
				$Temp = 'Upgrade Later';
			    }
			}
			else {
			    $Temp = 'Do not Upgrade';
			}
		    }
		    else {
			# Installing...
			if ($InterData->{$ThisOne}->{'Install'}) {
			    if ($InterData->{$ThisOne}->{'Delete'}) {
				$Temp = 'Install Now and Delete';
			    }
			    else {
				$Temp = 'Install Now';
			    }
			}
			elsif ($InterData->{$ThisOne}->{'Queue_Later'}) {
			    if ($InterData->{$ThisOne}->{'Delete'}) {
				$Temp = 'Install Later and Delete';
			    }
			    else {
				$Temp = 'Install Later';
			    }
			}
			else {
			    $Temp = 'Do not Install';
			}
		    }
		    if ($InterData->{$ThisOne}->{'FirstTime'}) {
			$Temp = 'NEW - ' . $Temp;
		    }
		    if ($InterData->{$ThisOne}->{'Error_Code'} =~ /^1\*$/) {
			$Temp .= ', use --nodeps';
		    }
		    elsif ($InterData->{$ThisOne}->{'Error_Code'} =~ /^2\*$/) {
			$Temp .= ', use --force';
		    }
		    elsif ($InterData->{$CurrItem}->{'Error_Code'}) {
			$Temp .= ', Previous Errors';
		    }
		    $MenuText .= ' ' . $ThisOne . ' "(' . $Temp . ')" ';
		}
	    }

	    $MenuText .= ' Apply "(Perform Actions Now)" Exit "(Save Changes)" ';
	    unless ($DoneInstallOnce) {
		$MenuText .= ' Quit "(Discard Changes)" ';
	    }

	    #system('/usr/bin/dialog --menu "AutoRPM Interactive Mode" 22 70 15 ' . $MenuText . ' 2>' . $TempDir . 'menu.tmp');
	    system($MenuProg . ' --menu "AutoRPM Interactive Mode" 22 70 13 ' . $MenuText . ' 2>' . $TempDir . 'menu.tmp');
	    if ($?) {
		# Exit...
		$StillMore = 0;
		$TryAuto = 0;
		unless ($DoneInstallOnce) {
		    $SaveChanges = 0;
		}
		unlink ($TempDir . 'menu.tmp');
	    }
	    else {
		# Process results of menu...
		open (MENUFILE, $TempDir . 'menu.tmp');
		$CurrItem = <MENUFILE>;
		chomp($CurrItem);
		close (MENUFILE);
		unlink ($TempDir . 'menu.tmp');
		if ($CurrItem eq 'Apply') {
		    # Don't do anything here...
		    # Let things pan out below...
		    $TryAuto = 1;
		}
		elsif ($CurrItem eq 'Quit') {
		    # Quit and don't save changes...
		    $StillMore = 0;
		    $TryAuto = 0;
		    unless ($DoneInstallOnce) {
			$SaveChanges = 0;
		    }
		}
		elsif ($CurrItem eq 'Exit') {
		    # Quit and save changes...
		    $StillMore = 0;
		    $TryAuto = 0;
		    $SaveChanges = 1;
		}
		else {
		    # Go to a submenu...
		    $TryAuto = 0;
		    $SubMenuDone = 0;
		    until ($SubMenuDone) {
			$MenuText = ' Return "(To Main Menu)" ';
			unless ($InterData->{$CurrItem}->{'Queue_Later'}) {
			    if (@{$InterData->{$CurrItem}->{'Local_Installed'}}) {
				# Upgrading
				if ($InterData->{$CurrItem}->{'Install'}) {
				    $MenuText .= ' Toggle-Install "(Currently Yes, Upgrade Now)" ';
				}
				else {
				    $MenuText .= ' Toggle-Install "(Currently No, Do Not Upgrade Now)" ';
				}
			    }
			    else {
				# Installing
				if ($InterData->{$CurrItem}->{'Install'}) {
				    $MenuText .= ' Toggle-Install "(Currently Yes, Install Now)" ';
				}
				else {
				    $MenuText .= ' Toggle-Install "(Currently No, Do Not Install Now)" ';
				}
			    }
			}
			if ( $InterData->{$CurrItem}->{'Allow_Delete'} ) {
			    if ($InterData->{$CurrItem}->{'Delete'}) {
				$MenuText .= ' Toggle-Delete "(Currently Yes, Delete After Install)" ';
			    }
			    else {
				$MenuText .= ' Toggle-Delete "(Currently No, Do Not Delete After Install)" ';
			    }
			}
			if ($InterData->{$CurrItem}->{'Error_Code'}) {
			    # There have been errors w/ this package...
			    $MenuText .= ' View-Errors "(View Previous Installation Errors)" ';
			    if ($InterData->{$CurrItem}->{'Error_Code'} =~ /^1\*$/) {
				$MenuText .= ' Use-NoDeps "(Currently Yes, use --nodeps to install)" ';
			    }
			    elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ /^1$/) {
				$MenuText .= ' Use-NoDeps "(Currently No, don\'t use --nodeps to install)" ';
			    }
			    elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ /^2\*$/) {
				$MenuText .= ' Use-Force "(Currently Yes, use --force to install)" ';
			    }
			    elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ /^2$/) {
				$MenuText .= " Use-Force \"(Currently No, don\'t use --force to install)\" ";
			    }
			}
			else {
			    unless ($InterData->{$CurrItem}->{'Install'}) {
				if (@{$InterData->{$CurrItem}->{'Local_Installed'}}) {
				    # Upgrading
				    if ($InterData->{$CurrItem}->{'Queue_Later'}) {
					$MenuText .= ' Toggle-Later "(Currently Yes, Upgrade Later)" ';
				    }
				    else {
					$MenuText .= ' Toggle-Later "(Currently No, Do Not Upgrade Later)" ';
				    }
				}
				else {
				    # Installing
				    if ($InterData->{$CurrItem}->{'Queue_Later'}) {
					$MenuText .= ' Toggle-Later "(Currently Yes, Install Later)" ';
				    }
				    else {
					$MenuText .= ' Toggle-Later "(Currently No, Do Not Install Later)" ';
				    }
				}
			    }
			}

			$MenuText .= ' Remove "(Remove From Queue)" ';
			if ( $InterData->{$CurrItem}->{'Allow_Delete'} ) {
			    $MenuText .= ' Remove-Delete "(Remove From Queue and Delete File)" ';
			}
			$MenuText .= ' AutoRPM-Info "(Internal Info)" Package-Info "(Info From RPM)" List-Files "(Show RPM File List)" List-Deps "(Show Dependencies)" ';
		        $MenuText .= ' Show-Provides "(What RPM Provides)" Show-Scripts "(Show Install Scripts)" ';
			$MenuText .= ' Check-Sig "(Check PGP Signature)" ';
			system("$MenuProg --menu $CurrItem 22 70 13 " . $MenuText . ' 2>' . $TempDir . 'menu.tmp');
			if ($?) {
			    $DoRPMString = '';
			    $ShowBox = 0;
			    $SubMenuDone = 1;
			}
			else {
			    open (MENUFILE, $TempDir . 'menu.tmp');
			    $ThisLine = <MENUFILE>;
			    chomp($ThisLine);
			    close (MENUFILE);
			    unlink ($TempDir . 'menu.tmp');

			    $ShowBox = 1;
			    $MessageText = '';
			    if ($ThisLine eq 'Return') {
				$DoRPMString = '';
				$ShowBox = 0;
				$SubMenuDone = 1;
			    }
			    elsif ($ThisLine eq 'AutoRPM-Info') {
				$DoRPMString = '';
				$MessageText = 'Internal AutoRPM Info for ' . $CurrItem . "\n\n";
				$MessageText .= "Filename:\n" . $InterData->{$CurrItem}->{'File'} . "\n\n";
				if ($InterData->{$CurrItem}->{'Local'}) {
				    if ($InterData->{$CurrItem}->{'Local'} =~ m=^/=) {
					# Local file was compared against...
					$MessageText .= "Compared Against File:\n" . $InterData->{$CurrItem}->{'Local'} . "\n\n";
				    }
				    else {
					# Local installed package was compared against...
					$MessageText .= 'Compared Against Installed Package: ' . $InterData->{$CurrItem}->{'Local'} . "\n\n";
				    }
				}
				if ($InterData->{$CurrItem}->{'Source'}) {
				    $MessageText .= "\nOriginal Location:\n" . $InterData->{$CurrItem}->{'Source'} . "\n\n";
				}
				if (@{$InterData->{$CurrItem}->{'Local_Installed'}}) {
				    $MessageText .= "\nVersion(s) Already Installed:\n";
				    foreach $Temp (@{$InterData->{$CurrItem}->{'Local_Installed'}}) {
					$MessageText .= '   ' . $Temp . "\n";
				    }
				}
			    }
			    elsif ($ThisLine eq 'View-Errors') {
				$DoRPMString = '';
				if ($InterData->{$CurrItem}->{'Error_Code'} =~ m/^1/ ) {
				    # Missing Dependencies...
				    $MessageText = "Missing Dependencies:\n";
				    foreach $Temp ( split (/:/, $InterData->{$CurrItem}->{'Error_Data'})) {
					$MessageText .= ( '   ' . $Temp . "\n" );
				    }
				}
				elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ m/^2/ ) {
				    # Conflicts...
				    $MessageText = "Conflicting Files:\n";
				    foreach $Temp ( split (/:/, $InterData->{$CurrItem}->{'Error_Data'})) {
					$Temp =~ s/^(.+)\((.+)\)$/   $1 is owned by $2/;
					$MessageText .= ( $Temp . "\n" );
				    }
				}
				elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ m/^3/ ) {
				    # Unknown Error...
				    $MessageText = "Unknown Error:\n";
				    print $InterData->{$CurrItem}->{'Error_Data'} . "\n";
				}
			    }
			    elsif ($ThisLine eq 'Use-NoDeps') {
				$DoRPMString = '';
				$ShowBox = 0;
				if ($InterData->{$CurrItem}->{'Error_Code'} =~ /^1\*$/) {
				    $InterData->{$CurrItem}->{'Error_Code'} = '1';
				}
				elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ /^1$/) {
				    $InterData->{$CurrItem}->{'Error_Code'} = "1*";
				}
			    }
			    elsif ($ThisLine eq 'Use-Force') {
				$DoRPMString = '';
				$ShowBox = 0;
				if ($InterData->{$CurrItem}->{'Error_Code'} =~ /^2\*$/) {
				    $InterData->{$CurrItem}->{'Error_Code'} = '2';
				}
				elsif ($InterData->{$CurrItem}->{'Error_Code'} =~ /^2$/) {
				    $InterData->{$CurrItem}->{'Error_Code'} = "2*";
				}
			    }
			    elsif ($ThisLine eq 'Toggle-Install') {
				$DoRPMString = '';
				$ShowBox = 0;
				$InterData->{$CurrItem}->{'Install'} = not $InterData->{$CurrItem}->{'Install'};
				$SubMenuDone = 1;
			    }
			    elsif ($ThisLine eq 'Toggle-Later') {
				$DoRPMString = '';
				$ShowBox = 0;
				$InterData->{$CurrItem}->{'Queue_Later'} = not $InterData->{$CurrItem}->{'Queue_Later'};
			    }
			    elsif ($ThisLine eq 'Toggle-Delete') {
				$DoRPMString = '';
				$ShowBox = 0;
				$InterData->{$CurrItem}->{'Delete'} = not $InterData->{$CurrItem}->{'Delete'};
			    }
			    elsif ($ThisLine eq 'Remove') {
				if ( $InterData->{$CurrItem}->{'Source'} ) {
				    Add_Auto_Ignore($InterData->{$CurrItem}->{'Source'},$InterData->{$CurrItem}->{'Local'});
				}
				else {
				    Add_Auto_Ignore($InterData->{$CurrItem}->{'File'},$InterData->{$CurrItem}->{'Local'});
				}
				$InterData->{$CurrItem}->{'Active'} = 0;
				$DoRPMString = '';
				$ShowBox = 0;
				$SubMenuDone = 1;
			    }
			    elsif ($ThisLine eq 'Remove-Delete') {
				if ( $InterData->{$CurrItem}->{'Source'} ) {
				    Add_Auto_Ignore($InterData->{$CurrItem}->{'Source'},$InterData->{$CurrItem}->{'Local'});
				}
				else {
				    Add_Auto_Ignore($InterData->{$CurrItem}->{'File'},$InterData->{$CurrItem}->{'Local'});
				}
				$InterData->{$CurrItem}->{'Active'} = 0;
				$DoRPMString = '';
				$ShowBox = 0;
				$SubMenuDone = 1;
				unlink ($InterData->{$CurrItem}->{'File'});
			    }
			    elsif ($ThisLine eq 'Package-Info') {
				$DoRPMString = ' -i ';
			    }
			    elsif ($ThisLine eq 'List-Files') {
				$DoRPMString = ' -l ';
			    }
			    elsif ($ThisLine eq 'List-Deps') {
				$DoRPMString = ' --requires ';
			    }
			    elsif ($ThisLine eq 'Show-Provides') {
				$DoRPMString = ' --provides ';
			    }
			    elsif ($ThisLine eq 'Show-Scripts') {
				$DoRPMString = ' --scripts ';
			    }
			    elsif ($ThisLine eq 'Check-Sig') {
				Get_Remote_File ($InterData->{$CurrItem}->{'Source'},$InterData->{$CurrItem}->{'File'});
				$DoRPMString = '';
				$Temp = $InterData->{$CurrItem}->{'File'};
				@Output = `$RPMLocation --checksig $Temp`;
				if ($?) {
				    die "ERROR: Command Failed:\n" . $RPMLocation . ' --checksig ' . $Temp . "\n";
				}
				else {
				    $ShowBox = 1;
				    $MessageText = join('', @Output);
				}
			    }
			    if ($DoRPMString) {
				Get_Remote_File ($InterData->{$CurrItem}->{'Source'},$InterData->{$CurrItem}->{'File'});
				$Temp = $InterData->{$CurrItem}->{'File'};
				@Output = `$RPMLocation -q $DoRPMString -p $Temp`;
				if ($?) {
				    die "ERROR: Command Failed:\n" . $RPMLocation . ' -q ' . $DoRPMString . ' -p ' . $Temp . "\n";
				}
				else {
				    $ShowBox = 1;
				    $MessageText = join('', @Output);
				}
			    }
			    if ($ShowBox) {
				#system("/usr/bin/dialog --msgbox '" . $MessageText . "' 22 80" );
				open (MSGFILE,'>' . $TempDir . 'tmp.msg');
				print MSGFILE $MessageText;
				close (MSGFILE);
				system("$DialogProg --textbox " . $TempDir . 'tmp.msg 22 80' );
				unlink ($TempDir . 'tmp.msg');
			    }
			}
		    }
		}
	    }
	}

	# Fill Delayed Queue and execute...
	open (QUEUEFILE,'>>' . $TempDir . 'delayed.queue');
	foreach $ThisOne (keys %{$InterData}) {
	    if ( ($InterData->{$ThisOne}->{'Queue_Later'}) and
		 ($InterData->{$ThisOne}->{'Active'}) ) {
		print QUEUEFILE $InterData->{$ThisOne}->{'File'} . ';' . $InterData->{$ThisOne}->{'Delete'} .
		    ';' . $InterData->{$ThisOne}->{'Source'} . ';' . $InterData->{$ThisOne}->{'Error_Code'} .
			':' . $InterData->{$ThisOne}->{'Error_Data'} . ';' . $InterData->{$ThisOne}->{'Local'} . ';' .
			    $InterData->{$ThisOne}->{'Allow_Delete'} . ';' . $InterData->{$ThisOne}->{'CopyTo'} . "\n";
		$InterData->{$ThisOne}->{'Active'} = 0;
		$DoneInstallOnce++;
	    }
	}
	close (QUEUEFILE);

	if ($TryAuto) {
	    # Fill Auto Queue and execute...
	    open (QUEUEFILE,'>>' . $TempDir . 'auto.queue');
	    foreach $ThisOne (keys %{$InterData}) {
		if ( ($InterData->{$ThisOne}->{'Install'}) and
		     ($InterData->{$ThisOne}->{'Active'}) ) {
		    print QUEUEFILE $InterData->{$ThisOne}->{'File'} . ';' . $InterData->{$ThisOne}->{'Delete'} .
			';' . $InterData->{$ThisOne}->{'Source'} . ';' . $InterData->{$ThisOne}->{'Error_Code'} .
			    ':' . $InterData->{$ThisOne}->{'Error_Data'} . ';' . $InterData->{$ThisOne}->{'Local'} . ';' .
				$InterData->{$ThisOne}->{'Allow_Delete'} . ';' . $InterData->{$ThisOne}->{'CopyTo'} . "\n";
		    $InterData->{$ThisOne}->{'Active'} = 0;
		    $DoneInstallOnce++;
		}
	    }
	    close (QUEUEFILE);
	    $ForcePrint = 1;
	    Start_Report();
	    Do_Auto_Queue();
	    End_Report();
	    WaitEnter ("\nPress ENTER to continue...");
	}

	if ($StillMore) {
	    # Check to see if there are any more active items in the queue
	    $StillMore = 0;
	    foreach $ThisOne (keys %{$InterData}) {
		if ($InterData->{$ThisOne}->{'Active'}) {
		    $StillMore++;
		}
	    }
	    if ( -s $TempDir . 'interactive.queue') {
		$StillMore++;
	    }
	}
    }
    if ($SaveChanges) {
	print "\nSaving changes...\n";
	open (QUEUEFILE,'>>' . $TempDir . 'interactive.queue');
	foreach $ThisOne (keys %{$InterData}) {
	    if ($InterData->{$ThisOne}->{'Active'}) {
		if ($InterData->{$ThisOne}->{'Install'}) {
		    $InterData->{$ThisOne}->{'File'} = "*" . $InterData->{$ThisOne}->{'File'};
		}
		print QUEUEFILE $InterData->{$ThisOne}->{'File'} . ';' . $InterData->{$ThisOne}->{'Delete'} .
		    ';' . $InterData->{$ThisOne}->{'Source'} . ';' . $InterData->{$ThisOne}->{'Error_Code'} .
			':' . $InterData->{$ThisOne}->{'Error_Data'} . ';' . $InterData->{$ThisOne}->{'Local'} . ';' .
			    $InterData->{$ThisOne}->{'Allow_Delete'} . ';' . $InterData->{$ThisOne}->{'CopyTo'} . "\n";
	    }
	}
	close (QUEUEFILE);
	unlink ($TempDir . 'interactive.queue.backup');
    }
    else {
	print "\nNot saving changes...\n";
	`mv $TempDir/interactive.queue.backup $TempDir/interactive.queue`;
    }
}

##############################################################################
# Auto-Ignore Functions
##############################################################################

sub Read_Auto_Ignore {
    if (open (IGNOREFILE,$TempDir . 'auto-ignore')) {
	# Get Auto-Ignore list...
	Inform ('Getting Auto-Ignore list... ');
	@AutoIgnore = <IGNOREFILE>;
	close (IGNOREFILE);
	chomp(@AutoIgnore);
	Inform ("Done.\n\n");
    }
}

sub Test_Auto_Ignore($$) {
    my ($RPM1, $RPM2) = @_;
    my $TestFile;
    if ($FTP) {
      if ($FTPUser eq 'anonymous') {
	     $TestFile = 'ftp://' . $FTPSiteName . $RPM1;
      }
      else {
	     $TestFile = 'ftp://' . $FTPUser . ':' . $FTPPasswd . '@' . $FTPSiteName . $RPM1;
      }
    }
    else {
	$TestFile = $RPM1;
    }
    if ($Debug) {
	@DebugTempList = (grep (/^\Q$TestFile\E;\Q$RPM2\E$/,@AutoIgnore));
	if (@DebugTempList) {
	    Report ('DEBUG: Auto-Ignoring ' . $TestFile . ';' . $RPM2 . "\n");
	    return (1);
	}
	return (0);
    }
    return (grep (/^\Q$TestFile\E;\Q$RPM2\E$/,@AutoIgnore));
}

sub Add_Auto_Ignore($$) {
    my ($RPM1, $RPM2) = @_;
    open (IGNOREFILE,'>>' . $TempDir . 'auto-ignore');
    print IGNOREFILE $RPM1 . ';' . $RPM2 . "\n";
    close (IGNOREFILE);
}

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

# Get arguments
$Usage = 0;
$Help = 0;
$AutoFTP = '';
$AutoDir = '';
$Inter = 0;
$GetVersion = 0;
$ForcePrint = 0;
$FlushMode = 0;
$ApplyOnly = 0;
$VersionCheckMode = 0;
GetOptions ( 'c|config=s' => \$ConfigFile,
	     'a|apply'    => \$Apply,
	     'i|interactive'    => \$Inter,
	     'e|debug'    => \$Debug,
	     'p|print'    => \$ForcePrint,
	     'l|flush'    => \$FlushMode,
	     'v|version'    => \$GetVersion,
	     't|vtest'    => \$VersionCheckMode,
        'o|applyonly' => \$ApplyOnly,
	     'f|ftp=s'    => \$AutoFTP,
	     'd|dir=s'    => \$AutoDir,
        'h|help'     => \$Help,
        'u|usage'    => \$Usage
	     ) or Usage();
($Usage or $Help) and Usage();

if ($GetVersion) {
    print 'AutoRPM Version ' . $Version . ', created ' . $VDate . "\n";
}
elsif ($FlushMode) {
    $TempDir = Check_Dir($TempDir);
    $GetTempDirOnly = 1;
    Read_Config ();
    `rm $TempDir/*`;
}
elsif ($VersionCheckMode) {
    $Debug = 1;
    print "This mode will test the version comparison code.\n";
    print "When typing in RPM version strings, enter them like this: 1.02-2.\n";
    print "Basically, don't include the package name or the architecture, but\n";
    print "but do include the version and release with a hyphen in between.\n";
    print "Just type CTRL-C to quit...\n\n";
    while (1) {
	print 'Type version string of package one: ';
	$Version1 = <STDIN>;
	print 'Type version string of package two: ';
	$Version2 = <STDIN>;
	$Result = RPM_Compare_Version ($Version1, $Version2);
	if ($Result < 0) {
	    print "Package two is newer.\n\n";
	}
	if ($Result > 0) {
	    print "Package one is newer.\n\n";
	}
	if ($Result == 0) {
	    print "Package one is the same as package two.\n\n";
	}
    }
}
else {
    if ($Apply or $Inter) {
	$Apply = 1;
    }

    # This tells Read_Config to only read the tempdir, debug, hostname, etc...
    $GetTempDirOnly = 0;

    $HostName = `hostname -f`;
    chomp ($HostName);

    # By default, display the AutoQueueReport to the screen
    $AutoQueueReport = 'PRINT';

    if ( ($AutoFTP) or ($AutoDir) ) {
	$ProcessDelayedQueue = 0;
	$GetTempDirOnly = 1;
	Read_Config ();
	$TempDir = Check_Dir($TempDir);
	if ( -s $TempDir . 'interactive.queue' ) {
	    print "\n\nFirst, we are going to enter interactive mode to clear\n";
	    print "out the current queue...\n\n";
	    WaitEnter ('Hit ENTER to start Interactive Mode');
	    Apply_Mode();
	}
	Setup_Auto_Mode();
	$ForcePrint = 1;
	Start_Report();
	Process_Local();
	End_Report();
	$InterTotal = 0;
	open (QUEUEFILE,$TempDir . 'interactive.queue');
	while (defined($ThisLine = <QUEUEFILE>)) {
	    $InterTotal++;
	}
	close (QUEUEFILE);
	if ($InterTotal) {
	    WaitEnter ('Hit ENTER to start Interactive Mode');
	    Apply_Mode();
	}
    }
    else {
	$TempDir = Check_Dir($TempDir);
	if ($Apply) {
	    # Go to Apply_Mode
	    $ProcessDelayedQueue = 0;
	    $GetTempDirOnly = 1;
	    Read_Config ();
	    Apply_Mode();
	}
   elsif ($ApplyOnly) {
      # Only do auto queue...
	    $ProcessDelayedQueue = 0;
	    $GetTempDirOnly = 1;
	    Read_Config ();
	    $ForcePrint = 1;
	    Start_Report();
	    Do_Auto_Queue();
	    End_Report();
   }
	else {
	    $ProcessDelayedQueue = 1;
	    Read_Config ();
	    Check_Queues;
	    Mail_Reports;
	}
    }
}

exit (0);

