#!/usr/bin/perl

# @@@ START COPYRIGHT @@@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# @@@ END COPYRIGHT @@@

use strict;
use warnings;

use Getopt::Long;

# forwards
sub find_elfs ($$);
sub find_jars ($);

# globals
my $detail = 0;
my $help = 0;
my $res;
my $utt = 0;
my $verbose = 0;
my %version_table_patch;
my %version_table_utt;

# main start
$res = GetOptions(
	'd' => \$detail,
	'h' => \$help,
	'u' => \$utt,
	'v' => \$verbose,
	);
my $prog = $0;
$prog =~ s/^.*\///;
if (!$res) {
	print_usage();
	exit(1);
} else {
	if ($help) {
		print_usage();
		exit(0);
	}
}

# get TRAF_HOME/who/host
my $sq_pdsh = defined $ENV{'SQ_PDSH'} ? $ENV{'SQ_PDSH'} : '';
my $traf_home = defined $ENV{'TRAF_HOME'} ? $ENV{'TRAF_HOME'} : '';
my $java_home = defined $ENV{'JAVA_HOME'} ? $ENV{'JAVA_HOME'} : '';
my $sq_mbtype = defined $ENV{'SQ_MBTYPE'} ? $ENV{'SQ_MBTYPE'} : '';
my $host;
gethost();
my $mtype;
my $btype;
getmbtype();

if ($verbose) {
	printf "v: cmd:whoami\n";
}
my $w = `whoami`;
$w =~ s/\s+$//;

if ($verbose) {
	printf "v: cmd:uname -r\n";
}
my $lr = `uname -r`;
$lr =~ s/\s+$//;

my $linuxdistro = "UnknownLinuxDistro";
my $dr = "?.?";
if (-r "/etc/redhat-release") {
    if ($verbose) {
	printf "v: cmd:cat /etc/redhat-release\n";
    }
    $linuxdistro = "redhat";
    my $linuxrel = `cat /etc/redhat-release`;
    if ($linuxrel =~ /^.*release ([0-9][0-9]*)\.([0-9][0-9]*)/) {
	$dr=$1 . "." . $2
    }
} elsif (-r "/etc/SuSE-release") {
    if ($verbose) {
	printf "v: cmd:cat /etc/SuSE-release\n";
    }
    $linuxdistro = "SuSE";
    my $susevers = `cat /etc/SuSE-release | grep VERSION | cut -f 2 -d '='`;
    my $susepatch = `cat /etc/SuSE-release | grep PATCH | cut -f 2 -d '='`;
    # trim white space
    $susevers =~ s/^\s+|\s+$//g;
    $susepatch =~ s/^\s+|\s+$//g;
    $dr=$susevers . "." . $susepatch
}

print "TRAF_HOME=$traf_home\n";
print "who\@host=$w\@$host\n";
print "JAVA_HOME=$java_home\n";
# can't do much if these two env variables are not setup
if (!defined $ENV{'TRAF_HOME'}) {
	die "TRAF_HOME is not set!";
}
if (!defined $ENV{'JAVA_HOME'}) {
	die "JAVA_HOME is not set!";
}

# only print SQ_MBTYPE if NOT 64(release)
if (($mtype =~ /32/) || ($btype =~ /debug/)) {
	print "SQ_MBTYPE=$sq_mbtype ($mtype-$btype)\n";
}
print "linux=$lr\n";
print "$linuxdistro=$dr\n";

# deal with elfs
my $dir_lib = "export/lib" . $sq_mbtype;
find_elfs($dir_lib, 0);
my $dir_bin = "export/bin" . $sq_mbtype;
find_elfs($dir_bin, 0);

# deal with jars
find_jars("export/lib");

if ($utt) {
	utt_print();
}

sub find_elfs ($$) {
	my ($dir, $so) = @_;
	my $chdir = "$traf_home/$dir";
	chdir($chdir) or die "can't chdir to $chdir $!";
	my $ls;
	if ($so) {
		$ls = `ls *.so`;
	} else {
		$ls = `ls`;
	}
	my @files = split(/\n/, $ls);
	my $file;
	foreach $file (@files) {
		my $dochk = 1;
		if ($file =~ /sqlite3/) {
			$dochk = 0;
		}
		if ($file =~ /libudis86.so/) {
			$dochk = 0;
		}
		if ($file =~ /libudis86.so.0/) {
			$dochk = 0;
		}
		if ($file =~ /libjit.so/) {
			$dochk = 0;
		}
		if ($file =~ /libACE.so/) {
			$dochk = 0;
		}
		if ($file =~ /libQtCoreCmpDbg.so.4/) {
			$dochk = 0;
		}
		if ($file =~ /libQtGuiCmpDbg.so.4/) {
			$dochk = 0;
		}
		if ($file =~ /libhdfs.so|libhadoop.so|libmysql|libthrift|libzookeeper|libcurl|^liblog4cxx|^libicu/) {
			$dochk = 0;
		}
		if ($file =~ /mpirun|hydra_pmi_proxy/) {
			$dochk = 0;
		}
		if ($dochk) {
			my $chk = `file -L $file`;
			if ($chk =~ /ELF/) {
				version_elf($dir, $file);
			}
		}
	}
}

