#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::InstUtils;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}

# if AIX - make sure we include perl 5.8.2 in INC path.
#       Needed to find perl dependencies shipped in deps tarball.
if ($^O =~ /^aix/i) {
        use lib "/usr/opt/perl5/lib/5.8.2/aix-thread-multi";
        use lib "/usr/opt/perl5/lib/5.8.2";
        use lib "/usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi";
        use lib "/usr/opt/perl5/lib/site_perl/5.8.2";
}

use lib "$::XCATROOT/lib/perl";
require xCAT::Table;
use POSIX qw(ceil);
use Socket;
use Sys::Hostname;
use strict;
require xCAT::Schema;
#require Data::Dumper;
use Data::Dumper;
require xCAT::NodeRange;
require DBI;

our @ISA       = qw(Exporter);
our @EXPORT_OK = qw(genpassword);

#-------------------------------------------------------------------------------

=head1    xCAT::InstUtils

=head2    Package Description

This program module file, is a set of utilities used by xCAT install
related commands.

=cut

#-------------------------------------------------------------

#----------------------------------------------------------------------------

=head3  getnimprime

	Get the name of the primary AIX NIM master

    Returns:

			hostname - short hostname of primary NIM master
			undef	 - could not find primary NIM master
    Example:

		my $nimprime = xCAT::InstUtils->getnimprime();
    Comments:

=cut

#-----------------------------------------------------------------------------

sub  getnimprime
{

	# the primary NIM master is either specified in the site table
	# or it is the xCAT management node.

	my $nimprime = xCAT::Utils->get_site_Master();
	my $sitetab = xCAT::Table->new('site');
	(my $et) = $sitetab->getAttribs({key => "NIMprime"}, 'value');
	if ($et and $et->{value}) {
		$nimprime = $et->{value};
	}

	my $hostname;
	if ($nimprime) {
		if ($nimprime =~ /\d+\.\d+\.\d+\.\d+/) {
			my $packedaddr = inet_aton($nimprime);
			$hostname = gethostbyaddr($packedaddr, AF_INET);
		} else {
			$hostname = $nimprime;
		}

		my $shorthost;
		($shorthost = $hostname) =~ s/\..*$//;
		chomp $shorthost;
		return $shorthost;
	} 

	return undef;
}

#----------------------------------------------------------------------------

=head3  myxCATname

	Gets the name of the node I'm running on - as known by xCAT
	(Either the management node or a service node)


=cut

#-----------------------------------------------------------------------------

sub myxCATname
{
	my ($junk, $name);

	$name = hostname();

	if (xCAT::Utils->isMN()) {
		# read the site table, master attrib
		my $hostname = xCAT::Utils->get_site_Master(); 
		if ($hostname =~ /\d+\.\d+\.\d+\.\d+/) {
			my $packedaddr = inet_aton($hostname);
			$name = gethostbyaddr($packedaddr, AF_INET);
		} else {
			$name = $hostname;
		}

	} elsif (xCAT::Utils->isServiceNode()) {

		# the myxcatpost_<nodename> file should exist on all nodes!
		my $catcmd="cat /xcatpost/myxcatpost_* | grep '^NODE='";
		my $output = xCAT::Utils->runcmd("$catcmd", -1);
		if ($::RUNCMD_RC == 0) {
			($junk, $name) = split('=', $output);
		}
	}

	my $shorthost;
    ($shorthost = $name) =~ s/\..*$//;
    chomp $shorthost;
    return $shorthost;
}

#----------------------------------------------------------------------------

=head3  is_me

	returns 1 if the hostname is the node I am running on

	Gets all the interfcaes defined on this node and sees if 
		any of them match the IP of the hostname passed in

    Arguments:
        none
    Returns:
        1 -  this is the node I am running on
        0 -  this is not the node I am running on
    Globals:
        none
    Error:
        none
    Example:
         if (xCAT::InstUtils->is_me(&somehostname)) { blah; }
    Comments:
        none

=cut

#-----------------------------------------------------------------------------

