#!/usr/bin/perl -w

#use strict;
use Getopt::Std;

my %opts;
my ($all,$files,$sizeopt,$perms,$provides) = (0,0,0,0,0);
my ($oldrpms,$newrpms);

#opts -a all -f files -s file size -p file perms -l dynamic linkages -r requires/provides -o old tree -n new tree

getopts('afsplro:n:',\%opts);

if(defined($opts{a})){	$all = 1; print "all\n";};
if(defined($opts{f})){ $files = 1; print "files\n"; };
if(defined($opts{s})){ $sizeopt = 1; print "size\n";};
if(defined($opts{p})){ $perms = 1; print "perms\n";};
if(defined($opts{l})){ $links = 1; print "links\n";};
if(defined($opts{r})){ $provides = 1; print "provides\n"; };
if(defined($opts{o})){ if (-d $opts{o}) { $oldrpms = $opts{o}; print "old $oldrpms\n"; }; };
if(defined($opts{n})){ if (-d $opts{n}) { $newrpms = $opts{n}; print "new $newrpms\n"; }; };

if(! ($all or $files or $sizeopt or $links or $perms or $provides)){
	print "$0\nOptions:\n-a\tall\n-f\tfiles\n-s\tfile size\n-p\tfile perms\n-l\tdynamic linkages\n-r\trequires/provides\n-o\t<dir> old tree\n-n\t<dir> new tree\n";
	exit;
	}
  

#print %opts;
#print "\n";

if ($ARGV[0]){
	print "Unknown extra argument: ";
	foreach (@ARGV) {
	print "$_ ";
	}
	print "\n";
	print "$0\nOptions:\n-a\tall\n-f\tfiles\n-s\tfile size\n-p\tfile perms\n-l\tdynamic linkages\n-r\trequires/provides\n-o\t<dir> old tree\n-n\t<dir> new tree\n";
	exit;
	}

#print @oldrpms."\n";
#print @newrpms."\n";

if(!$oldrpms or !$newrpms)
	{

	exit;
	}
	

#Point these to a directory of RH and rebuilt (aka new) RPMS
#$WB_RPMS = "/home/test/b8";
#$RH_RPMS = "/home/test/new";

#my $WB_RPMS = "/centos-3/build8/i386/RedHat/RPMS";
#my $RH_RPMS = "/centos-3/build6/i386/RedHat/RPMS";

# Size difference to trigger error, .1 = plus/minus 10%
my $SizeError=.10;

# Working directories
# Careful where these point, they get a "rm -rf" cleaning!
my $oldTMP="/tmp/old-$$";
my $newTMP="/tmp/new-$$";
my $rpm;

opendir(DIR,$newrpms);
my @allrpms= grep (/rpm$/, readdir(DIR) );
closedir(DIR);

