#!/usr/bin/perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#(C)IBM Corp

#

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

=head1   db2install 

 This postscript installs and sets up db2 on a client machine, usually a Service Node
 or a login node. This script can only be used to automatically install an
 AIX 6.1 or 7.1,  or a Linux Redhat5 or Redhat6 Client. 
 Note: the db2 code must be in a mountable directory  defined by the
 db2installloc attribute in the site table.
 The perl-DBD-DB2 code should have been installed during the xCAT install from 
 the AIX or Linux xCAT deps package for AIX6.1 or 7.1 or Redhat5 or Redhat 6.
 More information can be obtained from the xCAT http://xcat.svn.sourceforge.net/viewvc/xcat/xcat-core/trunk/xCAT-client/share/doc/xCAT2SetupDB2.pdf.
 Needs  export MASTER=site.master
        export DB2INSTALLLOC=site.db2installlloc
        export DATABASELOC=site.databaseloc
        export INSTALLDIR=site.installdir
        export XCATDPORT=site.xcatdport
        export NODE=<mynodename>
 
=cut

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

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) {
    unshift(@INC, qw(/usr/opt/perl5/lib/5.8.2/aix-thread-multi /usr/opt/perl5/lib/5.8.2 /usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi /usr/opt/perl5/lib/site_perl/5.8.2));
}

use lib "$::XCATROOT/lib/perl";
use strict;
use IO::Socket;
use File::Path;
use Expect;
use xCAT::Utils;
use xCAT::NetworkUtils;
use xCAT::MsgUtils;

my $useSocketSSL = eval { require IO::Socket::SSL; };
if ($useSocketSSL)
{
    require IO::Socket::SSL;
}

# MAIN

my $rc  = 0;
my $msg = "";
my $cmd;

# setup some important variable
$::osname = `uname`;
chomp $::osname;

$::sdate = `/bin/date`;
chomp $::sdate;

$::hname = `hostname`;
chomp $::hname;

$::MN          = $ENV{'MASTER'};         # as known by the node
$::databaseloc = $ENV{'DATABASELOC'};    # directory for db instance
my $installdir = $ENV{'INSTALLDIR'};     # installdir

# check to see if the databaseloc attribute is in the installdir attribute
# directory path.  If it is error out and send something to syslog
my @installlocdir = split '/', $installdir;       # get base of installdir
my @dblocdir      = split '/', $::databaseloc;    # get base of database
if ($installlocdir[1] eq $dblocdir[1]) {          # if they match,error
    my $msg = "db2install:The site databaseloc attribute is set to the directory or a sub-directory of the site table installloc or installdir attribute or the default of /install.  This is not a supported configuration.";
    `logger -t xcat -p local4.err $msg`;
    exit(1);
}

if ($::osname eq 'AIX')
{
    $::installdb2dir = "/opt/IBM/db2/V9.7";       # default
}
else
{                                                 # linux
    $::installdb2dir = "/opt/ibm/db2/V9.7";       # default
}
my $msg;

