#!/usr/bin/perl
use strict; # Always

### RHUM - RedHat Update Munger v1.0-RH9
### Originally written by Nick Levay <nick@nicklevay.net>
### Updated to work with RH9 by Alex Martin <a.j.martin@qmul.ac.uk>

### This is free software.  You may copy, modify, and distribute this program
### under the same terms as perl.

#########################
### DEFAULT CONFIGURATION

use vars qw( $RHDistro $RHUpdates $OUpdates $ghdl );

### Base of RedHat Distro
$RHDistro = '/mnt/installs/RPMS/redhat-9/i386';

### Base of RedHat Updates
$RHUpdates = '/mnt/installs/RPMS/updates-9';

### Base of "other" Updates
#$OUpdates = '/mnt/installs/RPMS/beowulf-8.0';
$OUpdates = '/mnt/installs/RPMS/qmul-9';

### Path to genhdlist (part of anaconda-runtime RPM)
$ghdl = '/usr/lib/anaconda-runtime/genhdlist';

#######################################################
### MAIN (You should not have to edit anything in here)

# Various modules we use
use File::Copy;
use RPM2;

# Process command line args
use vars qw( $opt_u $opt_d $opt_v $opt_R $opt_U $opt_O $opt_G );
use Getopt::Std;
getopts("udvR:U:O:G:");

# Print version information
if (defined $opt_v) {
	print_version();
	exit;
}

# Process comandline options, override defaults
if (defined $opt_R) {
	$RHDistro = $opt_R;
}
if (defined $opt_U) {
	$RHUpdates = $opt_U;
}
if (defined $opt_O && $opt_O eq 'NONE') {
	$OUpdates = '';
} elsif (defined $opt_O) {
	$OUpdates = $opt_O;
}
if (defined $opt_G) {
	$ghdl = $opt_G;
}

# Kill any trailing slashes
$RHDistro =~ s/\/$// if defined $RHDistro;
$RHUpdates =~ s/\/$// if defined $RHUpdates;
$OUpdates =~ s/\/$// if defined $OUpdates;

# Make sure we have all necessary paths
unless (defined $ghdl && -x $ghdl) {
	print_help("genhdlist missing or not executable!\n\t(Is anaconda-runtime RPM installed?)");
	exit 1;
}
unless (defined $RHDistro && -d "$RHDistro/RedHat/RPMS"
		&& -d "$RHDistro/RedHat/base") {
	print_help("Redhat Distribution Base Directory invalid!");
	exit 1;
}
unless (defined $RHUpdates && -d "$RHUpdates") {
	print_help("RedHat Updates Base Directory invalid!");
	exit 1;
}

# Make sure we have a command
unless (defined $opt_u || defined $opt_d) {
	print_help();
	exit;
}

# Scan RPMS Dirs
my %DRPMS = scan_dirs("$RHDistro/RedHat/RPMS");
my %URPMS = scan_dirs($RHUpdates, $OUpdates);
my %Updates = compare(\%URPMS, \%DRPMS);

# Print results
if ((defined $opt_d && !defined $opt_u)||(defined $opt_u && !defined $opt_d)) {
	# Exit if we we have no updates to merge
	unless (keys %Updates) {
		print "No updates to merge.\n";
		exit;
	}
	# Process updates
	foreach my $name (keys %Updates) {
		print "$name: \n";
		foreach my $arch (@{$Updates{$name}}) {
			print "  [$arch] ";
			if ($DRPMS{$name}{$arch}[0] && $DRPMS{$name}{$arch}[1]) {
				print $DRPMS{$name}{$arch}[0] . "-" . $DRPMS{$name}{$arch}[1];
			} else {
				print "(none)";
			}
			print " ==> ";
			print $URPMS{$name}{$arch}[0] . "-" . $URPMS{$name}{$arch}[1];
			# Here is where we do updates if -u
			if (defined $opt_u) {
				if (defined $DRPMS{$name}{$arch}[2]) {
					unlink($DRPMS{$name}{$arch}[2])
						|| die "$! trying to delete $DRPMS{$name}{$arch}[2]";
				}
#				copy($URPMS{$name}{$arch}[2], "$RHDistro/RedHat/RPMS/")
#                                || die "$! trying to copy $URPMS{$name}{$arch}[2]";

				`cp -p $URPMS{$name}{$arch}[2] $RHDistro/RedHat/RPMS/`;
				print " (done)";
			}
			print "\n";
		}
	}
	# Rebuild hdlist
	if (defined $opt_u) {
		print "Generating hdlist... ";
		system($ghdl, $RHDistro);
		print "done.\n";
	}
	exit;
} else {
	print_help("Use either -u or -d");
	exit 1;
}

# We should never actually get here
print_help("Unknown Error");
exit 1;

#############
### FUNCTIONS