sub is_me
{
    my ($class, $name) = @_;

	# convert to IP
	my $nameIP = inet_ntoa(inet_aton($name));
    chomp $nameIP;

	# split into octets
	my ($b1, $b2, $b3, $b4) = split /\./, $nameIP;

	# get all the possible IPs for the node I'm running on
    my $ifcmd = "ifconfig -a | grep 'inet '";
    my $result = xCAT::Utils->runcmd($ifcmd, 0, 1);
    if ($::RUNCMD_RC != 0)
    {
		my $rsp;
	#	push @{$rsp->{data}}, "Could not run $ifcmd.\n";
    #    xCAT::MsgUtils->message("E", $rsp, $callback);
		return 0;
    }

    foreach my $int (@$result)
    {
        my ($inet, $myIP, $str) = split(" ", $int);
		chomp $myIP;
		# Split the two ip addresses up into octets
    	my ($a1, $a2, $a3, $a4) = split /\./, $myIP;		

		if ( ($a1 == $b1) && ($a2 == $b2) && ($a3 == $b3) && ($a4 == $b4) ) {
			return 1;
		}		
    }
	return 0;
}

#----------------------------------------------------------------------------

=head3  get_nim_attr_val

        Use the lsnim command to find the value of a resource attribute.

        Arguments:
        Returns:
                0 - OK
                1 - error
        Globals:

        Error:

        Example:

				xCAT::InstUtils->get_nim_attr_val

        Comments:
=cut

#-----------------------------------------------------------------------------
sub get_nim_attr_val 
{
	my $class = shift;
	my $resname = shift;
	my $attrname = shift;
	my $callback = shift;
	my $target = shift;
	my $sub_req = shift;

	if (!$target) {
		$target = xCAT::InstUtils->getnimprime();
	}
	chomp $target;

	my $cmd = "/usr/sbin/lsnim -a $attrname -Z $resname 2>/dev/null";
    my $nout = xCAT::InstUtils->xcmd($callback, $sub_req, "xdsh", $target, $cmd, 0);
    if ($::RUNCMD_RC  != 0)
    {
        my $rsp;
        push @{$rsp->{data}}, "Could not run lsnim command: \'$cmd\'.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return undef;
    }

    my ($junk, $junk, $junk, $loc) = split(/:/, $nout);
    chomp $loc;

    return $loc;
}

#-------------------------------------------------------------------------------

=head3	xcmd
		Run command either locally or on a remote system.
		Calls either runcmd or runxcmd and does either xdcp or xdsh.

 	Arguments:

   	Returns:
		Output of runcmd or runxcmd or undef.

   	Comments:

	ex.	xCAT::InstUtils->xcmd($callback, $sub_req, "xdcp", $nimprime, $doarray, $cmd);

=cut

#-------------------------------------------------------------------------------
sub xcmd
{
	my $class = shift;
	my $callback = shift;
	my $sub_req = shift;
	my $xdcmd = shift;		# xdcp or xdsh
	my $target = shift;		# the node to run it on
	my $cmd = shift; 		# the actual cmd to run
	my $doarray = shift;    # should the return be a string or array ptr?

	my $returnformat = 0;   # default is to return string
	my $exitcode = 0;
	if ($doarray) {
		$returnformat = $doarray;
	}

	my $output;
    if (xCAT::InstUtils->is_me($target)) {
        $output=xCAT::Utils->runcmd($cmd, $exitcode, $returnformat);

    } else {
        # need xdsh or xdcp
        my @snodes;
        push( @snodes, $target );

        $output=xCAT::Utils->runxcmd(
                                {
                                    command => [$xdcmd],
                                    node    => \@snodes,
                                    arg     => [ $cmd ]
                                },
                                $sub_req,
                                $exitcode, $returnformat
        );
    }

	if ($doarray) {
		return @$output;
	} else {
		return $output;
	}

	return undef;
}

#----------------------------------------------------------------------------

=head3 readBNDfile

	Get the contents of a NIM installp_bundle file based on the name
		of the NIM resource.

=cut