sub find_jars ($) {
	my ($dir) = @_;
	my $chdir = "$traf_home/$dir";
	chdir($chdir) or die "can't chdir to $chdir $!";
	my $ls = `ls *.jar`;
	my @files = split(/\n/, $ls);
	my $file;
	foreach $file (@files) {
		my $dochk = 1;
		if ($dochk) {
			version_jar($dir, $file);
		}
	}
}

#
# gethost
#   default to hostname
#   search /etc/pdsh/machines | /opt/hptc/pdsh/nodes for non 'n[0-9][0-9]*'
#
sub gethost {
	if ($verbose) {
		printf "v: cmd:hostname\n";
	}
	$host = `hostname`;
	$host =~ s/\s+$//;
	if (-x ($sq_pdsh)) {
		# find head node
		if ($verbose) {
			printf "v: cmd:cat /etc/pdsh/machines\n";
		}
		my $mach;
		if (-r "/etc/pdsh/machines") {
			$mach = `cat /etc/pdsh/machines`;
		} elsif (-r "/etc/machines") {
			$mach = `cat /etc/machines`;
		} else {
			$mach = `cat /opt/hptc/pdsh/nodes`;
		}
		my @nstrs = split(/\n/, $mach);
		my $nstr;
		foreach $nstr (@nstrs) {
			my $hno = $nstr;
			$hno =~ s/\s+$//;
			if ($verbose) {
				print "v: /etc/pdsh/machines, rec=$hno\n";
			}
			if ($hno !~ /^n[0-9][0-9]*/) {
				$host = $hno;
				last;
			}
		}
	}
	if ($verbose) {
		print "v: *host=$host\n";
	}
	$host =~ s/\s+$//;
}

sub getmbtype {
	$mtype = "unknown";
	$btype = "unknown";
	if ($sq_mbtype =~ /^([0-9][0-9])/) {
		$mtype = "$1";
	}
	if ($sq_mbtype =~ /d/) {
		$btype = "debug";
	} else {
		$btype = "release";
	}
}

#
# Print Patch node
#
sub patch_print_node ($) {
	my ($vers_key) = @_;
	my $node_patch = $version_table_patch{$vers_key};
	my $vers = $node_patch->get_vers();
	my $count = $node_patch->get_count();
	my @list = $node_patch->get_list();
	print "[$count]\t$vers\n";
	my $list_key;
	foreach $list_key (@list) {
		print "\t  $list_key\n";
	}
}

sub print_usage {
	printf "usage: $prog [-d] [-h] [-u] [-v]\n";
	printf "  -d: detail (used with -u)\n";
	printf "  -h: display help\n";
	printf "  -u: display UTTs\n";
	printf "  -v: verbose\n";
}

sub version_elf ($$) {
	my ($dir, $elf) = @_;

	if ($verbose) {
		printf "v: cmd:nm $elf\n";
	}
	my $nm = `nm $elf | grep " VERS_"`;
	my @strs = split(/\n/, $nm);
	my $str;
	my $has_version = 0;
	foreach $str (@strs) {
		my $ver = $str;
		$ver =~ s/^.*?VERS_//;
		$ver =~ s/_sl_/\//g;
		$ver =~ s/_dt_/\./g;
		$ver =~ s/_dh_/-/g;
		if ($ver =~ /([a-zA-Z0-9_]*)_CV(\d*)_(\d*)_(\d*)_PV(\d*)_(\d*)_(\d*)_(\w*)_BV(\w*)_BR([\w\-\.\/]*)_DT(\w*)_SV(.*)/) {
			$has_version = 1;
			my $verstr = "$8 Release $5.$6.$7 (Build $9 [$12], branch $10, date $11)";
			my $bldstr = $12;
			if ($utt) {
				utt_add($dir, $elf, $verstr, $bldstr);
			} else {
				print "$elf $1 Version $2.$3.$4 $verstr\n";
			}
		}
	}
	if (!$has_version) {
		print "\$TRAF_HOME/$dir/$elf missing version string\n";
	}
}