foreach $rpm (sort @allrpms){

my  $rpmold=$rpm;
my $rpmoldf=$rpm;


    if (! (-e "$oldrpms/$rpm") ) {

	#try without .centos.x
	my $fname = $rpm;
	$fname =~ s/centos\.[0-9]//;
	print $fname;

	if (! (-e "$oldrpms/$fname")) {
		print "\nNo equivalent original for $rpm \t trying glob\n";

        	$fname= $rpm;
		$fname =~ s/(.*)-.*-.*/$1-/;
#		print "fname is $fname\n";
		my $found = 0;

#print "trying $oldrpms/$fname*\n";

		my @globmatch = glob("$oldrpms/$fname*");
		while ($rpmold = shift @globmatch) {
	#while($rpmold = glob("$oldrpms/$fname*")){
			my $try = $rpmold;
#		print "\nglob got $try\n ";
			$try =~ s/.*\///;
			$try =~ s/(.*)-.*-.*/$1-/;
		
			if($try eq $fname)
				{
				#this is the one
				$rpmoldf = $rpmold;		
				$rpmoldf =~ s/.*\///;
				$found = 1;

			        while($rpmold = glob("$oldrpms/$fname*")){};
	
				last;
				}
			}
	
		if(!$found)
			{
			next;
			}
		}
    }

    if($rpm =~ /^kernel/)
	{
	next;
	}	

    if($rpm eq $rpmoldf)
	{
	# only doing difficult ones
#	next;
	}
    else
	{
    	print "$rpm >> $rpmoldf\n";
	}

    if($all)
	{
	printf ("%-50s\t", $rpm);
	}

    $fileproblems=0; $libproblems=0;

    if ($all or $files or $sizeopt or $perms or $links){
    	# Initialize
    	%allfiles=(); %oldhash=(); %newhash=();
    	system("rm -rf $oldTMP;mkdir $oldTMP");
    	system("rm -rf $newTMP;mkdir $newTMP");
    	# Extract RPMS
    	system("cd $oldTMP/; rpm2cpio $oldrpms/$rpmoldf | cpio -id --no-absolute-filenames --quiet");
    	system("cd $newTMP/; rpm2cpio $newrpms/$rpm | cpio -id --no-absolute-filenames --quiet");

    	# Parse name/size info
    	@oldnamesize=`cd $oldTMP/; find ./ -printf "%p:_:%s:_:%U:_:%G:_:%04m:_:\n"`;
    	@newnamesize=`cd $newTMP/; find ./ -printf "%p:_:%s:_:%U:_:%G:_:%04m:_:\n"`;
    	for ($i=0;$i<@oldnamesize;$i++){
		($file,$size,$uid,$gid,$perms)=split(/:_:/,$oldnamesize[$i]);

#print "old: $file,$size,$uid,$gid,$perms\n";

		$oldhash{"$file"}=$size;
		$olduid{"$file"}=$uid;
		$oldgid{"$file"}=$gid;
		$oldperms{"$file"}=$perms;
		$allfiles{"$file"}=1;
	    }
	    for ($i=0;$i<@newnamesize;$i++){
		($file,$size,$uid,$gid,$perms)=split(/:_:/,$newnamesize[$i]);
#print "new: $file,$size,$uid,$gid,$perms\n";

		$newhash{"$file"}=$size;
        	$newuid{"$file"}=$uid;
        	$newgid{"$file"}=$gid;
        	$newperms{"$file"}=$perms;
		$allfiles{"$file"}=1;
    	}

	if($all or $files or $sizeopt) 
 		{   
    		# Print and tag file errors if appropraite
    		foreach $file (keys %allfiles){
			if (!(exists $oldhash{"$file"})){
		    		if(!$fileproblems){printf ("%-50s\t", $rpm);};
		    		print "\n\tPackage contains extra file:  $file";
		    		$fileproblems=1;
			}
			elsif (!(exists $newhash{"$file"})){
       	        	if(!$fileproblems){printf ("%-50s\t", $rpm);};
     			print "\n\tPackage is missing file: $file";
    			$fileproblems=1;
			}
			else {
    				if ($oldhash{"$file"}>0){
					$ratio=($newhash{"$file"})/($oldhash{"$file"})}
    				else{
				$ratio=1-$newhash{"$file"};  # Negative ratios indicate filesize vs. zero in RedHat
        			}
	    		if($sizeopt){
			    print "size: $sizeopt\n";
			    if (($ratio-1)*($ratio-1)>($SizeError*$SizeError)) {
				if(!$fileproblems){printf ("%-50s\t", $rpm);};
				printf("\n\tSize Ratio: %3.2f on file:%s",$ratio,$file);
				$fileproblems=1;
	        		};
			}
		}
	if($all or $perms)
		{
            	if (!$fileproblems){
			if($olduid{"$file"} ne $newuid{"$file"}) {
				printf ("%-50s\t", $rpm);
				printf("\n\tUid Diff $olduid{\"$file\"} : $newuid{\"$file\"} on $file");
				$fileproblems=1;
			}
            	if ($oldgid{"$file"} ne $newgid{"$file"}) {
			printf ("%-50s\t", $rpm);
                	printf("\n\tGid Diff $oldgid{\"$file\"} : $newgid{\"$file\"} on $file");
			$fileproblems=1;
                	}
            	if ($oldperms{"$file"} ne $newperms{"$file"}) {
			printf ("%-50s\t", $rpm);
                	printf("\n\tPerms Diff $oldperms{\"$file\"} : $newperms{\"$file\"} on $file");
			$fileproblems=1;
                	}
		   }
		}
	   }
    	}
	if($all or $links)
		{    
    		# Select executable files
    		@oldexec=`cd $oldTMP/; find ./ -perm +111 -type f -print`; 

    		# Check library dependencies
    		foreach $executable (@oldexec) {
			chop $executable;

			#  Move on if no equivalent binary exists in rebuild
			if (! (-e "$newTMP/$executable") ) {next;}

			# Get shared libs
			@oldlibs=`ldd $oldTMP/$executable`;
			@newlibs=`ldd $newTMP/$executable`;

        		# Clean up ldd output
			$oldlibline=join('',(sort @oldlibs)); 
			$newlibline=join('',(sort @newlibs)); 
			$oldlibline=~ s/=>(.*)(\n)/\n/g; $oldlibline=~ s/(\s+)/ /g; 
			$newlibline=~ s/=>(.*)(\n)/\n/g; $newlibline=~ s/(\s+)/ /g;
			@oldlibs=split(/ /,$oldlibline);
			@newlibs=split(/ /,$newlibline);

        		# Print and tag library errors
			foreach $lib (@oldlibs){
		    	$searchpat=$lib;
	    		$searchpat =~ s/\+/\\\+/g;
	    		$searchpat =~ s/\./\\\./g;
	    		unless ($newlibline =~ (/$searchpat/)){
				if(!$libproblems and !$fileproblems){printf ("%-50s\t", $rpm);};
				print "\n\tMissing link in $executable:\t$lib";$libproblems=1;
	    			}
			}
	    
			foreach $lib (@newlibs){
	    		$searchpat=$lib;
	    		$searchpat =~ s/\+/\\\+/g;
	    		$searchpat =~ s/\./\\\./g;
	    		unless ($oldlibline =~ (/$searchpat/)){
				if(!$libproblems and !$fileproblems){printf ("%-50s\t", $rpm);};
				print "\n\tExtra link in $executable:\t$lib";$libproblems=1;
	    			}
			}
		}
    	}
    }	
    if($all or $provides)
	{
    	# sort out provides and requires

   	@oldrequires = `rpm -qp --requires $newrpms/$rpm`;
   	@newrequires = `rpm -qp --requires $oldrpms/$rpmoldf`;

   	while ($oldrequire = shift(@oldrequires))
		{
		if($newrequire = shift(@newrequires))
			{
			chomp($oldrequire);
			chomp($newrequire);

#			print "$oldrequire : $newrequire\n";

			if ($oldrequire gt $newrequire)
				{
				# extra one in newrequire
				if(!$fileproblems and !$libproblems and !$fileproblems){printf ("%-50s\t", $rpm);};
				print "\n\tExtra require $newrequire";$fileproblems=1;
				unshift(@oldrequires,$oldrequire);
				}
			elsif($oldrequire lt $newrequire)
				{
				# one missing in newrequire
				if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
				print "\n\tMissing require $oldrequire";$fileproblems=1;
				unshift(@newrequires,$newrequire);
				}

			}
		else
			{
			# end of new array
			if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
			print "\n\tMissing require $oldrequire";$fileproblems=1;
			}

		}

  	if(shift(@newrequires)){
		# extra in the new array
		if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
		print "\n\tExtra require $newrequire";$fileproblems=1;
		}


   	@oldprovides = `rpm -qp --provides $newrpms/$rpm`;
   	@newprovides = `rpm -qp --provides $oldrpms/$rpmoldf`;

   	while ($oldprovide = shift(@oldprovides))
		{
		if($newprovide = shift(@newprovides))
			{
			chomp($oldprovide);
			chomp($newprovide);

#		print "$oldprovide : $newprovide\n";

			if ($oldprovide gt $newprovide)
				{
				# extra one in newprovide
				if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
				print "\n\tExtra provide $newprovide";$fileproblems=1;
				unshift(@oldprovides,$oldprovide);
				}
			elsif($oldprovide lt $newprovide)
				{
				# one missing in newprovide
				if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
				print "\n\tMissing provide $oldprovide";$fileproblems=1;
				unshift(@newprovides,$newprovide);
				}

			}
		else
			{
			# end of new array
			if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
			print "\n\tMissing provide $oldprovide";$fileproblems=1;
			}

		}

  	if(shift(@newprovides)){
		# extra in the new array
		if(!$fileproblems and !$libproblems){printf ("%-50s\t", $rpm);};
		print "\n\tExtra provide $newprovide";$fileproblems=1;
		}

	}

    if ($fileproblems || $libproblems) { print "\n";} else { if($all) { print "MATCH\n"; }; } 
}