#-----------------------------------------------------------------------------
sub  readBNDfile
{

	my ($class, $callback, $BNDname, $nimprime, $sub_req) = @_;

	my $junk;
	my @pkglist,
	my $pkgname;

	# get the location of the file from the NIM resource definition
	my $bnd_file_name = xCAT::InstUtils->get_nim_attr_val($BNDname, 'location', $callback, $nimprime, $sub_req);

	# open the file
	unless (open(BNDFILE, "<$bnd_file_name")) {
		return (1);
	}

	# get the names of the packages
	while (my $l = <BNDFILE>) {

		chomp $l;

		# skip blank and comment lines
        next if ($l =~ /^\s*$/ || $l =~ /^\s*#/);

		push (@pkglist, $l);
	}
	close(BNDFILE);

	return (0, \@pkglist, $bnd_file_name);
}

#----------------------------------------------------------------------------

=head3   restore_request

		Restores an xcatd request from a remote management server
		into the proper format by removing arrays that were added by
		XML and removing tags that were added to numeric hash keys.

		Arguments:
        Returns:
                ptr to hash
                undef
        Globals:
        Example:
        Comments:

=cut

#-----------------------------------------------------------------------------
sub restore_request
{
	my $class = shift;
	my $in_struct = shift;
	my $callback = shift;

	my $out_struct;

	if (ref($in_struct) eq "ARRAY") {
		# flatten the array it it has only one element 
		#  otherwise leave it alone
		if ( scalar(@$in_struct) == 1) {
    		return (xCAT::InstUtils->restore_request($in_struct->[0]));
		} else {
			return ($in_struct);
		}
	}

	if (ref($in_struct) eq "HASH") {
    	foreach my $struct_key (keys %{$in_struct}) {
        	my $stripped_key = $struct_key;
        	$stripped_key =~ s/^xxXCATxx(\d)/$1/;
			# do not flatten the arg or node arrays
			if (($stripped_key =~ /^arg$/) || ($stripped_key =~ /^node$/)){
				$out_struct->{$stripped_key} = $in_struct->{$struct_key};
			} else {
        		$out_struct->{$stripped_key} = xCAT::InstUtils->restore_request($in_struct->{$struct_key});
			}
    	}
    	return $out_struct;
	}

	if ((ref($in_struct) eq "SCALAR") || (ref(\$in_struct) eq "SCALAR")) {
    	return ($in_struct);
	}

	print "Unsupported data reference in restore_request().\n";
	return undef;
}

#----------------------------------------------------------------------------

=head3   taghash

		Add a non-numeric tag to any hash keys that are numeric.  

		Arguments:
        Returns:
                0 - OK
                1 - error
        Globals:
        Example:
        Comments:
			XML will choke on numeric values.  This happens when including
			a hash in a request to a remote service node.

=cut

#-----------------------------------------------------------------------
sub taghash
{
	my ($class, $hash) = @_;

    if (ref($hash) eq "HASH") {
        foreach my $k (keys %{$hash}) {
            if ($k =~ /^(\d)./ ) {
                my $tagged_key = "xxXCATxx" . $k;
                $hash->{$tagged_key} = $hash->{$k};
				delete($hash->{$k});
            }
        }
        return 0;
    } else {
        return 1;
    }
}

#-------------------------------------------------------------------------------

=head3  getOSnodes
			Split a noderange into arrays of AIX and Linux nodes.

    Arguments:
			\@noderange - reference to onde list array
    Returns:
		$rc -
			1 - yes, all the nodes are AIX
			0 - no, at least one node is not AIX
		\@aixnodes - ref to array of AIX nodes
		\@linuxnodes - ref to array of Linux nodes
		

    Comments:
		Based on "os" attr of node definition.  This assumes that the "os" 
		attribute of AIX nodes will always be set!

	Example:
    	my ($rc, $AIXnodes, $Linuxnodes) 
					= xCAT::InstUtils->getOSnodes(\@noderange) 

=cut

#-------------------------------------------------------------------------------
sub getOSnodes
{
	my ($class, $nodes) = @_;

	my @nodelist = @$nodes;
	my $rc=1;  # all AIX nodes
	my @aixnodes;
	my @linuxnodes;

	my $nodetab = xCAT::Table->new('nodetype');
    my $os = $nodetab->getNodesAttribs( \@nodelist, [ 'node', 'os' ]);
	foreach my $n (@nodelist) {
		if ( ($os->{$n}->[0]->{os} ne "AIX") && ($os->{$n}->[0]->{os} ne "aix")) {
			push(@linuxnodes, $n);
			$rc = 0;
		} else {
			push(@aixnodes, $n);
		}
	}
	$nodetab->close;

	return ($rc, \@aixnodes, \@linuxnodes);
}

1;