# Print help screen
sub print_help {
	if ($_[0]) {
		print "ERROR: $_[0]\n\n";
	}
	print "Usage:  $0 [-udv] <options>\n\n";
	print "Commands:\n";
	print "\t-u\tMerge updates\n";
	print "\t-d\tMerge updates [DRY RUN]\n";
	print "\t-v\tPrint version information\n";
	print "\nOptions:\n";
	print "\t-R\t<RedHat Distribution Base Directory>\n";
	if (defined $RHDistro) {
		print "\t\t\t(current: $RHDistro)\n";
	} else {
		print "\t\t\t(current: NONE [REQUIRED])\n";
	}
	print "\t-U\t<RedHat Updates Base Directory>\n";
	if (defined $RHUpdates) {
		print "\t\t\t(current: $RHUpdates)\n";
	} else {
		print "\t\t\t(current: NONE [REQUIRED])\n";
	}
	print "\t-O\t<Other Updates Base Directory>\n";
	if (defined $OUpdates) {
		print "\t\t\t(current: $OUpdates)\n";
	} else {
		print "\t\t\t(current: NONE [OPTIONAL])\n";
	}
	print "\t-G\t<Path to genhdlist>\n";
	if (defined $ghdl) {
		print "\t\t\t(current: $ghdl)\n";
	} else {
		print "\t\t\t(current: NONE [REQUIRED])\n";
	}
	print "\n\tEdit $0 to change defaults.\n";
}

# Print version information
sub print_version {
	print <<EOF
RHUM - RedHat Update Munger - v1.0
Written by Nick Levay <nick\@nicklevay.net>

Copyright (C) 2002 Nick Levay
This is free software.  You may copy, modify, and distribute this program
under the same terms as perl.
EOF
}

# Scan Update dirs for RPMS
sub scan_dirs {
	my %RPMS;
	foreach my $updatedir (@_) {
		next unless defined $updatedir;
		my %DIR = ();
		print "Scanning: $updatedir\n";
		foreach my $rpm (`find $updatedir -name \"*.rpm\" ! -name \"*src.rpm\"`) {
			# Attempt to parse RPM header
			chomp $rpm;
                        print $rpm,"\n"; 
			my $rpmi = RPM2->open_package( $rpm ) || print "$RPM2::err";
			my $name = $rpmi->name;
                        my $ver  = $rpmi->version;
                        my $rev  = $rpmi->release;
                        my $arch  = $rpmi->arch;
			# Complain if anything does not look right
			unless (defined $name && defined $ver
					&& defined $rev && defined $arch) {
				print "HEADER ERROR: $rpm";
				print " (No NAME)" unless defined $name;
				print " (No VERSION)" unless defined $ver;
				print " (No RELEASE)" unless defined $rev;
				print " (No ARCH)" unless defined $arch;
				print "\n";
				next;
			}
			$rpm =~ m/^\/.*\/(.*)\.rpm/;
			my $rpmname = $1;
			if ( $rpmname ne "${name}-${ver}-${rev}.${arch}" ) {
				print "WARNING: $rpm (Filename format incorrect)\n"
			}
			# Add if we don't already have newer one
			if (!defined $DIR{$name}{$arch}) {
				$DIR{$name}{$arch} = [$ver, $rev, $rpm];
			} else {
				my ($ever, $erev) = ($DIR{$name}{$arch}[0], $DIR{$name}{$arch}[1]);
				if (vercompare($ever, $ver) || vercompare($erev, $rev)) {
					$DIR{$name}{$arch} = [$ver, $rev, $rpm];
				}
			}
		}
		foreach my $name (keys %DIR) {
			$RPMS{$name} = $DIR{$name};
		}
	}
	return %RPMS;
}

# Compare version numbers
sub vercompare {
	# Split up version octets
	my @ver1 = split(/\./, $_[0]);
	my @ver2 = split(/\./, $_[1]);
	
	# Compare each octet until we can make a call
	my $max;
	if ( $#ver1 > $#ver2 ) {
		$max = $#ver1;
	} else {
		$max = $#ver2;
	}
	foreach my $i (0 .. $max) {
		if ($ver1[$i] eq $ver2[$i]) {
			next;
		} elsif ($ver1[$i] > $ver2[$i]) {
			return 0;
		} else {
			return 1;
		}
	}
}

# Compare Updates with Distro
sub compare {
	my ($URPMS, $DRPMS) = @_;
	my %Diff;
	foreach my $name (keys %$URPMS) {
		foreach my $arch (keys %{ $URPMS->{$name} }) {
			my $DRPMver = $DRPMS->{$name}->{$arch}->[0];
			my $DRPMrev = $DRPMS->{$name}->{$arch}->[1];
			my $URPMver = $URPMS->{$name}->{$arch}->[0];
			my $URPMrev = $URPMS->{$name}->{$arch}->[1];
			unless (($DRPMver eq $URPMver) && ($DRPMrev eq $URPMrev)) {
				push @{$Diff{$name}}, $arch;
			}
		}
	}
	return %Diff;
}

# <EOF>