# check to see if db2 already installed and configured, if so
# skip the install
my $db2install = $::installdb2dir;
$db2install .= "\/instance";
if (!(-e ($db2install)))
{    # not already installed
    $::db2installloc = $ENV{'DB2INSTALLLOC'};
    if (!$::db2installloc)
    {
        $::db2installloc = "/mntdb2";
    }
    chomp $::db2installloc;
    $::db2installloc =~
      s/^(\'|\")(.*)(\"|\')$/$2/;    # remove any surrounding quotes

    # startNFS
    $rc = &startNFS;

    # mount the db2 directory to get to the db2 code to install
    $rc = &mountdb2code;

    # install db2
    if ($rc == 0)
    {
        $rc = &installdb2code;
        if ($rc != 0)
        {
            $msg = "db2install:Error installing DB2. Cannot continue. \n";
            `logger -t xcat -p local4.err $msg`;
            &unmountdb2code;
            exit(1);

        }

        # unmount the db2 directory install directory
        &unmountdb2code;
    }
    else
    {
        $msg =
"db2install:Cannot mount the site.db2installloc directory to get the db2 code to install. Setup cannot continue.\n";
        `logger -t xcat -p local4.err $msg`;
        exit(1);
    }
}
else
{
    $msg = "db2install:DB2 is already installed. Will skip installation. \n";
    `logger -t xcat -p local4.info  $msg`;
}

# get the cfgloc file,if linux should be in /etc/xcat/cfgloc.db2 from xcatserver
# postscript
$rc = &getcfgloc;
if ($rc != 0)
{    # could not get the cfgloc file, cannot continue
    $msg =
"db2install:Cannot get the cfgloc file from the MN.  Client setup cannot continue. \n";
    `logger -t xcat -p local4.err $msg`;
    exit 1;
}

# setup the db2 Client for xCAT and start xCAT on the DB2 database

&rundb2sqlsetup;

exit 0;

#
# Subroutines
#

# run the command
sub runcmd
{
    my ($cmd) = @_;
    my $rc = 0;

    $cmd .= ' 2>&1';

    # my $outref = [];
    # @$outref = `$cmd`;

    $::outref = [];
    $::outref = `$cmd`;
    if ($?)
    {
        $rc = $? >> 8;
        if ($rc > 0)
        {
            my $msg = "db2install:$cmd returned rc=$rc $::outref\n";
            `logger -t xcat -p local4.info $msg`;
            return 1;
        }
    }
    return 0;
}

#####################################################
#
#  getcfgloc
#		Get  DB cfgloc file
#
#####################################################
sub getcfgloc
{
    my $msg;
    my $filename = "/etc/xcat/cfgloc";
    my $cfgsave  = "/etc/xcat/cfgloc.db2";
    if (-e $cfgsave)
    {
        my $cmd = " cp $cfgsave $filename ";
        xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0)
        {
            $msg = "db2install: $cmd failed.\n";
            `logger -t xcat -p local4.err $msg`;
            return 1;
        }

    }
    else
    {    # file not there get it from the MN
        my $response = &getresponse("xcat_cfgloc");
        if (defined($response))
        {
            my $fd;
            &runcmd("mkdir -p /etc/xcat");
            open($fd, '>', $filename);
            print $fd $response;
            close($fd);

            # set the permissions
            my $cmd = "chmod 600 $filename > /dev/null 2>&1";
            &runcmd($cmd);
        }
        else
        {
            $msg = "db2install: Could not get cfgloc file.\n";
            `logger -t xcat -p local4.err $msg`;
            return 1;
        }

        # get the info
        my $xcatcfg;
        my $cfgl;
        open($cfgl, "<", $filename);
        $xcatcfg = <$cfgl>;
        close($cfgl);
        chomp $xcatcfg;
        my ($hdr, $instance, $password) = split('\|', $xcatcfg);
        if ($hdr =~ /^DB2:/)
        {

            return 0;
        }
        else
        {
            $msg = "db2install: cfgloc file not for DB2.\n";
            `logger -t xcat -p local4.err $msg`;
            return 1;
        }
    }
    return 0;

}
#####################################################
#
# startNFS
#    Makes sure NFS is running
#
#####################################################
sub startNFS

