#!/usr/bin/perl -w
##############################################################################
# (C) Copyright 2016 Hewlett Packard Enterprise Development LP.
# (C) Copyright 2013-2015 Hewlett-Packard Enterprise Development Company, L.P.
# @(#) Serviceguard cluster creation command
# @(#) Product Name                : HP Serviceguard
# @(#) Product Version             : A.12.10.00
# @(#) Patch Name                  : 
##############################################################################
use strict;

use Getopt::Long qw(GetOptions HelpMessage :config no_ignore_case);
use Pod::Usage;
use Sys::Hostname;
use File::Basename;

my $sgeasylib = undef;
my @file_out = undef;
BEGIN {
    my $sgconffile = "/etc/cmcluster.conf";
    if (-f $sgconffile) {
        @file_out = qx(cat $sgconffile);
    } else {
        print STDERR "ERROR: Unable to run cmdeploycl ".
                     "command on unsupported OS\n";    
      exit 1;
    }
    
    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;
# POSIX exports a ton of stuff. Suppress all but what we'll use.
use POSIX qw(pathconf _PC_PATH_MAX _PC_NAME_MAX);

use constant MAX_SG_NAME_LEN => 39;

=head1 NAME

cmdeploycl - generate and apply a cluster configuration

=cut

# only root can run this
unless ($< == 0) {
    die "Only root is allowed to run $0\n";
}


# process argument
# -n <node>                     name of node to include. If none, local host
# -d                            do not use CWDSF
# -L <lock disk>                use lock disk for quorum (could be lun or pv)
# -q <quorum server>            qs to use for quorum
# -c <clustername>              name of cluster
# -t                            preview only
# -O                            log file location
# -D                            print debug messages
# -v                            do not suppress output from subcommands
# -N <network_template>         configure cluster network  #NOT READY 
# -s <site name>                defines sites for the nodes
# -I                            To turn on Smart Quorum

=head1 SYNOPSIS

cmdeploycl
[-t]
S<[-n node]...>
S<[-s site]...>
S<[-N net_template]>
S<[-c clustername]>
S<[-q qs_host [qs_ip]|-L locklun]>

cmdeploycl [-h]

=cut

my $arg_errors = 0;
my $sgeasyTool = new EDTools("cmdeploycl");
my $node_input = 0;
my @site_node_names =();
my $site_num = -1;
my @nodes;
my $errors=0;
my $SG_EASY_LOG_DIR = $sgeasyTool->get_sgeasy_dir_path(); # Based on OS version
my $SG_CONF_PATH = $sgeasyTool->get_sgconf_path();        # SGCONF value obtained
                                                          # from /etc/cmcluster.conf
my $SG_CMD_PATH = get_sgsbin_path();                      # SGSBIN value obtained
                                                          # from /etc/cmcluster.conf
my $MAX_NUMBER_OF_NODE_SUPPORTED_BY_SG = 48;              # It need to be updated if number of node
                                                          # supported by Serviceguard increases.

sub non_dash_arg {
    my ($name, $val, $target) = @_;
    if ($val =~ /^-/) {
        print "Option $name requires an argument\n";
        $arg_errors++;
    } else {
        if (ref($target) eq "ARRAY") {
            push @{$target}, $val;
            if($name eq "n") {
                if ($site_num != -1) {
                    push(@{$site_node_names[$site_num]}, $val);
                }
            }
            if($name eq "s") {
                $site_num++;
            }

        } elsif (ref($target) eq "SCALAR") {
            # a scalar target generally indicates an option that should not be
            # used more than once, so check for that.
            if (${$target}) {
                print "Option $name should not be supplied more than once\n";
                $arg_errors++;
            }
            ${$target} = $val;
        }
    }
}

# GLOBALS
my @opt_nodes;
my @opt_qs;
my ($opt_disk, $opt_clustername, $opt_debugfile, $opt_debug, $opt_preview, $opt_vgpv);
my ($hostname, $dev_map);
my $opt_network_template;
my @opt_site_names;
my $opt_smart_quorum;

GetOptions(
# these require an argument.
        "n|node=s"              => sub { non_dash_arg(@_, \@opt_nodes) },
        "L|lockdisk=s"          => sub { non_dash_arg(@_, \$opt_disk) },
        "q|quorumserver=s{,2}"  => sub { non_dash_arg(@_, \@opt_qs) },
        "c|clustername=s"       => sub { non_dash_arg(@_, \$opt_clustername) },
        "O|outputfile=s"        => sub { non_dash_arg(@_, \$opt_debugfile) },
        "D|debug=i"             => sub { non_dash_arg(@_, \$opt_debug) },
        "N|network_template=s"  => sub { non_dash_arg(@_, \$opt_network_template) },
        "s|site_name=s"         => sub { non_dash_arg(@_, \@opt_site_names) },
# below take no arguments
        "t|preview|test"        => \$opt_preview,
        "I"                     => \$opt_smart_quorum,				
        "h|help|?"              => sub { pod2usage(-verbose => 1) }
) || pod2usage();

if ($arg_errors) {
    pod2usage();
}

if (@ARGV) {
    pod2usage("ERROR: Extra text on command line: \"" . join(" ", @ARGV) . "\"");
}


$hostname = hostname();
chomp($hostname);
# if we got a fqdn, trim it down to just the host name.
$hostname =~ s/\..*//;

# if no node was specified, assume localhost
unless (@opt_nodes) {
    push(@opt_nodes, $hostname);
}

# Don't support more than $MAX_NUMBER_OF_NODE_SUPPORTED_BY_SG nodes
if (@opt_nodes > $MAX_NUMBER_OF_NODE_SUPPORTED_BY_SG){
    print STDERR "ERROR: More than $MAX_NUMBER_OF_NODE_SUPPORTED_BY_SG ".
                 "nodes are specified.\n";
    exit(3);
} 

# if localhost isn't in the list, bail
unless (grep {$_ eq $hostname} @opt_nodes) {
    pod2usage("$0 must be run on one of the nodes that will be in the cluster");
}

# look for number of IP address for quorum server as a maximum of two
# IP addresses are supported for the quorum server.
# -q option must be passed to command only once
if (scalar(@opt_qs) > 2) {
    pod2usage("ERROR: Option -q should not be supplied more than once.\n");
}

# look for long hostnames
if (grep { length > MAX_SG_NAME_LEN } @opt_nodes) {
    print STDERR "ERROR: Node names must not exceed ", MAX_SG_NAME_LEN,
                 " characters\n";
    exit(2);
}

# and long clustername
if ($opt_clustername and length $opt_clustername > MAX_SG_NAME_LEN) {
    print "Cluster names must not exceed ", MAX_SG_NAME_LEN,
        " characters\n";
    exit(2);
}

# can't specify both a lock disk (lun or vg) and a quorum server
if ($opt_disk and @opt_qs) {
    pod2usage("-q and -L are mutually exclusive.");
}

# can't specify both a lock disk (lun or vg) and enable smart quorum
if ($opt_disk and $opt_smart_quorum) {
    pod2usage("-I and -L are mutually exclusive.");
}

# If the user specified a disk with -L, parse it and determine if it
# makes sense.
my $local_opt_disk = $opt_disk;
if ($opt_disk) {
    parse_and_validate_disk();
}

# make sure the debug output file specified (if any) makes sense
validate_debug_file($opt_debugfile);

# having the node list as one string is very useful for calling other commands
my $nodelist_string;
foreach my $nodename (@opt_nodes) {
    $nodelist_string .= "-n $nodename ";
}

# In preview mode, we don't want to create the log file because we're not
# actually doing anything to the system. It is easier, however, to create
# the filehandle anyway so the other code can assume it exists, so we'll
# point it to /dev/null.
if ($opt_preview) {
    if(@opt_site_names) {
        $sgeasyTool->screen_and_syslog("Previewing cmdeploycl on nodes " .
                                        join(" ", @opt_nodes) .
                                        join(" ", " of sites @opt_site_names"));

    }
    else {
        $sgeasyTool->screen_and_syslog("Previewing cmdeploycl on nodes ".
                                       join (" ", @opt_nodes));
    }
} else {
    if(@opt_site_names) {
        $sgeasyTool->screen_and_syslog("Running cmdeploycl on nodes " .
                                       join(" ", @opt_nodes).
                                       join(" ", " of sites @opt_site_names"));
    }
    else {
        $sgeasyTool->screen_and_syslog("Running cmdeploycl on nodes " .
                                       join(" ", @opt_nodes));
    }
    $sgeasyTool->screen_and_syslog("Saving subcommand output ".
                                   "to $sgeasyTool->{logfile}");
    if(@opt_site_names) {
        $sgeasyTool->screen_and_log("Running cmdeploycl on nodes ".
                                    join(" ", @opt_nodes).
                                    join(" ", " of sites @opt_site_names"),
                                    "cmdeploycl:");
    }
    else {
        $sgeasyTool->screen_and_log("Running cmdeploycl on nodes " .
                                    join(" ", @opt_nodes),
                                    "cmdeploycl:");
    }
    if (! -d $SG_EASY_LOG_DIR) {
        die "$SG_EASY_LOG_DIR does not exist.";
    }
}

## CMPREPARECL
# sets up cmclnodelist, /etc/hosts, /etc/resolv.conf, /etc/nsswitch.conf,
#    inetd, and insf -C (for clusterwide device files)
my $cmpreparecl_cmd = "$SG_CMD_PATH/cmpreparecl $nodelist_string";
# Update quorum server information in /etc/hosts file
if (@opt_qs) {
    $cmpreparecl_cmd .= " -q " . join(" ", @opt_qs);
}
preview_or_run_system_proc("cmpreparecl", $cmpreparecl_cmd);

# verify if MC is installed to configure the sites
my $mc_inst_ret = validate_mc_installed_on_nodes();
if(@opt_site_names) {
    if(check_if_site_has_nodes() || check_if_nodes_part_of_any_site()) {
        $errors++;
    }
    if ($mc_inst_ret != 0) {
        print STDERR "ERROR: Sites can only be specified when Metrocluster ".
                     "product is installed on all nodes\n";
        $errors++; 
    }
}

if ($mc_inst_ret == 0 && $local_opt_disk) {
    # Metrocluster software is installed on all nodes.
    # we need to throw a warning stating cluster using lock lun
    # cannot be used in a Metrocluster environment.
    print STDERR "WARNING: Metrocluster software is installed ".
                 "on all nodes;\nDo not use Cluster Lock LUN ".
                 "if deploying an Metrocluster\n";
}

# For a single site cluster we should not error out if the quorum server is specified.
# Use case for a single site cluster is Continental cluster.
if (@opt_site_names && scalar(@opt_site_names) != 1 && !(@opt_qs) ) {
    print "ERROR: Quorum server must be specified when sites in a cluster" . 
          " are specified\n";
    $errors++;
}

# in case of errors we will not even do the preview.
# if we do not exit here then it will display a incorrect behavior
# non-preview option would exit from here.
if ($errors) {
    exit(1);
}
# so is having the -D -O options
my $debug_opts = "";
if ($opt_debug) {
    $debug_opts = "-D $opt_debug ";
}
if ($opt_debugfile) {
    $debug_opts .= "-O $opt_debugfile ";
}

## CMPREPARESTG
# validate and sets up device name (passed via cmdeploycl) as lock lun
# Note: It modifies global variable $opt_disk.
if ($opt_disk) {
    $opt_disk = assemble_node_locks();
    if ($opt_disk =~ m/-L/) {
        print "Using lock lun string $opt_disk\n";
    }
}

## CMAPPLYCONF -N network_template
my $cmapplyconf_cmd;
if ($opt_network_template) {
    $cmapplyconf_cmd = "$SG_CMD_PATH/cmapplyconf -v -N $opt_network_template";
    $cmapplyconf_cmd .= " -t" if ($opt_preview);
    run_system_proc("cmapplyconf", $cmapplyconf_cmd);
}


## CMQUERYCL
my $cmquerycl_cmd = "$SG_CMD_PATH/cmquerycl -v -w full";
if (@opt_qs) {
    $cmquerycl_cmd .= " -q " . join(" ", @opt_qs) . " $nodelist_string";
} elsif ($opt_disk) {
    $cmquerycl_cmd .= $opt_disk;
} else {
    $cmquerycl_cmd .= " $nodelist_string";
}
$cmquerycl_cmd .= " $debug_opts " .
    "-C $SG_EASY_LOG_DIR/cluster.ascii.generated";
preview_or_run_system_proc("cmquerycl", $cmquerycl_cmd);


## ASCII FILE MASSAGING
# We always have to copy the ascii file in non-preview mode. Sometimes
# we have to do other manipulation.
my $first_node_name_match=0;
if ($opt_preview) {
    print "Copying $SG_EASY_LOG_DIR/cluster.ascii.generated to ".
          "$SG_EASY_LOG_DIR/cluster.ascii, and \nedit ".
          "$SG_EASY_LOG_DIR/cluster.ascii to ";
    print "set cluster name as \"$opt_clustername\"\n" if ($opt_clustername);
} else {
    open SRC, "<$SG_EASY_LOG_DIR/cluster.ascii.generated" or
        die "Could not open ascii file from cmquerycl";
    open DEST, ">$SG_EASY_LOG_DIR/cluster.ascii" or
        die "Could not create new ascii file";
    my $curr_node;
    while (<SRC>) {
        # Update the cluster name in the ascii file
        if ($opt_clustername and /^CLUSTER_NAME/) {
            print DEST "CLUSTER_NAME\t$opt_clustername\n";
            next;
        }

		# Turn on Smart Quorum if the option is enabled.
        if ($opt_smart_quorum and /^QS_HOST\s+/) {
            print DEST;
            print DEST "QS_SMART_QUORUM\tON\n";
            print DEST "QS_ARBITRATION_WAIT\t3000000\n";
            next;
        }
		
        # if site names are specified then fill them up in the ascii file.
        if((@opt_site_names) and (/^NODE_NAME\s+/)) {
            # if this is first time a NODE NAME pattern is matched then add all the
            # sites information here.
            if($first_node_name_match == 0) {
                foreach my $sitename (@opt_site_names) {
                    print DEST "SITE_NAME            " . $sitename . "\n";
                }
                print DEST "\n";
            }
            my $_idx = 0;
            foreach my $node (@opt_nodes) {
                if (grep {/$node/} $_) {
                    last;     
                }
                $_idx++;
            }
            print DEST "NODE_NAME	" . $opt_nodes[$_idx] . "\n";
            my $site_idx=0;
            foreach my $site (@opt_site_names)
            {
               if(grep {/$opt_nodes[$_idx]/}  @{$site_node_names[$site_idx]}) {
                   print DEST "SITE     " . $site . "\n";
                   last;
               }
               $site_idx++;
            }
            $first_node_name_match = 1;
            next; 
        }
        print DEST;
        next;
    }
    close SRC;
    close DEST;
}

## CMAPPLYCONF
if ($opt_smart_quorum) {
    $cmapplyconf_cmd = "$SG_CMD_PATH/cmapplyconf -v -I -C ".
					   "$SG_EASY_LOG_DIR/cluster.ascii $debug_opts";
} else {
    $cmapplyconf_cmd = "$SG_CMD_PATH/cmapplyconf -v -C ".
					   "$SG_EASY_LOG_DIR/cluster.ascii $debug_opts";
}			   
preview_or_run_system_proc("cmapplyconf", $cmapplyconf_cmd);


## CMRUNCL
my $cmruncl_cmd = "$SG_CMD_PATH/cmruncl -v -w none $debug_opts";
preview_or_run_system_proc("cmruncl", $cmruncl_cmd);

## FINISH UP
unless ($opt_preview) {
    my $cmviewcl_cmd = "$SG_CMD_PATH/cmviewcl -v";
    system($cmviewcl_cmd);
    print "Cluster configuration saved to $SG_EASY_LOG_DIR/cluster.ascii\n";   

} 
else {
    print "Preview complete. To apply changes, run cmdeploycl  without -t ".
          "option.\n";
}

# This subroutine returns the following
# 0 - if the Metrocluster product is installed on all nodes.
# 1 - if the Metrocluster product is not installed on one or more nodes.
# 2 - if the Metrocluster product is not installed on any nodes.
sub validate_mc_installed_on_nodes{
    my $cmcheck_feature_cmd = "$SG_CONF_PATH/scripts/cmcheck_features mcrac";
    my $not_installed = 0;
    my @nodes_list;
    my ($ret, @cmd_output) = runandCaptureCommand($cmcheck_feature_cmd);
    $not_installed++ if ($ret != 0);
    
    foreach my $node_name (@opt_nodes) {
        if ($node_name !~ $hostname) {
            my $use_cmd = check_ssh_cmexec_cmd($node_name);
            if ($use_cmd == 2) {
                print "WARNING: Could not verify Metrocluster software on $node_name." . 
                      "\nPlease ensure that SSH or /etc/cmcluster/cmclnodelist ".
                      "authentication is configured.\n";     
                return 1;
            }
            my $ssh_cmd = "ssh -q -o NumberOfPasswordPrompts=0 $node_name " . "'$cmcheck_feature_cmd'";
            my $cmexec_cmd = "$SG_CMD_PATH/cmexec $node_name ". "'$cmcheck_feature_cmd'";
            if ($use_cmd == 0) {
                ($ret, @cmd_output) = runandCaptureCommand($ssh_cmd);
            }
            elsif ($use_cmd == 1) {
                ($ret, @cmd_output) = runandCaptureCommand($cmexec_cmd); 
            }
            # Non-zero return value means the command has failed. The feature
            # is not installed.
            $not_installed++ if ($ret !=0);
        }
    }
    # all the nodes have Metrocluster software installed.
    return 0 if (!$not_installed);
    # if the Metrocluster product is installed on some nodes then return 1
    return 1 if($not_installed < scalar(@opt_nodes)); 
    # no nodes have Metrocluster software installed.
    return 2 if ($not_installed == scalar(@opt_nodes)); 
}

# This function returns whether ssh or cmexec can be used to run command on a node.
# If ssh can be used then it is preferred over cmexec. This function returns
# 0 if ssh can be used
# 1 if cmexec can be used 
# 2 if none can be used.
sub check_ssh_cmexec_cmd {
    my ($node_name) = @_;
    my ($ret, @cmd_output) = runandCaptureCommand("ssh -q -o NumberOfPasswordPrompts=0 $node_name \"echo hi\" > /dev/null 2>&1");
    if ($ret != 0) {
        ($ret, @cmd_output) = runandCaptureCommand("$SG_CMD_PATH/cmexec $node_name \"echo hi\" > /dev/null 2>&1");
         return 2 if ($ret != 0);
         return 1;    
    }
    return 0;
}

## Validation is done as follows
## From the cmviewcl -v -f line output
##     a) get the heartbeat IP addresses configured.
##     b) for each IP address get the subnet configured.
##     c) Pass the subnet list to validate_lan_interface_names_for_subnets().
##     d) In validate_lan_interface_names_for_subnets() get the unique list of subnets.
##     e) for each subnet in the unique subnet list get the interface names on
##        for nodes push it in a hash.
##     f) if sites are defined then 
##        (1) populate the interface list to be compared with interface names 
##            of nodes for each site. Do step (g) for each site.
##                            else  
##        (2) populate the interface list to be compared with interface names 
##            of nodes provided in command line. Do step (g).
##     g) compute size of unique items in the interface list. 
##          i)  if the size is "1" then the interface names are same return 0.
##          ii) else display a WARNING for the subnet that does not have the same interface
##              and return 1.

sub validate_lan_interface_names {
    my $cmviewcl_cmd = "$SG_CMD_PATH/cmviewcl -v -f line";
    my @subnet_list = ();
    my ($ret, @cmd_output) = runandCaptureCommand($cmviewcl_cmd);
    if ($ret) {
        $sgeasyTool->screen_and_syslog("ERROR: Error in getting cmviewcl output\n");
        return 1;
    }
    my @hb_net_output = grep { /heartbeat/} @cmd_output;
    foreach my $line (@hb_net_output) {
        chomp($line);
        my @field_list = split(/\|/, $line);
        chomp($field_list[2]);
        my @ipaddress = split (/ip_address:/, $field_list[2]);
        my $pattern = "ip_address:" . $ipaddress[1] . "\\|subnet="; 
        my @subnet_address_output = grep { /$pattern/ } @cmd_output;
        chomp(@subnet_address_output);
        @field_list = split (/\|/, $subnet_address_output[0]); 
        chomp($field_list[3]);
        my @subnet = split (/=/, $field_list[3]);
        chomp(@subnet);
        push (@subnet_list , $subnet[1]);
    }
    (my $retval) =  validate_lan_interface_names_for_subnets(\@subnet_list, \@cmd_output); 
    if($retval) {
        return 1;
    }
    return 0;
}

# This function takes list of subnets and determines if the LAN interface
# names for each of the subnets are same or not.
# It returns 0 if the LAN interface names are same, 0 otherwise. 
# This function also considers nodes of the same sites for checking
# the LAN interface names on nodes.
sub validate_lan_interface_names_for_subnets {
     my ($subnet_list,$cmd_output) = @_;
     my $pattern;
     my %seen;
     my %node_interface_list;
     my @subnet_uniq_list = grep { !$seen{$_}++ }  @$subnet_list; 
     my $is_hb_subnet_ipv6 = 0;
     my $is_lan_intf_name_different = 0;
     foreach my $subnet (@subnet_uniq_list) {
         $pattern = "subnet=" . $subnet; 
         my @subnet_node_and_interface_list = grep { /$pattern/ } @$cmd_output;
         chomp(@subnet_node_and_interface_list);
         my @intf_list = ();
         # Build an hash of node_name => intf_name.
         %node_interface_list=();
         foreach my $subnet_line (@subnet_node_and_interface_list) {
             chomp($subnet_line);
             my @field_list = split (/\|/, $subnet_line);
             chomp($field_list[0]);
             my @node_name = split(/node:/,$field_list[0]);
             chomp($node_name[1]);
             chomp($field_list[1]);
             my @intf_name = split (/interface:/, $field_list[1]);
             chomp($intf_name[1]);
             $node_interface_list{$node_name[1]} = $intf_name[1];
         } 
         if(@opt_site_names) {
             my $j=0;
             foreach my $site (@opt_site_names) {
                 @intf_list = ();
                 # Get the interface names for nodes of the same site.
                 foreach my $node (@{$site_node_names[$j]}) {
                     push(@intf_list, $node_interface_list{$node});
                 }
                 if (check_if_subnet_is_ipv6($subnet)) {
                     $is_hb_subnet_ipv6++;
                 }
                 $j++;
             }
         }
         else {
             foreach my $node (@opt_nodes) {
                 push(@intf_list, $node_interface_list{$node});
             }
             if (check_if_subnet_is_ipv6($subnet)) {
                 $is_hb_subnet_ipv6++;
             }
         }
     }
     if ($is_lan_intf_name_different || $is_hb_subnet_ipv6) {
         return 1;
     } 
     return 0;
}

# This routine returns 1 if the subnet passed is IPv6 else returns 0
sub check_if_subnet_is_ipv6 {
    my ($subnet) = @_;
    if ($subnet =~ /[0-9a-zA-Z]+:/) {
        return 1;
    }
    return 0; 
}

sub runandCaptureCommand {
    my ($myCmd) = @_;
    my @lines = ();
    my @output = ();

    open(CMD, "$myCmd |") or return (1 , @output);
    while (<CMD>) {
        push(@output, $_);
        last if eof(CMD);
    }
    close(CMD);
    my $exitcode = ($? >> 8);
    return($exitcode, @output);
}

sub check_if_site_has_nodes {
    my $j=0;
    foreach my $site (@opt_site_names)
    {
        if(scalar(@{$site_node_names[$j]}) == 0) {
            print "ERROR: Site $site does not have any node names\n";
            return(1); 
        }
        $j++;
    }
    return(0);
}

sub check_if_nodes_part_of_any_site {
    my $j;
    my @no_site_defined = ();
    my $found;
    foreach my $node_name (@opt_nodes) 
    {
        $found = 0;
        $j=0;
        foreach my $site (@opt_site_names) 
        {
            if(grep {/$node_name/} @{$site_node_names[$j]}) {
                $found = 1;
                last;
            }   
            $j++;
        }
        if($found == 0) {
            my $node_to_added = $node_name . ",";
            push(@no_site_defined, $node_name);
        }
        
    }
    if(scalar(@no_site_defined) != 0) {
        print "ERROR: Nodes @no_site_defined do not have any sites defined\n";
        return 1;
    }
    return 0;
}

sub run_system_proc {
    my ($cmdname, $cmd) = @_;
    $sgeasyTool->screen_and_log("Calling $cmdname ($cmd) ", "cmdeploycl:");
    if ($cmdname =~ /^cm/) {
        $sgeasyTool->screen_and_log("$cmdname may take a while to complete ... ", "cmdeploycl:");
    }
    open(CMD, "$cmd 2>&1 |") or die "Failed to start $cmdname";
    my @msgs;
    while (<CMD>) {
        push(@msgs, $_);
    }

    close(CMD);
    my $saved_errno = $!;
    my $saved_exit_status = ($? >> 8);
    if (($? >> 8) != 0) {
        foreach (@msgs) {
            chomp;
           $sgeasyTool->log_from_subcmd($_, $cmdname . ":");
        }
        die $saved_errno ? "error closing command pipe: $saved_errno, stopping"
                 : "exit status ", $saved_exit_status, " from $cmdname, stopping";
    }

    $sgeasyTool->screen_and_log("Command $cmdname succeeded ", "cmdeploycl:");
}

sub preview_or_run_system_proc {
    my ($cmdname, $cmd) = @_;
    if ($opt_preview) {
        print "$cmd\n";
    } else {
        run_system_proc($cmdname, $cmd);
    }
}

sub validate_debug_file {
    my $opt_debugfile = shift;
    return unless ($opt_debugfile);

    # if the target exists and is a file, all the other requirements must
    # have already been satisfied. If it exists and is not a file, bail.
    if (-e $opt_debugfile) {
        unless (-f $opt_debugfile) {
            pod2usage("Debug output file must be a file or must not exist.");
        }
        return;
    }

    # overall debug file path must be within path limit for current directory
    my $max_pathlen = pathconf(".", _PC_PATH_MAX);
    if (length $opt_debugfile > $max_pathlen) {
        pod2usage("Debug file path is too long (limit of $max_pathlen)");
    }

    # must not have a trailing slash
    if ($opt_debugfile =~ /\/$/) {
        pod2usage("Debug output file must not be a directory");
    }

    # directory portion, if any, must exist
    my $dirpart = dirname($opt_debugfile);
    pod2usage("Directory for debug output file must exist") unless
        (-d $dirpart);

    # filename must be within length limit for the filesystem of the dir
    my $filepart = basename($opt_debugfile);
    my $max_filelen = pathconf($dirpart, _PC_NAME_MAX);
    if (length $filepart > $max_filelen) {
        pod2usage("Debug file name is too long for $dirpart " .
                  "(limit of $max_filelen)");
    }
}

sub assemble_node_locks {
    my $nodeStr = join(' -n ', @opt_nodes);
    my $cmd = "$SG_CMD_PATH/cmpreparestg -d -p $opt_disk -n $nodeStr";
    my %nodelock;
    my $exitcode = 0;
    my @cmd_output = ();
    my $ret = "";
    if ($opt_preview) {
        print "Validate and configure device $opt_disk as Lock lun using cmpreparestg commmand\n";
        return $ret;
    }
    $sgeasyTool->screen_and_log("Calling cmpreparestg to validate and configure device as Lock lun", "cmdeploycl:");
    $sgeasyTool->screen_and_log("cmpreparestg may take a while to complete ... ", "cmdeploycl:");
    ($exitcode, @cmd_output) = runandCaptureCommand($cmd);
    if ($exitcode) {
        my $error_msg = "@cmd_output";
        chop($error_msg);
        $sgeasyTool->screen_and_log($error_msg, "cmpreparestg:");
        die "exit status $exitcode, from cmpreparestg, stopping\n";
    }
    $cmd = $cmd. " |";
    open(CMQ, $cmd) or die "open failed for \"$cmd\": $!";
    while (<CMQ>) {
        chomp;
        # for now, don't do cluster device files
        if ($_ =~ /^device:(.+)\|node:(.+)$/) {
            $nodelock{$2} = $1;
        }
    }
    close(CMQ) or die $! ? "error closing command pipe: $!, stopping"
                         : "exit status ", $? >> 8, " from $cmd, stopping";
    foreach my $nodename (@opt_nodes) {
        my $lock = $nodelock{$nodename};
        # make sure that the lock we're looking at is the one requested
        if ($nodename eq $hostname and $opt_disk ne $lock) {
            die("requested lock disk $opt_disk does not match known disk " .
                "$lock for local node $nodename. Exiting.");
        }
        $ret .= " -n $nodename -L $lock";
    }
    if (defined($sgeasyTool)) {
        $sgeasyTool->screen_and_log("Command cmpreparestg succeeded ", "cmdeploycl:");
    }
    return $ret;
}

# It's the name of a disk device not apparently in a vg. Use it as a locklun.
sub parse_and_validate_disk {
    if (!isDisk($opt_disk)) {
        # It may be a typo but it's not usable.
        print "-L $opt_disk option does not seem to indicate a usable " .
              "storage device.\n";
        exit(4);
    }
}

# See if the kernel thinks the specified device is a disk.
sub isDisk {
    my $device = shift;
    if (-b $device) {
        return 1;
    }
    return 0;
}
