#!/usr/bin/perl -w
###########################################################################
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP.
# @(#) Serviceguard command for vCenter and ESXi user credential management
# @(#) Product Name    : HP Serviceguard
# @(#) Product Version : A.12.10.00
# @(#) Patch Name      : 
###########################################################################

use strict;
use Fcntl;
use Getopt::Long qw(GetOptions HelpMessage :config bundling no_ignore_case);
use Pod::Usage;
use Sys::Hostname;
use File::Path;
use File::Copy;
use Data::Dumper;
my $sgeasylib = undef;
my @file_out = undef;
my $CMD_PWD_MGMT = "cmvmusermgmt";

BEGIN {
    my $sgconffile = "/etc/cmcluster.conf";
    $CMD_PWD_MGMT = "cmvmusermgmt";
    if (-f $sgconffile) {
        @file_out = qx(cat $sgconffile);
    } else {
        print STDERR "ERROR: Unable to run $CMD_PWD_MGMT command on ".
                     "unsupported OS\n";
        exit 99;
    }

    my @sglib_out = grep(/^SGLIB/, @file_out);
    my @split_sglib_name = split(/=/,$sglib_out[0]);
    chomp($split_sglib_name[1]);
    $sgeasylib = $split_sglib_name[1];
}
use lib "$sgeasylib";
use EDTools;
use EDPkgTools;


=head1 NAME

cmvmusermgmt

=cut

# arguments
# -U -H host_name -H ....
# -U -F <input_file>
# example :
# cmvmusermgmt -U -S abc
# cmvmusermgmt -U -S abc -H host1 -H host2
# cmvmusermgmt -U -H host1 -H host2 
# cmvmusermgmt -U -F /tmp/host_user_file.txt
# cmvmusermgmt -U -F /tmp/host_user_file.txt -S abc
# cmvmusermgmt -R
# cmvmusermgmt -R -f line
# cmvmusermgmt -D -H host1
# cmvmusermgmt -D -H host1 -H host2
# cmvmusermgmt -D -F /tmp/host_file.txt
# cmvmusermgmt -s -n node1
# or
# cmvmusermgmt --sync -n node1

=head1 SYNOPSIS

cmvmusermgmt -U [-S <credential_key>] -F <hostfile> | -H <host_name> ...

cmvmusermgmt S<-D -F <hostfile>> | -H <host_name> ...

cmvmusermgmt S<-R [-f line]>

cmvmusermgmt S<-U -S <credential_key>>  

cmvmusermgmt S<-s|-sync -n <node_name>>

cmvmusermgmt S<-h|-help>

=cut

=head1 DESCRIPTION

Configuration of VMware vCenter/ESXi Host credentials required to configure 
Serviceguard packages with VMware Virtual Machine file system(VMFS) 
or storage exposed using raw device mapping(RDM).
These credentials are stored locally and is one per cluster 
configuration.

=head1 OPTIONS

=item -U

To update(add or modify) VMware vCenter/ESXi Host user credentials to 
Serviceguard Credential Store(SCS). These user credentials are 
required for configuring/managing VMware VMFS/RDM disks

=item -D

To remove VMware vCenter/ESXi Host user credentials from Serviceguard 
Credential Store(SCS)

=item -R

To view VMware vCenter/ESXi Host user credentials added to Serviceguard 
Credential Store(SCS)

=item -S credential_key

To specify Credential key (string) that will be used to secure passwords

=item -H host_name

To specify host name/IP adddress of the VMware vCenter/ESXi Host whose 
credentials are to be added/modified/deleted in the Serviceguard 
Credential Store(SCS) 

=item -F file

To specify name of file with absolute path that contains list of 
hostname/IP adddress of VMware vCenter/ESXi Host and username to update
in Serviceguard Credential Store(SCS) or list of only hostname/IP 
adddress of VMware vCenter/ESXi Host to delete from SCS

    Sample file would be for update operation with -F option:

        HostName=node1.abc.com
        UserName=user1
        HostName=node2.abc.com
        UserName=user2

    Sample file would be for delete operation with -F option:

        HostName=node1.abc.com
        HostName=node2.abc.com

=item -f line

To view in line format the VMware vCenter/ESXi Host user credentials 
added to Serviceguard Credential Store(SCS)

=item -n node_name

To specify cluster node name with whose Serviceguard Credential 
Store(SCS), the current node's SCS will be synchronized

=item -s|-sync

To synchronize the Serviceguard Credential Store(SCS) on the execution
node with the SCS on the node specified by the -n option

=item -help

Displays help

=cut

# Constant
use constant EXPECT_SUCCESS => 0;
use constant EXPECT_ANY     => 1;
use constant USAGE_ERROR    => 1;
use constant PARSING_ERROR  => 2;
use constant FILE_CORRUPTED_ERROR   => 11;
use constant INCORRECT_JAVA_VERSION => 19;
use constant COMMAND_FAILURE_ERROR  => 100;
use constant UNDEFINED_ERROR => 254;
use constant ERROR    => 0;
use constant WARNING  => 1;
use constant INFO     => 2;

# Global arrays (@ARGV is also global)
my @cls_hosts              = ();
my @cls_nodes              = ();
my @reachable_cls_nodes    = ();
my @unreachable_cls_nodes  = ();
my @input_hosts        = ();
my @input_file         = ();
my @node_list_with_updated_cred_db = ();
my @cred_list          = ();

my $FIND_CMD     = "/usr/bin/find";

my $sgeasyTool   = new EDTools($CMD_PWD_MGMT);

my $NON_FETCH_OP_SELECTED = 0;

my $SGBIN        = get_sgsbin_path(); # SGSBIN value obtained from
                                      # /etc/cmcluster.conf
my $SGLIB        = $sgeasyTool->get_sglib_path();  # SGLIB value obtained from
                                                   # /etc/cmcluster.conf
my $SG_CONF_PATH = $sgeasyTool->get_sgconf_path(); # SGCONF value obtained from
                                                   # /etc/cmcluster.conf
my $SGRUN        = $sgeasyTool->get_sgrun_path();  # SGRUN value obtained from
                                                   # /etc/cmcluster.conf
my $SGLX12       = 12;

my $SGLX_MINOR_VER_WITH_VMFS_ENABLED = 40;

my $JAVA        = "java";

my $JAR         = "\-jar";

my $JAVA_TOOL   = "$SGBIN/vmwutil";

my $CRED_KEY_FILE   = "$SG_CONF_PATH/.sdata";

my $CLUSTER_CONFIGURED                        = 0;

# Option related global variables
my $opt_opstring = undef;
my $fline_arg    = undef;
my $cred_key     = undef;
my $operation    = undef;
my $z_option_enabled        = 0;
my $update_option_enabled   = 0;
my $delete_option_enabled   = 0;
my $retrive_option_enabled  = 0;
my $fline_option_enabled    = 0;
my $sync_cred_file_enabled  = 0;
my $debug_option_enabled    = 0;#Internal use only
my $update_only_cred_key_option_enabled = 0;
my $create_cred_key_option_enabled = 0;

my $skip_delete_lock_file    = 0;
my $backup_of_cred_data_done = 0;
my $localnode                 = undef;
my $node_with_updated_cred_db = undef;
my $input_file                = undef;
my $oper_related_to_obj       = "Serviceguard Credential Store(SCS)";
my $c_db                      = 0;
my $c_key                     = 0;

my %java_path_on_node = ();
my %hash_of_cls_host_in_cred_db = ();
my %hash_of_cls_host_short_nm_cred_db = ();

## CMVMUSERMGMT
# It calls main function which set up host credential on Serviceguard node(if 
# cluster configured) or local node(if no cluster)
&main();


#This function will be used to construct messages in machine parsable 
#output
sub get_formatted_str {
    my ($msg, $tag) = @_;
    my $tag_str =undef;
    if (defined($tag)) {
        if ($tag == WARNING) {
            $tag_str = "WARNING";
        } elsif($tag == ERROR) {
            $tag_str = "ERROR";
        } else {
            $tag_str = "INFO";
        }
    } else {
        $tag_str = "ERROR";
    }
    return ("msgtype:$tag_str|message=", $tag_str);
}