{
    my $rc = 0;

    if (xCAT::Utils->isLinux())
    {
        my $os = xCAT::Utils->osver();
        if ($os =~ /sles.*/)
        {
            $rc = xCAT::Utils->startService("nfs");
            $rc = xCAT::Utils->startService("nfsserver");
        }
        else
        {
            $rc = xCAT::Utils->startService("nfs");
        }
    }
    else
    {    #AIX
        $rc = xCAT::Utils->startService("nfsd");
    }
    if ($rc != 0)
    {
        return 1;
    }

    return $rc;

}
#####################################################
#
#  mountdb2code
#     mount the directory from the db2server that contains
#     the db2 code
#
#####################################################
sub mountdb2code
{
    my $msg;
    my $rc;
    my $server;
    my $db2installloc = $ENV{'DB2INSTALLLOC'};
    if (!($db2installloc))
    {
        $db2installloc = "/mntdb2";    # set default
    }

    # determine server and mount location
    if (grep /:/, $db2installloc)
    {
        my ($hostname, $newinstallloc) = split ":", $db2installloc;
        if ($hostname)
        {                              # hostname set in /installloc attribute
            $server       = $hostname;         # set server for mount
            $::installloc = $newinstallloc;    #set path for mount point
        }
    }
    else
    {
        $server       = $::MN;
        $::installloc = $db2installloc;
    }

    # mount the install directory from the installloc location
    # make the directory to mount on-- same name
    if (!(-e $::installloc))
    {
        mkpath($::installloc);
    }

    # check to see if   already mounted

    my $mounted = xCAT::Utils->isMounted($::installloc);
    if ($mounted == 0)
    {    # not mounted

        # need to  mount the directory
        my $cmd;
        if ($ENV{'USENFSV4ONAIX'} && ($ENV{'USENFSV4ONAIX'} =~ /1|Yes|yes|YES|Y|y/))
        {
            $cmd = "mount -o vers=4 $server:$::installloc $::installloc";
        }
        else
        {
            $cmd = "mount  $server:$::installloc $::installloc";
        }
        $rc = &runcmd($cmd);
    }

    return $rc;

}
#####################################################
#
#  installdb2code
#     installs the db2 code from the mounted server
#
#####################################################
sub installdb2code
{
    my $msg;
    my $rc = 0;
    my $installcode;
    my $installcode1 = "$::installloc/ese/db2_install";
    my $installcode2 = "$::installloc/server/db2_install";
    my $installcode3 = "$::installloc/wser/db2_install";
    my $installcode4 = "$::installloc/wse/db2_install";

    if ((!(-e $installcode1)) && (!(-e $installcode2)) && (!(-e $installcode3)) && (!(-e $installcode4)))
    {
        $msg = "db2install:Install code  does not exist. Cannot install DB2 code.\n";
        `logger -t xcat -p local4.err $msg`;
        exit 1;
    }

    # pick the correct install directory
    if (-e $installcode1) {
        $installcode = $installcode1;
    } else {
        if (-e $installcode2) {
            $installcode = $installcode2;
        } else {
            if (-e $installcode3) {
                $installcode = $installcode3;
            } else {
                $installcode = $installcode4;
            }
        }
    }

    my $expect_log = "/tmp/xcatexpect.log";
    $msg =
"db2install:Starting DB2 install. Monitor progress in $expect_log on the node. \n";
    `logger -t xcat -p local4.info $msg`;
    my $db2sql;
    my $timeout = 1000;    #sets Expect default timeout, 0 accepts immediately
    my $timeout;           # null says wait forever
    my $pwd_sent       = 0;
    my $dir_prompt     = 'Do you want';
    my $product_prompt = " to exit";
    my $debug          = 0;
    if ($::VERBOSE)
    {
        $debug = 1;
    }
    $db2sql = new Expect;

    #
    # -re $dir_prompt
    #    Installation of products - /opt/ibm/db2/V9.7
    #
    #    Do you want to choose a different directory to install [yes/no] ?
    #
    # -re $product_prompt
    #     Specify one of the following keywords to install DB2 products.
    #     ESE
    #     CLIENT
    #     RTCL
    #
    #

    # disable command echoing
    #$db2sql->slave->stty(qw(sane -echo));
    # restart timeout
    $db2sql->restart_timeout_upon_receive(1);

    #
    # exp_internal(1) sets exp_internal debugging
    # to STDERR.
    #
    #$db2sql->exp_internal(1);
    $db2sql->exp_internal($debug);

    #
    # log_stdout(0) prevent the program's output from being shown.
    #
    #$db2sql->log_stdout(1);
    $db2sql->log_stdout($debug);

    #
    # log session
    #
    #$db2sql->log_file($expect_log);
    $db2sql->log_file($expect_log);

    my $spawncmd;
    $spawncmd = "$installcode";
    unless ($db2sql->spawn($spawncmd))
    {
        $msg = "db2install:Unable to run $spawncmd to create database.\n";
        `logger -t xcat -p local4.err $msg`;
        exit 1;

    }

    #
    # setup input to db2_install
    #

    my @result = $db2sql->expect(
        $timeout,
        [
            $dir_prompt,
            sub {
                $db2sql->send("no\r");
                $db2sql->clear_accum();
                $db2sql->exp_continue();
            }
        ],
        [
            $product_prompt,
            sub {

                $db2sql->send("CLIENT\r");
                $db2sql->exp_continue();

            }
        ]
    );
    ##########################################
    # Expect error - report and quit
    ##########################################
    if (defined($result[1]))
    {
        my $errmsg = $result[1];
        $db2sql->soft_close();
        $msg = "db2install:Installing DB2 results = $errmsg.\n";
        `logger -t xcat -p local4.info  $msg`;

    }
    $db2sql->soft_close();
    $msg = "db2install:Ending DB2 install.\n";
    `logger -t xcat -p local4.info $msg`;
    return $rc;

}