sub version_jar ($$) {
	my ($dir, $jar) = @_;

	my $cmd_vers = "$java_home/bin/java -jar $traf_home/export/lib/sqmanvers.jar $jar";
	if ($verbose) {
		printf "v: cmd:$cmd_vers\n";
	}
	my $vers = `$cmd_vers 2>/dev/null`;
	my @strs = split(/\n/, $vers);
	my $str;
	my $has_version = 0;
	foreach $str (@strs) {
		my $ver = $str;
		if ($ver =~ /^Version (\d*)\.(\d*)\.(\d*) (.*)/) {
			$has_version = 1;
			my $v1 = $1;
			my $v2 = $2;
			my $v3 = $3;
			my $verstr = $4;
			my $bldstr;
			if ($verstr =~ /\(Build [a-z]+ \[([a-zA-Z0-9_-]*)\].*/) {
				$bldstr = $1;
			} else {
				$bldstr = $verstr;
			}
			if ($utt) {
				utt_add($dir, $jar, $verstr, $bldstr);
			} else {
				print "$jar Version $v1.$v2.$v3 $verstr\n";
			}
		}
	}
	
	if (!$utt && !$has_version) {
		print "\$TRAF_HOME/$dir/$jar missing version string\n";
	}
}

#
# Add utt to version_table_utt
#
sub utt_add ($$$$) {
	my ($dir, $elf, $vers, $buildid) = @_;

	if ($buildid =~ /(BUGFIX_[0-9_-]+)/) {
		my $patch = $1;
		my $node_patch;
		if (exists $version_table_patch{$patch}) {
			$node_patch = $version_table_patch{$patch};
			$node_patch->add($dir, $elf);
		} else {
			$node_patch = new Node($dir, $elf, $vers);
			$version_table_patch{$patch} = $node_patch;
		}
	} else {
		my $node_utt;
		if (exists $version_table_utt{$vers}) {
			$node_utt = $version_table_utt{$vers};
			$node_utt->add($dir, $elf);
		} else {
			$node_utt = new Node($dir, $elf, $vers);
			$version_table_utt{$vers} = $node_utt;
		}
	}
}

#
# Print UTTs
#
sub utt_print {
	my $vers_key;
	my $vers_count_patch = keys %version_table_patch;
	my $vers_count_utt = keys %version_table_utt;
	my $vers;
	if ($vers_count_patch < 1) {
		printf "NO patches\n";
	}

	if ($vers_count_utt <= 1) {
		if ($vers_count_patch > 0) {
			printf "Patch count is $vers_count_patch\n";
			foreach $vers_key (sort keys %version_table_patch) {
				patch_print_node($vers_key);
			}
		}

		my @list = %version_table_utt;
		printf "NO UTTs - $list[0]\n";
		return;
	}
	if ($detail) {
		printf "ALL versions displayed\n";

		printf "Patch count is $vers_count_patch\n";
		foreach $vers_key (sort keys %version_table_patch) {
			patch_print_node($vers_key);
		}

		printf "UTT count is $vers_count_utt\n";
		foreach $vers_key (sort keys %version_table_utt) {
			utt_print_node($vers_key);
		}
	} else {
		if ($vers_count_patch > 0) {
			printf "Patch count is $vers_count_patch\n";
			foreach $vers_key (sort keys %version_table_patch) {
				patch_print_node($vers_key);
			}
		}

		my $max = 0;
		my $max_vers = 0;
		foreach $vers_key (sort keys %version_table_utt) {
			my $node_utt = $version_table_utt{$vers_key};
			my $count = $node_utt->get_count();
			if ($count > $max) {
				$max = $count;
				$max_vers = $node_utt->get_vers();
			}
		}
		my $print_count = $vers_count_utt - 1;
		printf "Most common $max_vers\n";
		printf "UTT count is $print_count\n";
		foreach $vers_key (sort keys %version_table_utt) {
			my $node_utt = $version_table_utt{$vers_key};
			$vers = $node_utt->get_vers();
			if ($vers ne $max_vers) {
				utt_print_node($vers_key);
			}
		}
	}
}

#
# Print UTT node
#
sub utt_print_node ($) {
	my ($vers_key) = @_;
	my $node_utt = $version_table_utt{$vers_key};
	my $vers = $node_utt->get_vers();
	my $count = $node_utt->get_count();
	my @list = $node_utt->get_list();
	print "[$count]\t$vers\n";
	my $list_key;
	foreach $list_key (@list) {
		print "\t  $list_key\n";
	}
}

#
# Node object
#
package Node;
sub new {
	my $class = shift;
	my @list = ();
	my $list_r = \@list;
	my $self = {
		_dir => shift,
		_elf => shift,
		_version => shift,
		_count => 1,
		_list => $list_r,
	};
	my $dir = $self->{_dir};
	my $elf = $self->{_elf};
	my $list = $self->{_list};
	push(@{$list}, "$dir/$elf");
	bless $self, $class;
	return $self;
}
sub add {
	my ($self, $dir, $elf) = @_;
	$self->{_count}++;
	my $list = $self->{_list};
	push(@{$list}, "$dir/$elf");
}
sub get_count {
	my ($self) = @_;
	return $self->{_count};
}
sub get_list {
	my ($self) = @_;
	my $list = $self->{_list};
	return @{$list};
}
sub get_vers {
	my ($self) = @_;
	return $self->{_version};
}