#This function will be used to print messages with 
#ERROR/WARNING tags.
sub log_msg {
    my ($msg, $tag) = @_;
    if (!defined($tag)) {
        $tag = INFO; 
    }
    my ($formatted_msg_str, $tag_str) = get_formatted_str($msg, $tag);
    if ($z_option_enabled) {
        $msg =~ s/\n/ /g;
        if ($tag != INFO) {
            print STDERR $formatted_msg_str.$msg.".\n";
        } else {
            print STDOUT "$msg.\n";
        }
    } else {
        if ($tag != INFO) {
            print STDERR "$tag_str: ".$msg.".\n";
        } else {
            print STDOUT "$msg.\n";
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : fail()
# Calls         : lck_file_cleanup_on_nodes()
# Called by     : test_cmexec_on_all_nodes(), check_for_duplicate_entries(), 
#                 check_for_root_user(), set_java_path(), java_ver_check(), 
#                 check_sg_version_on_nodes(), runCmd(), captureCmd(),
#                 check_and_create_lck_file_on_nodes(), 
#                 validate_input_file_populate_host_list(), 
#                 sync_file_from_a_node(), 
#                 sync_cred_data_from_a_node(), 
#                 sync_cred_data_from_a_node(), perform_pwd_mgmt(), 
#                 verify_credential_for_hosts(), main()
# Globals       : -
# Input Params  : Message to be shown as error and exist code of 
#                 program (optional)
# Return Value  : -
#
# This function display error msg on stdout. It also do cleanup of lock files.
#-----------------------------------------------------------------------------
sub fail {
    my ($msg, $exit_code) = @_;
    log_msg($msg, ERROR);
    if (!defined($exit_code)) {
        $exit_code = COMMAND_FAILURE_ERROR;
    }
    #In sync or update operation we need to rollback the Credential Store and 
    #Credential key file on local node.
    if ($backup_of_cred_data_done) {
        rollback_of_cred_data_on_local_node();
    }
    if ($skip_delete_lock_file == 0) { 
        if (-f "$SGRUN/$CMD_PWD_MGMT.lck") {
            lck_file_cleanup_on_nodes();
        }
    }
    exit($exit_code);
}

#-----------------------------------------------------------------------------
#
# Subroutine    : usage_error()
# Calls         : get_formatted_str(), pod2usage
# Called by     : parse_args()
# Globals       : -
# Input Params  : Error message string
# Return Value  : -
#
# This function will print message in STDOUT and 
# exit the command with 1. 
# In also take care of formating message for SG_MGR with "-z" opt
#-----------------------------------------------------------------------------
sub usage_error {
    my ($message_text) = @_;
    my ($formatted_str, $tag_str) = get_formatted_str($message_text);
    if ($z_option_enabled) {
        $message_text = $formatted_str.$message_text;
        $message_text =~ s/\n/ /g;
    } else {
        $message_text = "$tag_str: $message_text";
    }
    pod2usage( -message => "$message_text\.\n", 
               -exitval => USAGE_ERROR );
}

#-----------------------------------------------------------------------------
#
# Subroutine    : parse_args()
# Calls         : -
# Called by     : -
# Globals       : @input_hosts, $delete_option_enabled, $update_option_enabled, 
#                 $retrive_option_enabled, $z_option_enabled, 
#                 $sync_cred_file_enabled, $cred_key, $opt_opstring,
#                 @node_list_with_updated_cred_db, $fline_option_enabled
#                 $node_with_updated_cred_db, @input_file, $input_file
# Input Params  : None
# Return Value  : -
#
# This function does parsing of command line opts and arguments.
# If user try to use incorrect combination of option then it fails
# the command and print the error message.
#-----------------------------------------------------------------------------
sub parse_args {
    GetOptions(
        "H=s"         => \@input_hosts,
        "n=s"         => \@node_list_with_updated_cred_db,
        "U"           => \$update_option_enabled,
        "D"           => \$delete_option_enabled,
        "R"           => \$retrive_option_enabled,
        "z"           => \$z_option_enabled,
        "sync|s"      => \$sync_cred_file_enabled,
        "f=s"         => \$fline_arg,
        "F=s"         => \@input_file,
        "S=s"         => \@cred_list,
        "debug|d"     => \$debug_option_enabled, #For debugging
        "help|h|?"    => sub { pod2usage(-exitval => 0, 
                                         -verbose => 1) },
     ) || usage_error("Invalid argument");

     if (@ARGV){
         usage_error("Unknown arguments on command line : " .
                     join (" ", @ARGV));
     }

     if ($update_option_enabled) {
         $operation = "update";
     } elsif ($delete_option_enabled) {
         $operation = "delete";
     } elsif ($retrive_option_enabled) {
         $operation = "retrieve";
     } elsif ($sync_cred_file_enabled) {
         $operation = "synchronize";
     }

     # Below check will not allow to use of multiple "-S" with command.
     if (scalar(@cred_list) > 1) { 
         usage_error("Option -S should not be supplied more than once");
     } elsif (scalar(@cred_list) == 1) { 
         $cred_key = "@cred_list"; 
         if (($cred_key =~ /^ *$/) || ($cred_key =~ /^\s*$/) || 
             (length($cred_key) == 0)) {
             usage_error("Invalid value is specified as ".
                         "argument with -S option");
         } 
     }

     # Below check will not allow to use of multiple "-n" with command.
     if (scalar(@node_list_with_updated_cred_db) > 1) { 
         usage_error("Option -n should not be supplied more than once");
     } elsif (scalar(@node_list_with_updated_cred_db) == 1) { 
         $node_with_updated_cred_db = "@node_list_with_updated_cred_db"; 
     }

     if ((defined($fline_arg) && ($fline_arg ne "line"))) {
         usage_error("Only \"line\" is allowed as argument with -f option");
     }

     if (defined($fline_arg) && ($fline_arg eq "line")) {
         if (!($retrive_option_enabled)) {
             usage_error("-f line option is supported only with -R");
         } else {
             $fline_option_enabled = 1;
         }
     }

     if (!$update_option_enabled && !$delete_option_enabled &&
         !$retrive_option_enabled &&
         !$sync_cred_file_enabled) {
         usage_error("No operation specified");
     }
     
     if (($update_option_enabled + $delete_option_enabled +
          $retrive_option_enabled + $sync_cred_file_enabled) > 1) {
          usage_error("Specifying more than one operation is not allowed");
     }

     if ($sync_cred_file_enabled) {
         if (!defined($node_with_updated_cred_db)) {
             usage_error("Missing -n option with -s|--sync");
         }
         if ($z_option_enabled) {
             usage_error("Unsupported option used with -s|--sync option");
         }
     } else {
         if (defined($node_with_updated_cred_db)) {
             usage_error("-n option is supported only with -s|--sync");
         }
     }

     if (defined($cred_key) && !$update_option_enabled) {
         usage_error("-S option is allowed only with -U");
     }

     if ($update_option_enabled || $delete_option_enabled || 
         $sync_cred_file_enabled) {
         $NON_FETCH_OP_SELECTED = 1;                
     }

     if (scalar(@input_file) > 1) {
         usage_error("Option -F should not be supplied more than once");
     } elsif (scalar(@input_file) == 1) {
         $input_file = $input_file[0];
     }

     if ($update_option_enabled || $delete_option_enabled) {
         if (defined($input_file) && (scalar(@input_hosts) > 0)) {
             usage_error("Either -F or -H can be specified");
         } elsif (!defined($input_file) && (scalar(@input_hosts) == 0)) {
             if (!defined($cred_key)) {
                 usage_error("Missing both option -F and -H");
             }
         }
     } else {
         if (defined($input_file) || (scalar(@input_hosts) > 0)) {
             usage_error("-F or -H option is allowed only with -U|-D");
         }
     }

     if (defined($cred_key) && 
        (defined($input_file) || (scalar(@input_hosts) > 0))) {
         if (-f "$SG_CONF_PATH/.credentials.xml") {
             usage_error("Serviceguard Credential Store already exist, -S ".
                         "option is not allowed with -F|-H,\n".
                         "to add/update Credential key use ".
                         "\"$CMD_PWD_MGMT -U -S <Credential key>\"" , ERROR);
         } else {
             $create_cred_key_option_enabled = 1;
         }
     }

     if (defined($cred_key) && (!defined($input_file) && 
                                (scalar(@input_hosts)==0))) {
         if (!(-f "$SG_CONF_PATH/.credentials.xml")) {
             usage_error("Failed to update Credential key as ".
                         "Serviceguard Credential Store(SCS) does not exist.\n". 
                         "To create Credential key and SCS, add at least".
                         " one entry to SCS using -U option\n".
                         " and try again");
         } else  {       
               $update_only_cred_key_option_enabled = 1;
               $oper_related_to_obj = "Credential key";
         }
     }

     if ($update_option_enabled) {
         if (defined($cred_key) && (!defined($input_file) && 
                                    (scalar(@input_hosts)==0))) {
             $opt_opstring = "change";
         } else {
             $opt_opstring = "add"; 
         }
     } elsif ($retrive_option_enabled) {
         $opt_opstring = "fetch"; 
     } elsif ($delete_option_enabled) {
         $opt_opstring = "delete"; 
     } else {
         $opt_opstring = ""; 
     }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : check_for_root_user()
# Calls         : -
# Called by     : main()
# Globals       : -
# Input Params  : user of command and command name
# Return Value  : -
#
# This function checks if user of command is root or not.
# If command's user is non-root then command will be failed.
#-----------------------------------------------------------------------------
sub check_for_root_user {
    my ($user, $CMD_PWD_MGMT) = @_;
    if ($user != 0) {
        fail("Must be root to use $CMD_PWD_MGMT command");
    }
}

#------------------------------------------------------------------------------
#
# Subroutine    : set_local_node()
# Calls         : -
# Called by     : main()
# Globals       : $localnode
# Input Params  : $localnode
# Return Value  : -
#
# This function takes reference of global var $localnode, it populates with
# local host name.
#-----------------------------------------------------------------------------
sub set_local_node {
    my ($localnode_ref) = @_;
    $$localnode_ref = hostname();
    chomp $$localnode_ref;
    my @temp = split(/\./, $$localnode_ref);
    $$localnode_ref = shift(@temp);
}

#-----------------------------------------------------------------------------
#
# Subroutine    : set_java_path()
# Calls         : -
# Called by     : main()
# Globals       : -
# Input Params  : -
# Return Value  : -
#
# This function find java path and set 
#-----------------------------------------------------------------------------
sub set_java_path {
    my $cmd = "command -v java 2> /dev/null";
    my $tmp = "";
    foreach my $node (@reachable_cls_nodes) {
        my ($exit, @output) = captureCmd($cmd, $node);
        if ($exit != 0) {
            fail("Unable to find Java installation, command requires Java ".
                 "version 1.7.0\n".
                 "to be installed on node $node");
        } else {
            $tmp = "@output";
            chomp $tmp;
            $java_path_on_node{'$node'} = $tmp;
            if ($node eq $localnode) {
                $JAVA = $tmp;
                $JAR = $tmp." ".$JAR;
            }
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : java_ver_check()
# Calls         : -
# Called by     : main()
# Globals       : -
# Input Params  : -
# Return Value  : -
#
# This function find java path and set
#-----------------------------------------------------------------------------
sub java_ver_check () {
    my $java_error = 0;
    my $msgtext = undef;
    foreach my $node (@reachable_cls_nodes) {
        my $cmd = "$java_path_on_node{'$node'} -d64 -version 2>&1";
        my $java_version = qx($cmd);
        if ($? eq 0) {
            my $op = qx($cmd |head -1 | awk '{print \$NF}'| cut -d '"' -f 2 | cut -d '_' -f 1);
            $java_version = $op;
            $op =~ s/\.//g;
            if ($op lt 170) {
                $java_error = 1;
            }
        } else {
            $java_error = 3;
        }
        if ($java_error eq 1) {
            $msgtext = "Command requires Java version 1.7.0".
                       " or higher than 1.7.0 to be ".
                       "installed on node $node.\n".
                       "Upgrade java version and ensure that ".
                       "JRE(Java Runtime Environment) of version 1.7.0 ".
                       "or greater\n".
                       "is installed on node $node";
        } elsif ($java_error eq 3) {
            $msgtext = "Command requires 64 bit Java to be ".
                       "installed on node $node.\n".
                       "Command \"$java_path_on_node{'$node'} ".
                       "-d64 -version\" does not give expected output.\n". 
                       "Ensure 64 bit Java and JRE(Java Runtime Environment) ".
                       "of version 1.7.0\nor greater is installed on node $node";
        }
        if (defined($msgtext)) {
            fail($msgtext, INCORRECT_JAVA_VERSION);
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : test_cmexec_on_all_nodes()
# Calls         : cmexec
# Called by     : main()
# Globals       : -
# Input Params  : List of node
# Return Value  : -
#
# This function test successful execution of cmexec command on all nodes.
#-----------------------------------------------------------------------------
sub test_cmexec_on_all_nodes {
    my (@node_list) = @_;
    foreach my $node (@node_list) {
        if ($sgeasyTool->test_cmexec($node) != 0) {
            if ($NON_FETCH_OP_SELECTED) {
                log_msg("Node $node is currently unreachable, unable to ".
                        "verify installed Serviceguard and \n".
                        "JRE(Java Runtime Environment) version ", 
                        WARNING);
            }
            push(@unreachable_cls_nodes, $node);
        }
    }
    @reachable_cls_nodes = difference_of_two_array(@node_list, @unreachable_cls_nodes);
}

#Internal function
sub validate_input_file_populate_host_list {
    my @hostlist_in_input_file = ();
    my $salt_set = 0;                 
    open (MYFILE, $input_file);
    while (<MYFILE>) {
        chomp;
        if ($_ =~ m/^HostName=/) {
            my @temp_host = split(/=/, $_);
            if (scalar(@temp_host) == 2) {
                if ((!grep {$_ =~ /$temp_host[1]/} @hostlist_in_input_file)) {
                    push(@hostlist_in_input_file, "$temp_host[1]");
                } else {
                    fail("Duplicate entry found for host name ".
                         "\"$temp_host[1]\"". 
                         " in input file \"$input_file\".\n".
                         "Remove duplicates and try again",
                         PARSING_ERROR);
                }
            } else {
                fail("Invalid text input from file \"$input_file\. ". 
                     "Refer command help ($CMD_PWD_MGMT -h) \n".
                     "or man page for correct input format",
                     PARSING_ERROR);
            }
        } else {
            if (!($_ =~ m/^UserName=/) && !($_ =~ m/^Password=/) && 
                !($_ =~ m/^Salt=/)) {
                fail("Invalid text input from file \"$input_file\. ". 
                     "Refer command help ($CMD_PWD_MGMT -h) \n".
                     "or man page for correct input format",
                     PARSING_ERROR);
            } elsif ($_ =~ m/^Salt=/) {
                 if ($salt_set == 1) {
                     fail("Invalid text input from file \"$input_file\. ". 
                          "Found more than one Credenetial key in file.\n".
                          "Remove extra Credenetial keys from file and ".
                          "try again", PARSING_ERROR);
                 }
                 $salt_set=1;
                 my @temp_host = split(/=/, $_);
                 if (scalar(@temp_host) == 2) {
                     if (-f "$SG_CONF_PATH/.credentials.xml") {
                         usage_error("Serviceguard Credential Store already ".
                                     "exist, update of Credential key can be \n".
                                     "done using command ".
                                     "\"$CMD_PWD_MGMT -U -S <Credential key>\"");
                     }
                 } else {
                     fail("Invalid text input from file \"$input_file\. ". 
                          "Refer command help ($CMD_PWD_MGMT -h) \n".
                          "or man page for correct input format",
                          PARSING_ERROR);
                 }
            }
        }
    }
    close (MYFILE);
    return (@hostlist_in_input_file);
} 

#-----------------------------------------------------------------------------
#
# Subroutine    : validate_input_file()
# Calls         : validate_input_file_populate_host_list()
# Called by     : main()
# Globals       : -
# Input Params  : -
# Return Value  : -
#
# This function validate and read input file and create input host list
#----------------------------------------------------------------------------- 
sub validate_input_file {
    if (defined($input_file)) {
        if (!(-f $input_file)) {
            usage_error("File \"$input_file\" does not exist");
        }
        @input_hosts = validate_input_file_populate_host_list();
        if (@input_hosts == 0) {
            usage_error("Atleast one VMware vCenter/ESXi Host ".
                        "must be specified in input file \"$input_file\"");
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : check_for_duplicate_entries()
# Calls         : fail()
# Called by     : main()
# Globals       : None
# Input Params  : None
# Return Value  : -
#
# This function does checking for duplicate entries with same option.
#-----------------------------------------------------------------------------
sub check_for_duplicate_entries {
    my ($input_name, $input_ref) = @_;
    my @input = @$input_ref;
    if (@input) {
        my %seen = ();
        my @input_tmp = @input;
        @input = ();
        foreach my $elem (@input_tmp) {
            fail("Duplicate $input_name $elem found in input to command")
            if $seen{ $elem }++;
            push @input, $elem;
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : populate_cluster_host_list()
# Calls         : -
# Called by     : -
# Globals       : @cls_hosts,
# Input Params  : None
# Return Value  : -
#
# This function create list of host/vCenter IP configured in a cluster.
#-----------------------------------------------------------------------------
sub populate_cluster_host_list {
    # If nodes are not specified add cluster nodes if cluster configured
    # otherwise localnode.
    my $cmd = "$SGBIN/cmviewcl -v -f line -s config";
    my ($exit, @output) = captureCmd($cmd, $localnode);
    if ($exit == 0) {
        #node:bermese|name=bermese
        foreach my $line (@output) {
            if (($line =~ /^node:(.+)\|esx_name=/) || 
                ($line =~ /^vcenter_name=/)) {
                my @parts = split('=',$line);
                chomp $parts[1];
                if ((!grep {$_ =~ /$parts[1]/} @cls_hosts)) {
                    push(@cls_hosts, $parts[1]);
                }
            }
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : create_node_list()
# Calls         : -
# Called by     : -
# Globals       : $sgeasyTool, $SGBIN, $localnode, @cls_nodes
# Input Params  : None
# Return Value  : -
#
# This function create guest node list if cluster exist then adding all
# configured node in a cluster otherwise local node only
#-----------------------------------------------------------------------------
sub create_node_list {
    # If nodes are not specified add cluster nodes if cluster configured
    # otherwise localnode.
    my $cmd = "$SGBIN/cmviewcl -f line -s config";
    my ($exit, @output) = captureCmd($cmd, $localnode);
    if ($exit != 0) {
        push(@cls_nodes, $localnode);
        push(@reachable_cls_nodes, $localnode);
    } else {
        $CLUSTER_CONFIGURED=1;
        #node:bermese|name=bermese
        foreach my $line (@output) {
            if ($line =~ /^node:(.+)\|name=/) {
                  my @parts = split('=',$line);
                  chomp $parts[1];
                  push(@cls_nodes, $parts[1]);
                  push(@reachable_cls_nodes, $parts[1]);
            }
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : check_sg_version_on_nodes()
# Calls         : -
# Called by     : -
# Globals       : $SGBIN, @reachable_cls_nodes,
# Input Params  : None
# Return Value  : -
#
# It checks Serviceguard version 12.00.40 or later version must be installed
# on all nodes.
#-----------------------------------------------------------------------------
sub check_sg_version_on_nodes {
    # All the nodes should be on the same SG version.
    my $sg_version;
    foreach my $node_name (@reachable_cls_nodes) {
        my $cmd = "$SGBIN/cmversion 2> /dev/null";
        my ($exit, $version) = captureCmd($cmd, $node_name);
        if ($exit != 0) {
            log_msg("Unable to get Serviceguard version from $node_name",
                    WARNING);
        }
        if (!$sg_version) {
            $sg_version = $version;
            my @sgversion = split /\./, $sg_version;
            if ((scalar($sgversion[1]) < $SGLX12) && 
                (scalar($sgversion[2]) < $SGLX_MINOR_VER_WITH_VMFS_ENABLED)) {
                fail("$node_name must have Serviceguard version ".
                     "A.$SGLX12.$SGLX_MINOR_VER_WITH_VMFS_ENABLED or higher");
            }
        }
        elsif ($version ne $sg_version) {
            fail("All the nodes must have the same version of ".
                 "Serviceguard installed");
        }
    }
}

#Internal function: Create lock file on all reachable cluster's node.
sub check_and_create_lck_file_on_nodes {
    my $each_node_ret = 0;
    foreach my $node_name (@reachable_cls_nodes) {
        my $cmd = "ls -l $SGRUN/$CMD_PWD_MGMT.lck 2>/dev/null";
        my ($exit,) = captureCmd($cmd, $node_name);
        if ($exit == 0) {
            $skip_delete_lock_file = 1;
            fail("One more instance of \"$CMD_PWD_MGMT\" command ".
                 "is running on $node_name");
        }
    }
    open(my $fh, '>', "$SGRUN/$CMD_PWD_MGMT.lck") or 
        fail("Failed to create $SGRUN/$CMD_PWD_MGMT.lck on $localnode: $!\n");
    close $fh;
    runCmd("chmod 000 $SGRUN/$CMD_PWD_MGMT.lck 2>/dev/null");
    foreach my $node_name (@reachable_cls_nodes) {
        if ($node_name ne $localnode) {
            my $cmd = "$SGBIN/cmcp $SGRUN/$CMD_PWD_MGMT.lck ".
                      "$node_name:$SGRUN/$CMD_PWD_MGMT.lck"." &>/dev/null";
            ($each_node_ret)=runCmd($cmd);
            if ($each_node_ret != 0) {
                log_msg("Node $node_name is currently unreachable, unable ".
                        "to create $SGRUN/$CMD_PWD_MGMT.lck file ", 
                        WARNING);
            }
        }
    }
}

#Internal function: Clears lock file on all reachable cluster's node.
sub lck_file_cleanup_on_nodes {
    foreach my $node_name (@reachable_cls_nodes) {
        my $cmd = "unlink $SGRUN/$CMD_PWD_MGMT.lck 2>/dev/null";
        my ($exit,) = captureCmd($cmd, $node_name);
        if ($exit != 0) {
            log_msg("Node $node_name is currently unreachable, unable ".
                    "to remove $SGRUN/$CMD_PWD_MGMT.lck file.\n".
                    "Remove $SGRUN/$CMD_PWD_MGMT.lck file once ".
                    "node will be accessible", WARNING);
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : runCmd()
# Calls         : fail()
# Called by     : -
# Globals       : -
# Input Params  : command 
# Return Value  : -
#
# This function execute command on local system and returns exit code of cmd
#-----------------------------------------------------------------------------
sub runCmd {
    my ($cmd) = @_;
    my $exitCode = 0;
    system("$cmd");
    if ( $? == -1 ) {
        fail("Cannot run command $cmd : $!");
    } else {
        $exitCode = ($? >> 8);
    }
    return $exitCode;
}

#-----------------------------------------------------------------------------
#
# Subroutine    : captureCmd()
# Calls         : -
# Called by     : -
# Globals       : $SGBIN, $localnode
# Input Params  : command, node on which command need to execute  
# Return Value  : exit code and output of command
#
# This function execute command on any given node and returns exit code of cmd
# and output of command
#-----------------------------------------------------------------------------
sub captureCmd {
    my ($cmd, $node, $redirecting_exception_to_file) = @_;
    my @output = ();
    my $temp = undef;
    my $my_cmd;
    if ( $localnode eq $node ){
        if (defined($redirecting_exception_to_file) && 
            ($redirecting_exception_to_file)) {
            $my_cmd = $cmd . " |"; 
        } else {
            $my_cmd = $cmd . " 2>&1 |"; 
        }
    } else {
        $my_cmd = "$SGBIN/cmexec " . $node . " " . $cmd. "  2>&1 |"; 
    }
    open (CMD, $my_cmd) or fail("Cannot run command $my_cmd : $!"); 
    while ($temp=<CMD>) {
        push(@output, $temp);
        last if eof(CMD);
    }
    close(CMD);
    my $exitCode = ($? >> 8);        
    if ($debug_option_enabled) {
        print "<captureCmd> cmd: $my_cmd\texitCode: $exitCode\n";
    }
    return ($exitCode, @output);
}

#Internal function: This will set permisson on cred db and key file to 
#read only 
sub setFilePermission {
    my ($file, $fileType) = @_;
    my $permisson = "644";
    if ($file =~ m/.lck/) {
        $permisson = "000";
    } elsif (($file =~ m/.credentials.xml$/) || 
             ($file =~ m/.sdata$/)) { 
        $permisson = "400";
    }
    my $cmd = "chmod $permisson $file 2>&1>/dev/null";
    runCmd($cmd);
}

#Internal function: Copy file and set permisson
sub copy_file {
    my ($file1, $file2) = @_;
    copy("$file1", "$file2") or fail("Copy of file $file1 failed: $!");
    setFilePermission("$file2");
}

#Internal function: Move file and set permisson
sub move_file {
    my ($file1, $file2) = @_;
    move("$file1", "$file2") or fail("Move failed for $file1: $!");
    setFilePermission("$file2");
}

#-----------------------------------------------------------------------------
#
# Subroutine    : backup_of_cred_data_on_local_node()
# Calls         : -
# Called by     : -
# Globals       : $SG_CONF_PATH, $CRED_KEY_FILE, $localnode
# Input Params  : -
# Return Value  : -
#
# This function will take backup of existing cred key and cred store files on
# local node.
#-----------------------------------------------------------------------------
sub backup_of_cred_data_on_local_node {
    if (-f "$SG_CONF_PATH/.credentials.xml.bak") {
        unlink("$SG_CONF_PATH/.credentials.xml.bak") or
               log_msg("Unable to clean up older temporary Serviceguard ".
                       "Credential Store(SCS) backup file: $!\n", WARNING);
    }
    if (-f "$SG_CONF_PATH/.credentials.xml") {
        copy_file("$SG_CONF_PATH/.credentials.xml",
                  "$SG_CONF_PATH/.credentials.xml.bak");
        $c_db = 1;
    }
    if (-f "$CRED_KEY_FILE.bak") {
        unlink("$CRED_KEY_FILE.bak") or
               log_msg("Unable to clean up older temporary Credential ".
                       "key backup file: $!", WARNING);
    }
    if (-f "$CRED_KEY_FILE") {
        copy_file("$CRED_KEY_FILE", "$CRED_KEY_FILE.bak");
        $c_key = 1;
    }
    $backup_of_cred_data_done=1;
}

#-----------------------------------------------------------------------------
#
# Subroutine    : rollback_of_cred_data_on_local_node()
# Calls         : log_msg()
# Called by     : fail()
# Globals       : $SG_CONF_PATH, $CRED_KEY_FILE, $localnode, 
# Input Params  : - 
# Return Value  : -
#
# This function will do rollback of existing cred key and cred store files on
# local node, before excution of any operation.
#-----------------------------------------------------------------------------
sub rollback_of_cred_data_on_local_node {
    if ($c_db) {
        if (-f "$SG_CONF_PATH/.credentials.xml.bak") {
            move_file("$SG_CONF_PATH/.credentials.xml.bak",
                      "$SG_CONF_PATH/.credentials.xml");
        }
    } else {
        if (-f "$SG_CONF_PATH/.credentials.xml") {
            unlink("$SG_CONF_PATH/.credentials.xml") or
                  log_msg("Unable to clean up temporarily created ".
                          "Serviceguard Credential Store(SCS) file: $!.\n".
                          "If cluster exists resync SCS from other cluster ".
                          "nodes using the ".
                          "\"$CMD_PWD_MGMT --sync -n <remote node>\"\n".
                          "else recreate SCS again", ERROR);
            if (-f "$SGRUN/$CMD_PWD_MGMT.lck") {
                lck_file_cleanup_on_nodes();
            }
            exit(COMMAND_FAILURE_ERROR);
        }
    }
    if ($c_key) {
        if (-f "$CRED_KEY_FILE.bak") {
            move_file("$CRED_KEY_FILE.bak", "$CRED_KEY_FILE");
        }
    } else {
        if (-f "$CRED_KEY_FILE") {
            unlink("$CRED_KEY_FILE") or 
                  log_msg("Unable to clean up temporarily created ".
                          "Credential key file: $!.\n".
                          "Make sure delete the file ".
                          "$CRED_KEY_FILE ".
                          "If cluster exists resync SCS from other cluster ".
                          "nodes using the ".
                          "\"$CMD_PWD_MGMT --sync -n <remote node>\"\n".
                          "else recreate SCS again", ERROR);
            if (-f "$SGRUN/$CMD_PWD_MGMT.lck") {
                lck_file_cleanup_on_nodes();
            }
            exit(COMMAND_FAILURE_ERROR);
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : cleanup_of_backuped_cred_data_on_local_node()
# Calls         : unlink
# Called by     : main()
# Globals       : $SG_CONF_PATH, $CRED_KEY_FILE,
# Input Params  : -
# Return Value  : -
#
# This function cleanup of all backuped file which were created before 
# perfoming any operation on local node.
#-----------------------------------------------------------------------------
sub cleanup_of_backuped_cred_data_on_local_node {
    if (-f "$SG_CONF_PATH/.credentials.xml.bak") {
        unlink("$SG_CONF_PATH/.credentials.xml.bak");
    }
    if (-f "$CRED_KEY_FILE.bak") {
        unlink("$CRED_KEY_FILE.bak");
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : copy_cred_data_from_remote_node()
# Calls         : -
# Called by     : -
# Globals       : $SGBIN, $SG_CONF_PATH, $CRED_KEY_FILE, $localnode
# Input Params  : node from which file will be sync-ed.
# Return Value  : -
#
# This function will copy cred db and key files from remote node to local
# node.
#-----------------------------------------------------------------------------
sub copy_cred_data_from_remote_node {
    my ($remote_node) = @_;
    my $file = "$SG_CONF_PATH/.credentials.xml";
    my $cmd = "ls -l $file 2>/dev/null";
    my ($exit,) = captureCmd($cmd, $remote_node);
    if ($exit == 0) {
        my $cmd = "$SGBIN/cmcp $remote_node:$SG_CONF_PATH/.credentials.xml ".
                  "$SG_CONF_PATH/.credentials.xml"." &>/dev/null";
        if (runCmd($cmd) != 0) {
            fail("Remote node $remote_node is currently unreachable, ".
                 "unable to $operation Serviceguard Credential Store(SCS) ".
                 "from target node");
        }
    } else {
        fail("Either Serviceguard Credential Store(SCS) does not exist ".
             "on target node $remote_node or it is currently unreachable.\n".
             "Unable to $operation SCS from remote node $remote_node");
    }
    
    $file = "$CRED_KEY_FILE";
    $cmd = "ls -l $file 2>/dev/null";
    ($exit,) = captureCmd($cmd, $remote_node);
    if ($exit == 0) {
        my $cmd = "$SGBIN/cmcp $remote_node:$CRED_KEY_FILE ".
                  "$CRED_KEY_FILE"." &>/dev/null";
        if (runCmd($cmd) != 0) {
            fail("Remote node $remote_node is currently unreachable, unable ".
                 "to $operation Credential key from remote node");
        }
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : sync_file_from_a_node()
# Calls         : -
# Called by     : -
# Globals       : $SGBIN, $localnode
# Input Params  : command, node on which command need to execute  
# Return Value  : exit code and output of command
#
# This function execute command on any given node and returns exit code of cmd
# and output of command
#-----------------------------------------------------------------------------
sub sync_file_from_a_node {
    my ($node_to_copy_cred_db) = @_;
    copy_cred_data_from_remote_node($node_to_copy_cred_db);
    #If cluster exist we do addition check to ensure all configured hosts in 
    #a cluster must be present in Serviceguard Credential Store(SCS) on 
    #remote node, before proceed for sync database from that node to 
    #local node.
    my $exitcode = validate_cls_db_in_remote_node($node_to_copy_cred_db);
    if ($exitcode == 0) {
        $exitcode = validate_credential();
    }
    return($exitcode);
}

#-----------------------------------------------------------------------------
#
# Subroutine    : delete_file_from_remote_nodes()
# Calls         : runCmd(),
# Called by     : main()
# Globals       : $SGBIN, $localnode
# Input Params  : file type
# Return Value  : exit code
#
# This function delete Serviceguard Credential Store(SCS) or Credential key 
# from all nodes in a cluster apart from local node.
#-----------------------------------------------------------------------------
sub delete_file_from_remote_nodes {
    my ($file) = @_;
    my $failed = 0;
    my $object = "Serviceguard Credential Store(SCS)";
    if ($file eq "credential_key") {
        $file = ".sdata";
        $object = "Credential key";
    } elsif ($file eq "credential_database") {
        $file = ".credentials.xml";
    } else {
        fail("No file to distribute");
    }
    my @failed_node_list = ();
    foreach my $node (@reachable_cls_nodes) {
        if ($node ne $localnode) {
            my $cmd = "$FIND_CMD $SG_CONF_PATH -name ".
                      "$file -delete"." &>/dev/null";
            my ($exitcode, @output) = captureCmd($cmd,
                                                 $node);
            if ($debug_option_enabled) {
                print "<delete_file_from_remote_nodes> cmd: ".
                      "$cmd\texitcode: $exitcode\n";
            }
            if ($exitcode != 0) {
                push(@failed_node_list, $node);
                $failed = 1;
                log_msg("Node $node is currently unreachable, ".
                        "unable to peform $operation operation ".
                        "on $object", WARNING);
            }
        }
    }
    foreach my $node (@unreachable_cls_nodes) {
        $failed = 1;
        push(@failed_node_list, $node);
        log_msg("Node $node is currently unreachable, ".
                "unable to perform $operation operation on $object",
                WARNING);
    }
    if ($failed == 1) {
         # This need to be pushed to syslog
         my $text_msg = "Nodes [ ".join(", ",@failed_node_list)." ] ".
                     "are unreachable, to ensure consistency of \n".
                     "Serviceguard Credential Store(SCS) across cluster ".
                     "nodes execute \"$CMD_PWD_MGMT --sync -n $localnode\" \n".
                     "command on unreachable nodes once they will be ".
                     "accessible";
         $sgeasyTool->only_syslog("WARNING: $text_msg");
         log_msg($text_msg, WARNING);
    }
    return;
}

#-----------------------------------------------------------------------------
#
# Subroutine    : distribute_file_to_all_nodes()
# Calls         : runCmd(),
# Called by     : main()
# Globals       : $SGBIN, $localnode
# Input Params  : file type , list of node names to distribute a file
# Return Value  : exit code
#
# This function copies Serviceguard Credential Store(SCS) or Credential key 
# to all other nodes in a cluster.
#-----------------------------------------------------------------------------
sub distribute_file_to_all_nodes {
    my ($file, $skip_print_final_warning_msg) = @_;
    my $failed = 0;
    my $object = "Serviceguard Credential Store(SCS)";
    if ($file eq "credential_key") {
        $file = "$CRED_KEY_FILE";
        $object = "Credential key";
    } elsif ($file eq "credential_database") {
        $file = "$SG_CONF_PATH/.credentials.xml";
    } else {
        fail("No file to distribute");
    }
    my @failed_node_list = ();
    foreach my $node (@reachable_cls_nodes) {
        if ($node ne $localnode) {
            my $cmd = "$SGBIN/cmcp $file ".
                      "$node:$file"." &>/dev/null";
            my $exitcode = runCmd($cmd);
            if ($debug_option_enabled) {
                print "<distribute_file_to_all_nodes> cmd: ".
                      "$cmd\texitcode: $exitcode\n";
            }
            if ($exitcode != 0) {
                push(@failed_node_list, $node);
                $failed = 1;
                if (!defined($skip_print_final_warning_msg)) {
                     log_msg("Node $node is currently unreachable, ".
                             "unable to perform $operation on $object", 
                             WARNING);
                }
            }
        }
    }
    foreach my $node (@unreachable_cls_nodes) {
        $failed = 1;
        push(@failed_node_list, $node);
        if (!defined($skip_print_final_warning_msg)) {
             log_msg("Node $node is currently unreachable, unable ".
                     "to perform $operation on $object", 
                     WARNING);
        }
    }
    if ($failed == 1) {
         if (!defined($skip_print_final_warning_msg)) {
             # This need to be pushed to syslog
             my $text_msg = "Nodes [ ".join(", ",@failed_node_list)." ] ".
                     "are unreachable, to ensure consistency of \n".
                     "Serviceguard Credential Store(SCS) across cluster ".
                     "nodes execute \"$CMD_PWD_MGMT --sync -n $localnode\" \n".
                     "command on unreachable nodes once they will be ".
                     "accessible";
             $sgeasyTool->only_syslog("WARNING: $text_msg");
             log_msg($text_msg, WARNING);
         }
    }
    return;
}

#------------------------------------------------------------------------------
#
# Subroutine    : construct_cmd_with_addition_options()
# Calls         : -
# Called by     : -
# Globals       : $SGBIN, $localnode
# Input Params  : command, node on which command need to execute  
# Return Value  : exit code and output of command
#
# This function execute command on any given node and returns exit code of cmd
# and output of command
#-----------------------------------------------------------------------------
sub construct_cmd_with_addition_options {
    my ($cmd) = @_;
    if ($z_option_enabled) {
        $cmd = "$cmd"." -z";
    } else {
        if ($fline_option_enabled) {
            $cmd = "$cmd"." -fline";
        }
    }
    return ($cmd);
}

#Internal function: Remove all duplicates from given array and return 
#updated array
sub remove_duplicates_from_array {
    my ($uniq_list_ref) = @_;
    my %unique = ();
    foreach my $item (@$uniq_list_ref)
    {
        $unique{$item} ++;
    }
    @$uniq_list_ref = keys %unique;
}

#Internal function: to find one array is subset of another array or not
sub isSubset {
    my ($cluster_hosts_set, $input_hosts_set) = @_;
    my %hash = ();
    @hash{@$cluster_hosts_set} = undef;  # add a hash key for each element 
                                         # of @$cluster_hosts_set
    delete @hash{@$input_hosts_set};     # remove all keys for elements of 
                                         # @$input_hosts_set
    return !%hash;                       # return false if any keys are left 
                                         # in the hash
}

#Internal function: to return array of elements which are not 
#common across two given arrays
sub difference_of_two_array {
    my (@array1, @array2) = @_;
    my %diff = ();
    foreach my $temp (@array1, @array2) { 
        $diff{lc $temp}++ 
    }
    return(grep { $diff{$_} == 1 } keys %diff);
}

#Internal function: to return array of elements which are 
#common across two given arrays
sub intersection_of_two_array {
    my (@array1, @array2) = @_;
    my %isect = ();
    foreach my $temp (@array1, @array2) { 
        $isect{lc $temp}++ 
    }
    return(grep { $isect{$_} == 2 } keys %isect);
}

#------------------------------------------------------------------------------
#
# Subroutine    : validate_cls_db_in_remote_node()
# Calls         : fail()
# Called by     : -
# Globals       : %hash_of_cls_host_short_nm_cred_db
# Input Params  : remote node name  
# Return Value  : -
#
# This function will check that in Serviceguard Credential Store(SCS) on 
# remote node, has entries of all configured hosts in a cluster. 
#-----------------------------------------------------------------------------
sub validate_cls_db_in_remote_node {
    my ($remote_node) = @_;
    my $exitcode = create_hash_maps_of_cluster_hosts_in_cred_db();
    if ($exitcode == 0) {
        my $size = keys %hash_of_cls_host_short_nm_cred_db;
        if ($size < scalar(@cls_hosts)) {
            fail("Some of hosts configured in cluster do not have entry ".
                 "in Serviceguard Credential Store(SCS) present on ".
                 "node target $remote_node.\n".
                 "Update all configured hosts into SCS, ".
                 "before perform sync operation using node $remote_node.");
        }
    }
    return ($exitcode);
}

#------------------------------------------------------------------------------
#
# Subroutine    : validate_credential()
# Calls         : fail()
# Called by     : -
# Globals       : $CLUSTER_CONFIGURED, @cls_hosts, @hosts_exist_in_db
# Input Params  : - 
# Return Value  : -
#
# This function will verify credential of all configured hosts
#-----------------------------------------------------------------------------
sub validate_credential {
    my $exit_code = 0;
    if ($CLUSTER_CONFIGURED) {
        $exit_code = verify_credential_for_hosts(@cls_hosts);
    }
    return $exit_code;
}

#-----------------------------------------------------------------------------
#
# Subroutine    : read_file()
# Calls         : -
# Called by     : -
# Globals       : -
# Input Params  : File path to read content
# Return Value  : a variable which has whole file content
#
# This function read a file and add content into a variable and return it.
#-----------------------------------------------------------------------------
sub read_file {
    my ($filename) = @_;
    open (FILE, "<", $filename);
    my $var_to_get_all_file_content = "";
    while (<FILE>) {
        $var_to_get_all_file_content = $var_to_get_all_file_content ."$_";
    }
    close FILE;
    return($var_to_get_all_file_content);
}

#-----------------------------------------------------------------------------
#
# Subroutine    : perform_pwd_mgmt()
# Calls         : construct_cmd_with_addition_options(), 
#                 sync_cred_data_from_a_node(), read_file(),
#                 create_hash_maps_of_cluster_hosts_in_cred_db(),
#                 
# Called by     : main()
# Globals       : $sync_cred_file_enabled, $cred_key, $update_option_enabled,
#                 @input_hosts, @cls_nodes, $delete_option_enabled, $SGRUN,
#                 $CMD_PWD_MGMT,
# Input Params  : command, node on which command need to execute  
# Return Value  : exit code
#
# This function try to perform all credential related operation like "update"
# "retrive" and "delete" of host. It also perform update of Credential key.
# If command fails returns non-zero value otherwise return zero.
#-----------------------------------------------------------------------------
sub perform_pwd_mgmt {
    if ($sync_cred_file_enabled) { 
        return(sync_file_from_a_node($node_with_updated_cred_db));
    } else {
        my $cmd = "$JAR $JAVA_TOOL -o $opt_opstring";
        if ($update_option_enabled || 
            $delete_option_enabled) {
            if (!$update_only_cred_key_option_enabled) {
                if (defined($input_file)) {
                    $cmd = "$cmd -F $input_file"; 
                } else {
                    foreach my $host_name (@input_hosts) {
                        $cmd = "$cmd"." -H $host_name";
                    }
                }
            } else {
                $cmd = "$cmd -S \"$cred_key\""; 
            }
        }
        $cmd=construct_cmd_with_addition_options($cmd);
        $cmd = $cmd." 2>$SGRUN/.$CMD_PWD_MGMT.error";
        my $exitcode = runCmd($cmd);
        if ($debug_option_enabled) {
            print "<perform_pwd_mgmt> cmd: $cmd\texitcode: $exitcode\n";
        }
        if (($exitcode != 0) && (-f "$SGRUN/.$CMD_PWD_MGMT.error")) {
            $sgeasyTool->only_syslog(read_file("$SGRUN/.$CMD_PWD_MGMT.error"));
        }
        return($exitcode);
   }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : verify_credential_for_hosts()
# Calls         : -
# Called by     : -
# Globals       : $sgeasyTool, $JAVA_TOOL, $JAR, $localnode
# Input Params  : list if hosts (for each of them credential need to verify)
# Return Value  : exit code
#
# This function try to connect using stored credential values of each host in
# Serviceguard Credential Store(SCS) and return not zero value if for one of them connect
# command fails otherwise return zero.
#-----------------------------------------------------------------------------
sub verify_credential_for_hosts {
    my (@hosts) = @_;
    my $cmd = "$JAR $JAVA_TOOL -o connect";
    my $final_cmd = undef;
    my $last_error_code = 0;
    my $exitcode = 0;
    my @output = ();
    my $redirecting_exception_to_file = 1;
    foreach my $host_name (@hosts) {
        $final_cmd = "$cmd"." -H $host_name";
        $final_cmd = construct_cmd_with_addition_options($final_cmd);
        ($exitcode, @output) = captureCmd($final_cmd.
                                          " 2>$SGRUN/.$CMD_PWD_MGMT.error",
                                          $localnode, 
                                          $redirecting_exception_to_file);
        if ($debug_option_enabled) {
            print "<verify_credential_for_hosts> cmd: $cmd\texitcode: $exitcode\n";
        }
        if ($exitcode != 0) { 
            my $output_msg = "@output";
            $last_error_code=$exitcode;
            print STDERR "$output_msg";
            if ((-f "$SGRUN/.$CMD_PWD_MGMT.error")) {
                $sgeasyTool->only_syslog(read_file("$SGRUN/.$CMD_PWD_MGMT.error"));
                if (($exitcode == FILE_CORRUPTED_ERROR) || 
                    ($exitcode == UNDEFINED_ERROR)) {
                    return ($exitcode);
                }
            }
        } 
    }
    if ($last_error_code == 0) {
        return ($exitcode);
    } else {
        return $last_error_code;
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : is_exist_in_hash_of_hosts()
# Calls         : -
# Called by     : perform_pwd_mgmt()
# Globals       : -
# Input Params  : -
# Return Value  : 0 | 1
#
# This function will search a give short name/FQDN or IP from given 
# hash map.
#-----------------------------------------------------------------------------
sub is_exist_in_hash_of_hosts {
    my ($host_to_search, %hash_of_hosts) = @_;
    if (exists $hash_of_hosts{$host_to_search}) {
        return 1;
    } else {
        return 0;
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : create_hash_maps_of_cluster_hosts_in_cred_db()
# Calls         : captureCmd()
# Called by     : perform_pwd_mgmt()
# Globals       : -
# Input Params  : -
# Return Value  : hash map of hosts in Serviceguard Credential Store(SCS)
#
# This function will return a hash map that will contain all host's short name, 
# fqdn and IP address of configured cluster
#-----------------------------------------------------------------------------
sub create_hash_maps_of_cluster_hosts_in_cred_db {
    my $redirecting_exception_to_file = 1;
    my $cmd = "$JAR $JAVA_TOOL -o fetch -fline ".
              "2>$SGRUN/.$CMD_PWD_MGMT.error";
    my ($exitcode, @output) = captureCmd($cmd, $localnode,
                                         $redirecting_exception_to_file);
    if ($debug_option_enabled) {
         print "<create_hash_maps_of_cluster_hosts_in_cred_db> ".
               "cmd: $cmd\texitcode: $exitcode\n";
    }
    if ($exitcode == 0) {
        foreach my $cls_host (@cls_hosts) { 
            if ((scalar(@output) > 0)) {
                foreach my $line (@output) {
                    if ($line =~ m/$cls_host/) {
                        my @host_line = split(/\|/, $line);
                        foreach my $part_of_line (@host_line) {
                            my @key_pair_value = split(/\:/, $part_of_line);
                            if (($key_pair_value[0] =~ m/host_ip/) ||
                                    ($key_pair_value[0] =~ m/host_name/) ||
                                    ($key_pair_value[0] =~ m/host_shortname/)) {
                                    $hash_of_cls_host_in_cred_db{$key_pair_value[1]} =
                                    $key_pair_value[0];
                                    if ($key_pair_value[0] =~ m/host_shortname/) {
                                        $hash_of_cls_host_short_nm_cred_db{$key_pair_value[1]} = 
                                        $key_pair_value[0];
                                    }  
                            } else {
                                #Skip any entry other than host's
                                #short/fqdn name and IP
                                #Like "user_name=root" need to be skipped
                            } 
                        }
                    }
                }
            }
        } 
    } else {
        if ((-f "$SGRUN/.$CMD_PWD_MGMT.error")) {
            my $output_msg = "@output";
            print STDERR "$output_msg";
            $sgeasyTool->only_syslog(read_file("$SGRUN/.$CMD_PWD_MGMT.error"));
        }
    }
    return($exitcode);
}

#-----------------------------------------------------------------------------
#
# Subroutine    : check_credential_db_existence()
# Calls         : fail(), log_msg()
# Called by     : main()
# Globals       : -
# Input Params  : -
# Return Value  : hash map of hosts in Serviceguard Credential Store(SCS)
#
# This function will do some prechecks like db existence so that if there 
# db is not there then there is no point to allow delete or retrieval 
# operation.
#-----------------------------------------------------------------------------
sub check_credential_db_existence {
    if (!(-f "$SG_CONF_PATH/.credentials.xml")) {
        if (!($z_option_enabled || $fline_option_enabled)) {
            if ($delete_option_enabled) {
                print("$CMD_PWD_MGMT: Deleting entries from ".
                      "$oper_related_to_obj on node [$localnode]\n");
            } else {
                print("$CMD_PWD_MGMT: Retrieving entries from ".
                      "$oper_related_to_obj on node [$localnode]\n");
            }
        }
        log_msg("Serviceguard Credential Store(SCS) does not exist", ERROR);
        fail("Failed to perform $operation operation");
    }
}

#-----------------------------------------------------------------------------
#
# Subroutine    : precheck_for_only_sync_operation()
# Calls         : fail(), 
# Called by     : main()
# Globals       : -
# Input Params  : -
# Return Value  : -
#
# This function will do some prechecks so that if there is problem then fail
# the sync operation in early stage.
#-----------------------------------------------------------------------------
sub precheck_for_only_sync_operation {
    print("$CMD_PWD_MGMT: Synchronizing $oper_related_to_obj ".
          "on node [ $localnode ] from remote node ".
          "[ $node_with_updated_cred_db ]\n");
    if ($sync_cred_file_enabled &&
        ($localnode eq $node_with_updated_cred_db)) {
        fail("For SCS synchronization source cannot be same as ".
             "execution node.\nSynchronize with other cluster node");
    }
    if ($CLUSTER_CONFIGURED) {
        if (($sync_cred_file_enabled) &&
            (!grep {$_ =~ /$node_with_updated_cred_db/} @cls_nodes)) {
            fail("Remote node $node_with_updated_cred_db is not configured ".
                 "in a cluster, unable to $operation ".
                 "Serviceguard Credential Store(SCS)");
        }
    } else {
        if ($sync_cred_file_enabled) {
            fail("Cluster is not configured, unable to $operation ".
                 "Serviceguard Credential Store(SCS)");
        }
    }
}

#Internal function: create new Credential key on local node
sub create_credential_key_file {
    open(my $fh, '>', "$CRED_KEY_FILE") or
        fail("Failed to create file "."$CRED_KEY_FILE");
        print $fh "$cred_key";
    close $fh;
    setFilePermission($CRED_KEY_FILE);
}

sub log_err_post_operation_and_exit {
    my ($exitcode) = @_;
    if ($exitcode == FILE_CORRUPTED_ERROR) {
        log_msg("If cluster exists resync SCS from other cluster ".
                "nodes using the \n".
                "\"$CMD_PWD_MGMT --sync -n <remote node>\" ".
                "else recreate SCS again", ERROR);
        if (!$sync_cred_file_enabled) {
            $backup_of_cred_data_done = 0;
        }
    } else {
        if ($exitcode == UNDEFINED_ERROR) {
            log_msg("Unexpected error occured in $operation operation. ". 
                    "If cluster exists resync SCS from other cluster ".
                    "nodes using the \n".
                    "\"$CMD_PWD_MGMT --sync -n <remote node>\" ".
                    "else recreate SCS again", ERROR);
        }
    }
    fail("Failed to perform $operation operation", $exitcode);
}

#------------------------------------------------------------------------------
#
# Subroutine    : main()
# Calls         : check_for_root_user(), set_local_node(), parse_args(),
#                 check_for_duplicate_entries(), set_java_path(),
#                 test_cmexec_on_all_nodes(), java_ver_check(),
#                 check_sg_version_on_nodes(), populate_cluster_host_list(),
#                 validate_input_file(), check_and_create_lck_file_on_nodes(),
#                 perform_pwd_mgmt(), verify_credential_for_hosts(),
#                 isSubset(), lck_file_cleanup_on_nodes().
#
# Called by     : -
# Globals       : @cls_hosts, @input_hosts, $SG_CONF_PATH, $CMD_PWD_MGMT
#                 $NON_FETCH_OP_SELECTED, $CLUSTER_CONFIGURED, $CRED_KEY_FILE,
#                 $localnode,
# Input Params  : -
# Return Value  : -
#
# This Main function which performs all checks to commandline arguments/opts
# as well as Serviceguard version on all nodes, tests cmexec command on
# all nodes then finally update/delete or retrieve credential of ESXi/vCenter 
# appropriatly in Serviceguard Credential Store(SCS).
#-----------------------------------------------------------------------------
sub main {
    my $program_exit_code = 0;

    #This to handle if user interrupted command by pressing cntl-c.
    $SIG{'INT'} = sub {fail("User interupted, exiting")};

    # only root can run this
    check_for_root_user($<, $0);

    # Find local hostname and set in global variable "$localnode"
    set_local_node(\$localnode);

    $sgeasyTool->only_syslog("Started $CMD_PWD_MGMT on node [ $localnode ]");

    # To ensure avoid printing any old exceptions message
    # in syslog, delete the cmvmusermgmt.error file if 
    # exists
    if (-f "$SGRUN/.$CMD_PWD_MGMT.error") {
        unlink("$SGRUN/.$CMD_PWD_MGMT.error");
    }

    # To parase and set flags and values based on operation.
    parse_args();

    # For delete and retrival option this check need to be skipped
    # For update operation only verify entries and format in file
    if ($update_option_enabled || $delete_option_enabled) {
        validate_input_file();
    }

    # this function will help to fail the command in early 
    # stages as retrieval/delete operation can not 
    # performed if db is not exist.
    if (($delete_option_enabled || 
         $retrive_option_enabled)) { check_credential_db_existence(); }

    # Set @cls_nodes and @reachable_cls_nodes
    create_node_list();

    # Some pre check can be done only for sync operation.
    # this function will help to fail the command in 
    # stages of sync operation.
    if ($sync_cred_file_enabled) { precheck_for_only_sync_operation() };

    if ($CLUSTER_CONFIGURED) {
        $sgeasyTool->only_syslog("Running $CMD_PWD_MGMT on node(s) " .
                                 "[ ".join(", ", @cls_nodes)." ]");
    } else {
        $sgeasyTool->only_syslog("Running $CMD_PWD_MGMT on node " .
                                 "[ ".join(", ", @cls_nodes)." ]");
    }
    
    if (!($z_option_enabled || $fline_option_enabled)) {
        if ($retrive_option_enabled) {
            print("$CMD_PWD_MGMT: Retrieving entries from ".
                  "$oper_related_to_obj on node(s) [ $localnode ]\n");
        } elsif ($delete_option_enabled) {
            print("$CMD_PWD_MGMT: Deleting entries from ".
                  "$oper_related_to_obj on node(s) ".
                  "[ ".join(", ", @cls_nodes)." ]\n");
        } elsif($update_option_enabled) {
            if ($update_only_cred_key_option_enabled) {
                 print("$CMD_PWD_MGMT: Updating ".
                       "$oper_related_to_obj on node(s) ".
                       "[ ".join(", ", @cls_nodes)." ]\n");
            } else {
                 print("$CMD_PWD_MGMT: Updating entries to ".
                       "$oper_related_to_obj on node(s) ".
                       "[ ".join(", ", @cls_nodes)." ]\n");
            }
        }
    }
    
    # Test whether cmexec to remote node is successful. If not ask the
    # user to run cmpreparecl, which does all the required things for
    # the cmexec.
    if ((scalar(@cls_nodes) > 1)) {
        test_cmexec_on_all_nodes(@cls_nodes);
    }

    # Java must be installed on local system
    set_java_path();
     
    # Check for Java dependency
    java_ver_check();

    # Check for the duplicate node names entered on the command line
    check_for_duplicate_entries("host name", \@input_hosts);

    # Populate @cls_nodes array with existing cluster node otherwise only
    # local node, if they is no node name passed with command. (size of
    # @cls_nodes is zero) and also check same version of Serviceguard is
    # installed on all nodes. Skip this check if nodes from configured
    # cluster are not reachable.
    check_sg_version_on_nodes();

    # Create array that will contain only hosts configured in a cluster
    # as ESXi Host or/and vCenter.
    populate_cluster_host_list();

    # Check for lock file on all reachable nodes, if found any node
    # then fail the command otherwise create on all nodes under $SGRUN 
    # dir as cmvmusermgmt.lck
    # This is to avoid excution of cmvmusermgmt command on more than
    # one node at same time.
    check_and_create_lck_file_on_nodes();    
    
    # Take backup of existing .sdata and .credentials.xml files
    backup_of_cred_data_on_local_node();

    if ($CLUSTER_CONFIGURED && ($update_option_enabled || $delete_option_enabled)) {
        #Create hash map of each host's entry in credential db which are
        #also part of existing cluster like host's full name, short name
        #and IP address.
        my $exitcode = create_hash_maps_of_cluster_hosts_in_cred_db();
        if ($exitcode ==0) {
            if ($update_option_enabled && !$update_only_cred_key_option_enabled) {
                my $size = keys %hash_of_cls_host_short_nm_cred_db;
                if ($size < scalar(@cls_hosts)) {
                     my $all_cls_host_are_part_of_input_host_list = isSubset(\@cls_hosts,
                                                                            \@input_hosts);
                     if (!$all_cls_host_are_part_of_input_host_list) {
                         log_msg("Adding subset of configured hosts in a ".
                                 "cluster is not allowed, add all of them ".
                                 "in Serviceguard Credential Store(SCS)",
                                 ERROR);
                         fail("Failed to perform $operation operation");
                     }
                }
            } elsif ($delete_option_enabled) {
                my $hosts_are_in_cluster = 0;
                my @input_hosts_exist_in_cls = ();
                foreach my $host_to_search (@input_hosts) {
                    if (is_exist_in_hash_of_hosts($host_to_search,
                                                  %hash_of_cls_host_in_cred_db)) {
                        $hosts_are_in_cluster = 1;
                        push(@input_hosts_exist_in_cls,
                             $host_to_search);
                    }
                }
                if ($hosts_are_in_cluster) {
                    log_msg("Unable to delete from Serviceguard Credential ".
                            "Store(SCS) as some of hosts ".
                            "[ ".join(", ",@input_hosts_exist_in_cls)." ] ".
                            "are configured in existing cluster", ERROR);
                    fail("Failed to perform $operation operation");
                } 
            }
        } else {
            log_err_post_operation_and_exit($exitcode);
        }
    }
    if ($create_cred_key_option_enabled) {
        # create new Credential key on local node
        create_credential_key_file();
    }

    $program_exit_code = perform_pwd_mgmt(); 

    if ($program_exit_code == 0) {
        if ($delete_option_enabled) {
            if (-f "$SG_CONF_PATH/.credentials.xml") {
                distribute_file_to_all_nodes("credential_database");
            } else {
                #Serviceguard Credential Store has been deleted as 
                #it might have only one entry. Need to remove 
                #sdata from local node.
                #for consistency.
                if (-f "$CRED_KEY_FILE") {
                   unlink("$CRED_KEY_FILE");
                }
                #Serviceguard Credential Store has been deleted as 
                #it might have only one entry. Now need to remove 
                #Serviceguard Credential Store and sdata from all
                #other nodes in a cluster for consistency.
                delete_file_from_remote_nodes("credential_database");
                delete_file_from_remote_nodes("credential_key");
            }
        }
        #Below block will get executed, for all operation except update. Only 
        #that operation one addition function will be called to validate 
        #credential of each hosts in Serviceguard Credential Store(SCS).
        if ($delete_option_enabled ||
            $sync_cred_file_enabled ||
            $retrive_option_enabled)
        {
            cleanup_of_backuped_cred_data_on_local_node();
            lck_file_cleanup_on_nodes();
            if (!($z_option_enabled || $fline_option_enabled)) {
               print("$CMD_PWD_MGMT: Successfully performed ".
                     "$operation operation.\n");
            }
            exit($program_exit_code);
        }
    } else {
        log_err_post_operation_and_exit($program_exit_code);
    }

    #Only for update operation, we need to call one addition function to
    #validate credential of each hosts in Serviceguard Credential Store(SCS).
    if ($program_exit_code == 0) {
        $program_exit_code = verify_credential_for_hosts(@input_hosts);
    } 
    
    if ($program_exit_code == 0) {
        if (-f "$SG_CONF_PATH/.credentials.xml") {
            my $skip_print_warning_msg = 0;
            if (-f "$CRED_KEY_FILE") {
                $skip_print_warning_msg = 1;
            }
            distribute_file_to_all_nodes("credential_database", 
                                         $skip_print_warning_msg);
        } 
        if (-f "$CRED_KEY_FILE") {
            distribute_file_to_all_nodes("credential_key");
        } else {
            delete_file_from_remote_nodes("credential_key");
        }
        cleanup_of_backuped_cred_data_on_local_node();
        lck_file_cleanup_on_nodes();
        if (!($z_option_enabled || $fline_option_enabled)) {
            print STDOUT "$CMD_PWD_MGMT: Successfully performed ".
                         "$operation operation.\n";
        }
    } else {
        log_err_post_operation_and_exit($program_exit_code);
    }
    exit($program_exit_code);
}