#####################################################
#
#  unmountdb2code
#     unmounts the directory from the db2server that contains
#     the db2 code
#
#####################################################
sub unmountdb2code
{
    my $msg;
    my $rc = 0;
    my $cmd;

    # need to  un mount the directory
    if ($::osname eq 'AIX')
    {
        $cmd = "unmount $::installloc";
    }
    else
    {
        $cmd = "umount $::installloc";
    }
    $rc = &runcmd($cmd);

    return $rc;

}
#####################################################
#
#  rundb2sqlsetup
#     runs the db2sqlsetup script and sets up xCAT/DB2 Client
#
#####################################################
sub rundb2sqlsetup
{
    my $msg;
    my $rc = 0;
    my $cmd;
    my $filename = "/etc/xcat/cfgloc";

    # get the info
    my $xcatcfg;
    my $cfgl;
    open($cfgl, "<", $filename);
    $xcatcfg = <$cfgl>;
    close($cfgl);
    chomp $xcatcfg;
    my ($database, $instance, $password) = split('\|', $xcatcfg);
    if ($::databaseloc) {

        if (!(-e $::databaseloc)) {    # if it does not exist, create it
            &runcmd("mkdir -p $::databaseloc");
        }
        $cmd =
"DATABASELOC=$::databaseloc XCATDB2SERVER=$::MN XCATDB2PW=$password $::XCATROOT/bin/db2sqlsetup -i -C";
    } else {
        $cmd =
"XCATDB2SERVER=$::MN XCATDB2PW=$password $::XCATROOT/bin/db2sqlsetup -i -C";
    }
    $msg = "db2install:Running Client setup. \"$::XCATROOT/bin/db2sqlsetup -i -C\"\n";
    `logger -t xcat -p local4.info $msg`;

    $rc = &runcmd($cmd);

    $msg = "db2install: Client setup finished.\n";
    `logger -t xcat -p local4.info $msg`;

    return $rc;

}
#####################################################
#
#  getresponse
#       Request info from xcatd on the management node
#
#	- uses SSL socket on port 3001 to connect to MN xcatd
#		to make the request for info
#
#####################################################
sub getresponse
{
    my ($req) = @_;
    my $msg;
    my $port = $ENV{'XCATDPORT'};
    my $node = $ENV{'NODE'};

    # open listener connection to wait for check from management node
    my $lpid = &openlistener();

    # open a socket to request data
    my $sock =
      IO::Socket::SSL->new(
        PeerAddr => $::MN,
        PeerPort => $port,
        Proto    => 'tcp',
      );

    # try a few more times
    my $times = 1;
    while (!$sock)
    {
        sleep(2);
        $times++;
        $sock =
          IO::Socket::SSL->new(
            PeerAddr => $::MN,
            PeerPort => $port,
            Proto    => 'tcp',
          );
        if ($times == 5)
        {
            last;
        }
    }

    unless ($sock)
    {
        $msg = "db2install: Cannot connect to host \'$::MN\'\n";
        `logger -t xcat -p local4.err $msg`;
        print $msg;
        return undef;
    }

    # request must be in XML format
    print $sock "<xcatrequest>\n";
    print $sock "   <command>getcredentials</command>\n";
    print $sock "   <arg>$req</arg>\n";
    print $sock "   <callback_port>300</callback_port>\n";
    print $sock "</xcatrequest>\n";

    #TODO - do we have to try again after waiting for a bit????
    my $response = '';
    my $line;
    while (defined($line = <$sock>))
    {

        # skip xml tags
        next if ($line =~ /^\s*</);

        # once we get to serverdone we have the whole response
        if ($line =~ m/<\/serverdone>/)
        {
          last:
        }
        $response .= $line;
    }

    close($sock);

    #print "resp = \'$response\'\n";

    kill 2, $lpid;
    if ($response)
    {
        return $response;
    }
    return undef;
}

#####################################################
#
#  openlistener
#   - fork a child process to respond to a check from the MN
#
#####################################################
sub openlistener
{
    my $node = $ENV{'NODE'};

    # fork a child process to open a socket to listen for communication
    #	from the server
    my $pid = xCAT::Utils->xfork;
    unless (defined $pid)
    {

        # fork failed
        $msg = "db2install: Could not fork process.\n";
        `logger -t xcat -p local4.err $msg`;

        #print $msg;
        return undef;
    }

    if ($pid != 0)
    {

        # This is the parent process, just return
        return $pid;
    }

    my $listener =
      IO::Socket::INET->new(
        LocalPort => '300',
        Proto     => 'tcp',
        Listen    => '64',
        Reuse     => 1
      );

    unless ($listener)
    {
        my $msg = "db2install: Cannot open socket on \'$node\'\n";
        `logger -t xcat -p local4.err $msg`;
        print $msg;
        exit 1;
    }

    #	xcatd sends a quick req to see if we are really asking
    #  	for info - this listener checks for the req and says ok
    my $client;
    while ($client = $listener->accept())
    {

        # $client is the new connection
        my $text = <$client>;

        #  see if we got "CREDOKBYYOU?"
        if ($text =~ /CREDOKBYYOU?/)
        {
            print $client "CREDOKBYME";
            close($client);
            close($listener);
            exit 0;
        }
        close($client);
    }
    close($client);
    close($listener);
    exit 0;
}
