MINI MINI MANI MO

Path : /opt/oracle/product/18c/dbhomeXE/lib/
File Upload :
Current File : //opt/oracle/product/18c/dbhomeXE/lib/asmcmdsys.pm

# Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      asmcmdsys - ASM CoMmanD line interface (Sample Module)
#
#    DESCRIPTION
#      This module is a dummy/sample module that provides a working 
#      template for module additions.
#
#    NOTES
#      usage: asmcmdcore [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    emsu       07/07/17 - 26412596: Have lscc display storage access
#    prabbala   05/25/17 - 25785813: log more detail if spbackup src has fn.finc
#    anovelo    04/26/17 - 25950711: Added GI user check to lscc
#    emsu       02/28/17 - 25497063: Add --guid to mkcc, add chcc
#    emsu       10/15/16 - 24760407: Pass version to mgmtca in mkcc
#    emsu       10/21/16 - 25108506: Add -legacy to 'mgmtca queryRepos' in lscc
#    anovelo    10/24/16 - 24706236: Update call to asmcmdshare_get_subdirs
#    emsu       08/12/16 - 24381765: Don't allow indirect access on AIX in mkcc
#    emsu       07/23/16 - 24322507: Add equivalent PRGR errors in mkcc/rmcc
#    anovelo    06/09/16 - 21360641: Create backup spfiles in server mode
#    emsu       04/26/16 - 21537310: Add --file option to lscc
#    diguzman   05/30/16 - 19654070: Little change at _no_instance_cmd routine
#    diguzman   05/24/16 - 23061091: asmcmdshare_set_instance signature change
#    emsu       03/03/16 - 22817301: DSC not supported on HPUX
#    samjo      04/12/16 - 23052791: Use nohdr with KFOD
#    emsu       04/03/16 - 22956099: Make rmcc error messages clearer
#    emsu       12/02/15 - 22301967: Add ACFS to mkcc/rmcc/lscc 
#    diguzman   03/08/16 - 22752719: validate shutdown's target option
#    emsu       02/22/16 - 22780177: Fix guid/version mismatch trace in lscc
#    emsu       02/03/16 - 22652416: Fix order of RHP version/guid in lscc
#    samjo      01/29/16 - 20690631: Start IOS when configuring ASMCC with
#                          indirect storage
#    emsu       01/25/16 - 22375430: Fail mkcc if credfile dir nonexistent
#    diguzman   01/21/16 - 22014252: add shutdown --target option
#    emsu       11/23/15 - 21623472: Add RHP to lscc
#    emsu       10/06/15 - 22088795: Add TFA to mkcc/rmcc/lscc
#    emsu       10/22/15 - 21864926: Fix version in mkcc, fix error reporting
#                          in mk/rmcc, mk/rmcc must be executed by GI user
#    emsu       07/28/15 - 20035825: Add RHP to mkcc/rmcc
#    emsu       06/22/15 - 20732199: Add GIMR to lscc, support rmcc -f for GIMR
#    emsu       05/12/15 - 21044453: Add GIMR to mkcc/rmcc
#    siyarlag   05/11/15 - Bug/21116936: fix label set, disallow afd on exadata
#    siyarlag   04/02/15 - Bug/19700132: make afd commands clusterwide
#    diguzman   03/13/15 - 20678151: Pass array references to asmcmdshare_runcmd
#    ssprasad   03/09/15 - Change afd.conf to oracleafd.conf
#    prabbala   01/30/15 - 20101171: used asmcmdshare_runcmd to capture cmd op
#    siyarlag   01/08/15 - Bug/20307737: print msg based on return code
#    siyarlag   12/04/14 - Bug/18838998: display if no device to label
#    prabbala   11/25/14 - 19512500: use asmcmdshare_runcmd to capture output
#                          of a shell command
#    samjo      10/15/14 - Bug 19803231. Successful 'asmcmd lscc' should go to
#                          STDOUT
#    ykatada    10/03/14 - #19617921: use bind variables to SELECTs
#    somgupta   08/22/14 - Bug/18369237: add lscc
#    siyarlag   07/30/14 - Bug/19326908: use HAS commands for SIHA
#    alolau     06/10/14 - Add afd_di
#    siyarlag   06/24/14 - Bug/19035573: use afdtool always to update afd conf
#    siyarlag   05/29/14 - Bug/18865657: add afd_scan
#    hppancha   03/26/14 - bug11634278: Add mkcc and rmcc
#    pvenkatr   01/22/14 - Bug # 18136383 Added afd commands
#    samjo      05/08/14 - Bug 18515716. Add checks in 'spcopy'
#    siyarlag   05/21/14 - Bug/18812974: add afd_filter afd_lsdsk
#    siyarlag   04/16/14 - Bug/18430406: add afd_configure afd_deconfigure
#                          afd_state
#    pvenkatr   01/22/14 - Bug # 18136383 Added AFD commands
#                                afd_dsget/afd_dsset/afd_label/afd_unlabel
#    pvenkatr   01/15/14 - Bug # 17998892 - Added check for SYSASM for
#                          spbackup, spcopy, spget, spmove, spset.
#    pvenkatr   11/21/13 - #17526682 - Added a check for client cluster before
#                          calling gpnptsetspfile.
#    pvenkatr   09/25/13 - 17398626 - using 'uname -n' & %COMPUTERNAME% instead
#                          of $HOSTNAME env var.
#    pvenkatr   09/03/13 - # 17365414 - invoking SQLPLUS with appropriate
#                          privileges.
#    pvenkatr   08/27/13 - Rearranged reconnect logic in dsset for FlexASM
#                          scenarios.
#    manuegar   05/08/13 - Bug13951456 Support bind parameters
#    pvenkatr   02/01/13 - Using asmcmdshare_filter_invisible_cmds
#    pvenkatr   08/24/11 - Removed flags hash table - using from XML
#    adileepk   06/20/11 - Connection Pooling.
#    soye       05/13/11 - lrg5557538: spbackup accepts DB spfile type
#    dfriedma   05/12/11 - v$asm_operation updates
#    adileepk   11/08/10 - Adding changes to integrate parser module with
#                          asmcmd.
#    moreddy    09/21/10 - 9846823 spcopy -u update gpnp when dstn is os file
#    shmubeen   08/16/10 - fix for bug 7189804. lsop enhancement
#    shmubeen   05/31/10 - change Dsk_Num header of lsop to Operation
#    moreddy    05/06/10 - bug 8667038 NLS for error messages
#    amitroy    04/21/10 - BUG 8933243 - USE DIFFERENT CHARACTER IN ASMCMD TO
#                          "HIDE COLUMN DETAILS" INSTEAD OF -H
#    pvenkatr   03/31/10 - Syntax, description, example - all from XML
#    moreddy    03/22/10 - Adding more tracing
#    moreddy    01/19/10 - Adding tracing messages
#    sanselva   12/16/09 - lrg 4067759 fix crsctl/srvctl:for NT
#    sanselva   11/02/09 - NT paths interpreted wrongly for spcopy spmove
#    pvenkatr   09/03/09 - Help message from xml file.
#    amitroy    09/24/09 - adding 'mount' option for STARTUP; bug#8676198
#    amitroy    09/22/09 - modifying the hashkey for 'normal'
#    amitroy    09/15/09 - #8676198 add NORMAL option for SHUTDOWN
#    canuprem   07/29/09 - update dsset and dsget to work in SIHA mode
#    sanselva   07/27/09 - #8683273 asm_diskstring glob string issue
#    lajain     07/23/09 - Display empty string in dsget.
#    sanselva   06/24/09 - correct syntax for command startup
#    canuprem   06/18/09 - options for dsset should be mutually exclusive.
#    gayavenk   06/11/09 - Fix pathname issue in dsset
#    shagarw    06/11/09 - add param to setds to ignore the input ds.
#    canuprem   05/29/09 - modify dsset and dsget
#    sanselva   05/14/09 - spbackup,spmove,spcopy issue when target directory
#    heyuen     04/17/09 - add dsset dsget help
#    heyuen     04/20/09 - add spbackup
#    sanselva   04/12/09 - ASMCMD long options and consistency
#    heyuen     04/06/09 - update spmove to use asmcmdsys_dualget
#    heyuen     03/23/09 - remove iostat
#    heyuen     01/30/09 - update offline commands
#    heyuen     02/09/09 - fix asmcmd_spset
#    heyuen     12/03/08 - add spmove, spset, spget
#    heyuen     10/24/08 - change zones to regions
#    heyuen     10/14/08 - use dynamic modules
#    heyuen     09/19/08 - fix ctrl-c in iostat
#    heyuen     09/17/08 - complete path for spcopy
#    heyuen     08/02/08 - fix startup/shutdown msgs
#    heyuen     07/28/08 - use command properties array
#    heyuen     07/17/08 - fix messages, rewrite iostat
#    heyuen     05/14/08 - add spcopy
#    heyuen     05/19/08 - startup restricted
#    heyuen     05/12/08 - enable iostat continuously
#    heyuen     04/15/08 - bug 6935431, reorder help
#    heyuen     03/26/08 - bug 6753856: fix shutdown hang
#    heyuen     03/17/08 - enable -g for iostat
#    heyuen     02/21/08 - disable iostat for non mounted disks
#    heyuen     09/20/07 - creation
#
############################################################################
#
############################ Functions List #################################
#
#############################################################################

package asmcmdsys;
require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(asmcmdsys_init
                 %asmcmdsys_cmds
                 );

use strict;
use DBI qw(:sql_types);
use Getopt::Long qw(:config no_ignore_case bundling);
use asmcmdglobal;
use asmcmdshare;
use asmcmdparser;
use asmcmdbase;
use XML::Parser;
use asmcmdexceptions;
use asmcmdxmlexceptions;

use List::Util qw[min max];
use File::Find;
use File::Spec;
use File::Spec::Functions;
use File::Copy;
use Sys::Hostname;
use Cwd;

####################### ASMCMDSYS Global Constants ######################
our $ASMCMDSYS_SQLPLUS = "$ENV{'ORACLE_HOME'}/bin/sqlplus -S / as sysasm";

# SQLPLUS command with appropriate roles.
our $ASMCMDSYS_SQLPLUS_SYSASM  =
                 "$ENV{'ORACLE_HOME'}/bin/sqlplus -S / as sysasm";
our $ASMCMDSYS_SQLPLUS_SYSDBA  =
                 "$ENV{'ORACLE_HOME'}/bin/sqlplus -S / as sysdba";
our $ASMCMDSYS_SQLPLUS_SYSOPER =
                 "$ENV{'ORACLE_HOME'}/bin/sqlplus -S / as sysoper" ;

our $ASMCMDSYS_DSC_NAME   = "Domain Services Cluster";
our $ASMCMDSYS_IOS_NAME   = "Oracle ASM IOServer";
our $ASMCMDSYS_MIN_MC_VER = "12.2.0.1.0"; # minimum valid MC version

# cluster class
use constant CLUSTER_CLASS_STANDALONE     => "Standalone";
use constant CLUSTER_CLASS_DOMAINSERVICES => "DomainServices";
use constant CLUSTER_CLASS_MEMBER         => "Member";

####################### ASMCMDSYS Global Variables ######################
our (%asmcmdsys_lsop_header) = ('name'       ,'Group_Name',
                                'pass'       ,'Pass     ',
                                'state'      ,'State',
                                'power'      ,'Power',
                                'est_work'   ,'EST_WORK',
                                'est_rate'   ,'EST_RATE',
                                'est_minutes'   ,'EST_TIME',
                                );

our (%asmcmdsys_cmds) = (lsop            => {},
                         shutdown        => {},
                         spcopy          => {},
                         spmove          => {},
                         startup         => {},
                         spset           => {},
                         spget           => {},
                         dsset           => {},
                         dsget           => {},
                         spbackup        => {},
                         mkcc            => {},
                         rmcc            => {},
                         lscc            => {},
                         chcc            => {}
                         );

# PLSQL constants
my ($PLSQL_NUMBER)      = 22;

# lscc --file component mapping
our (%lsccfile_comps);
sub is_asmcmd
{
  return 1;
}

########
# NAME
#   asmcmdsys_init
#
# DESCRIPTION
#   This function initializes the asmcmdsys module.  For now it simply 
#   registers its callbacks with the asmcmdglobal module.
#
# PARAMETERS
#   None
#
# RETURNS
#   Null
#
# NOTES
#   Only asmcmdcore_main() calls this routine.
########
sub init
{
  # All of the arrays defined in the asmcmdglobal module must be 
  # initialized here.  Otherwise, an internal error will result.
  push (@asmcmdglobal_command_callbacks, \&asmcmdsys_process_cmd);
  push (@asmcmdglobal_help_callbacks, \&asmcmdsys_process_help);
  push (@asmcmdglobal_command_list_callbacks, \&asmcmdsys_get_asmcmd_cmds);
  push (@asmcmdglobal_is_command_callbacks, \&asmcmdsys_is_cmd);
  push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmdsys_is_wildcard_cmd);
  push (@asmcmdglobal_syntax_error_callbacks, \&asmcmdsys_syntax_error);
  push (@asmcmdglobal_no_instance_callbacks, \&asmcmdsys_is_no_instance_cmd);
  %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdsys_cmds);

  #Perform ASMCMD consistency check if enabled
  if ($asmcmdglobal_hash{'consistchk'} eq 'y')
  {
     if (!asmcmdshare_check_option_consistency(%asmcmdsys_cmds))
     {
       exit 1;
     }
  }
}


########
# NAME
#   asmcmdsys_process_cmd
#
# DESCRIPTION
#   This routine calls the appropriate routine to process the command 
#   specified by $asmcmdglobal_hash{'cmd'}.
#
# PARAMETERS
#   dbh       (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   1 if command is found in the asmcmdsys module; 0 if not.
#
# NOTES
#   Only asmcmdcore_shell() calls this routine.
########
sub asmcmdsys_process_cmd 
{
  my ($dbh) = @_;
  my ($succ) = 0;

  # Get current command from global value, which is set by 
  # asmcmdsys_parse_asmcmd_args()and by asmcmdcore_shell().
  my ($cmd) = $asmcmdglobal_hash{'cmd'};

  # Declare and initialize hash of function pointers, each designating a 
  # routine that processes an ASMCMDSYS command.
  my (%cmdhash) = ( startup          => \&asmcmdsys_process_startup ,
                    shutdown         => \&asmcmdsys_process_shutdown ,
                    lsop             => \&asmcmdsys_process_lsop,
                    spcopy           => \&asmcmdsys_process_spcopy,
                    spmove           => \&asmcmdsys_process_spmove,
                    spset            => \&asmcmdsys_process_spset,
                    spget            => \&asmcmdsys_process_spget,
                    dsset            => \&asmcmdsys_process_dsset,
                    dsget            => \&asmcmdsys_process_dsget,
                    spbackup         => \&asmcmdsys_process_spbackup,
                    mkcc             => \&asmcmdsys_process_mkcc,
                    rmcc             => \&asmcmdsys_process_rmcc,
                    lscc             => \&asmcmdsys_process_lscc,
                    chcc             => \&asmcmdsys_process_chcc);

  if (defined ( $cmdhash{ $cmd } ))
  {
    # If user specifies a known command, then call routine to process it. #
    $cmdhash{ $cmd }->($dbh);
    $succ = 1;
  }
  
  return $succ;
}


########
# NAME
#   asmcmdsys_process_startup
#
# DESCRIPTION
#   This function processes the asmcmd command startup.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_startup
{
  my (%args);
  my ($ret);
  my ($qry);
  my ($pfile);
  my ($sqlplus_stmt);

  my ($tempoutputfile) = $asmcmdglobal_hash{'tempdir'} . "/startuptemp.out";
  my ($tempfilehandle);
  my (@lines) = ();

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  #bug 6994980
  delete $ENV{'ENV'} if (defined($ENV{'ENV'}));

  if ($asmcmdglobal_hash{'contyp'} eq "sysasm")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSASM" ;
  }
  if ($asmcmdglobal_hash{'contyp'} eq "sysdba")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSDBA" ;
  }
  if ($asmcmdglobal_hash{'contyp'} eq "sysoper")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSOPER" ;
  }

  $qry = "startup";

  #nomount
  if (defined($args{'nomount'}))
  {
    $qry .= " nomount ";
  }

  if (defined($args{'restrict'}))
  {
    $qry .= " restrict ";
  }

  if (defined($args{'pfile'}))
  {
    $qry .= " pfile=" . $args{'pfile'};
  }

  #mount
  if (defined($args{'mount'}))
  {
    $qry .= " mount ";
  }

  # untaint sqlplus
  $sqlplus_stmt =~ m/(.*)/;
  $sqlplus_stmt = $1;
  
  unlink $tempoutputfile if(-e $tempoutputfile);
  open SQLPLUS, "| $sqlplus_stmt > $tempoutputfile";
  print SQLPLUS $qry;
  close SQLPLUS;

  eval
  {
      open($tempfilehandle, "<$tempoutputfile") or 
                        die "Could not open the temporary file $tempoutputfile";
      my @lines = <$tempfilehandle>;
      close $tempfilehandle;
      unlink $tempoutputfile;
      foreach (@lines)
      {
         asmcmdshare_print $_;
         if($_ =~ m/[A-Z]+-[0-9]+\s*:/)
         {
            $asmcmdglobal_hash{'e'} = -1;
         }
      }
  };
  asmcmdshare_trace(3, "$qry", 'y', 'n');

  return;
}

########
# NAME
#   asmcmdsys_process_shutdown
#
# DESCRIPTION
#   This function processes the asmcmd command shutdown.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_shutdown
{
  my ($dbh)  = shift;
  my ($tgt)  = undef;                                      # Target instance. #
  my ($sid)  = undef;                                      # Target's SID.    #
  my ($osid) = undef;
  my ($inst) = undef;
  my (%args);                              # Argument hash used by getopts(). #
  my ($ret);                       # asmcmdsys_parse_int_args() return value. #
  my ($qry);                                           # SQL query statement. #
  my ($sqlplus_stmt);
  
  my ($tempoutputfile) = $asmcmdglobal_hash{'tempdir'} . "/shutdowntemp.out";
  my ($tempfilehandle);
  my (@lines) = ();
  my (@what);
  my (@from);
  my ($sth);
  my ($rc);

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);
  
  if (defined($args{'target'}))
  {
    $tgt = $args{'target'};
    $sid = asmcmdshare_set_instance($tgt);
    if (!defined($sid))
    {
      my (@eargs) = ("target", $tgt);
      asmcmdshare_error_msg(8608, \@eargs);
      return 0;
    }

    return 0 if ($sid eq '');
  }

  #bug 6994980
  delete $ENV{'ENV'} if (defined($ENV{'ENV'}));


  if ($asmcmdglobal_hash{'contyp'} eq "sysasm")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSASM" ;
  }
  if ($asmcmdglobal_hash{'contyp'} eq "sysdba")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSDBA" ;
  }
  if ($asmcmdglobal_hash{'contyp'} eq "sysoper")
  {
    $sqlplus_stmt = "$ASMCMDSYS_SQLPLUS_SYSOPER" ;
  }

  $qry = "SHUTDOWN ";

  if (defined($args{'immediate'}))
  {
    $qry .= "IMMEDIATE ";
  }

  elsif (defined($args{'abort'}))
  {
    $qry .= "ABORT ";
  }

  else
  {
    $qry .= "NORMAL ";
  }

  # untaint sqlplus
  $sqlplus_stmt =~ m/(.*)/;
  $sqlplus_stmt = $1;

  if (defined($dbh))
  {
    asmcmdbase_disconnect($dbh);
  }

  if (defined($sid))
  {
    $osid = $ENV{'ORACLE_SID'};
    $ENV{'ORACLE_SID'} = $sid;
  }

  unlink $tempoutputfile if(-e $tempoutputfile);
  open SQLPLUS, "| $sqlplus_stmt > $tempoutputfile";
  print SQLPLUS $qry;
  close SQLPLUS;

  eval
  {
    open($tempfilehandle, "<$tempoutputfile") or 
      die "Could not open the temporary file $tempoutputfile";
    my @lines = <$tempfilehandle>;
    close $tempfilehandle;
    unlink $tempoutputfile;
    foreach (@lines)
    {
      asmcmdshare_print $_;
      if ($_ =~ m/[A-Z]+-[0-9]+\s*:/)
      {
        $asmcmdglobal_hash{'e'} = -1;
      }
    }
  };
  asmcmdshare_trace(3, "$qry", 'y', 'n');

  # Restore original value of $ORACLE_SID and reconnect to ASM instance
  if (defined($osid))
  {
    $ENV{'ORACLE_SID'} = $osid;
    $dbh = asmcmdbase_connect(undef);
  }

  return;
}

########
# NAME
#   asmcmdsys_process_lsop
#
# DESCRIPTION
#   This function processes the asmcmd command lsop.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_lsop
{
  my ($dbh) = shift;
  my (@what , @from, $sth, @where, @order, @binds);
  my (%min_col_wid, $print_format, $print_string, @what_print);
  my ($k, $v, @op_list, $h);
  my ($row);
  my (%args);
  my (@tmp_cols);  
  my $gnum; 
  my $ret;
  my @eargs;
  my $dgname;

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);
  
  asmcmdshare_trace(3, "ASMCMD (PID = $$) - LSOP Command\n", 'y', 'n');

  push (@what, 'v$asm_diskgroup_stat.name as name');
  push (@what, 'v$asm_operation.pass as pass');
  push (@what, 'v$asm_operation.state as state');
  push (@what, 'v$asm_operation.power as power');
  push (@what, 'v$asm_operation.est_work as est_work');
  push (@what, 'v$asm_operation.est_rate as est_rate');
  push (@what, 'v$asm_operation.est_minutes as est_minutes');
 
  push (@from, 'v$asm_operation');
  push (@from, 'v$asm_diskgroup_stat');

  push (@where, 'v$asm_operation.group_number = v$asm_diskgroup_stat.group_number');

  if( defined($args{'G'}) )
  {
    $dgname = $args{'G'};
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $dgname);
    if( $gnum )
    {
      # Use the name, rather than the number. Doing so preserves the order
      # of v$asm_operation
      push (@where, 'v$asm_diskgroup_stat.name = ?');
      push (@binds, [uc($dgname), SQL_VARCHAR]);
    }

    else
    {
      @eargs = ($dgname); 
      asmcmdshare_error_msg(8001, \@eargs);
      return;
    }
  }

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where, 
                                         \@order, \@binds);

  @tmp_cols = @{$sth->{NAME}};
  @what = ();
  foreach (@tmp_cols)
  {
    push (@what, "\L$_");
  }

  #initialize the min_col_wid array
  foreach(@what)
  {
    $min_col_wid{$_} = 0;
  }

  while (defined($row = asmcmdshare_fetch($sth)))
  {
    my(%op_info) = ();

    while(($k, $v) = each(%{$row}))
    {
      $k =~ tr/[A-Z]/[a-z]/;
      if(!defined($v))
      {
        $v="";
      } 
      $op_info{$k} = $v;

      $min_col_wid{$k} = max($min_col_wid{$k}, length($v));
    }

    push (@op_list, \%op_info);
  }

  #get header length
  foreach (@what)
  {
    $min_col_wid{$_} = max($min_col_wid{$_},
                           length($asmcmdsys_lsop_header{$_}));
  }

  $print_format = '';

  foreach (@what)
  {
    $print_format .= "%-" . $min_col_wid{$_} . "s  ";
  }
  $print_format .= "\n";

  #print header
  if (!defined ($args{'suppressheader'}) )
  {
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, $asmcmdsys_lsop_header{$_});
    }
    $print_string = sprintf($print_format, @what_print);
    asmcmdshare_trace(3, "ASMCMD (PID = $$) - LSOP Command - $print_string\n", 'y', 'n');
    asmcmdshare_print($print_string);
  }
  
  #print rows
  foreach $h (@op_list)
  {
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, $h->{$_});
    }
    $print_string = sprintf($print_format, @what_print);
    asmcmdshare_trace(3, "ASMCMD (PID = $$) - LSOP Command - $print_string\n", 'y', 'n');
    asmcmdshare_print($print_string);
  }
  
  asmcmdshare_trace(3, "ASMCMD (PID = $$) - LSOP Command Finished\n", 'y', 'n');
  return;
}


########
# NAME
#   asmcmdsys_process_spcopy
#
# DESCRIPTION
#   This function processes the asmcmd command spcopy.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
#   The source file type can be ASM SP FILE or ASM BACKUP FILE 
#   The destination file type is always ASM SP FILE 
#   When the source file is on a file system, one cannot distinguish between 
#   various SP file types and has to accept any one of them
########
sub asmcmdsys_process_spcopy
{
  my ($dbh) = shift;
  my ($src_path, $dst_path, $src_name, $dst_name);
  my ($sth);
  my ($hdl, $pblk, $fname, $ftyp, $blksz, $openmode);
  my (%norm, $ret);
  my ($update_gpnp) = 0;
  my ($pl_sql_number) = 22;
  my ($spfile_number) = 253;
  my ($spfile_type)   = 31; # KSFD_ASMINIT
  my ($spbackup_type) = 33; # KSFD_ASMINITBAK
  my ($db_spfile_type) = 13; # KSFD_INIT
  my (%args);
  my ($fileType, $blkSz, $fileSz);
  my ($client_mode) = 0;
  my (@eargs);

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  # SPCOPY operation for ASM SPFILE requires SYSASM
  if ($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    asmcmdshare_error_msg (9485, undef);
    return;
  }

  $src_path = shift(@{$args{'spcopy'}});
  $src_path =~ /(.*)/;
  $src_path = $1;
 
  $src_name = $src_path;
  #Match the last part in the src_path which is source file name
  #seperator is '/' for linux path and '\' for NT 
  $src_name =~ s,(.*/|.*\\)(.*)$,$2,;  
  
  $dst_path = shift(@{$args{'spcopy'}});

  # complete relative paths
  # paths begining with "/"(linux) or containing ":\" (NT) are OS paths
  if ($src_path !~ m':\\' and $src_path !~ m'^/')
  {
    $src_path = asmcmdshare_make_absolute($src_path);
  }

  if ($dst_path !~ m':\\' and $dst_path !~ m'^/')
  {
    # Target is ASM
    asmcmdsys_get_target_name($dbh,$src_name,$dst_path,\$dst_name);
  }
  else
  {
    # target is OS
    # if path exists it must be a directory
    if ( -d $dst_path )
    {
      $dst_name = $dst_path . '/' . $src_name;
    }
    else
    {
      $dst_name = $dst_path;
    }
  }
   
  if (!defined $src_path || !defined $dst_name)
  {
    asmcmdshare_trace(3, "NOTE: Either src_path or dst_name is not defined",
                        'n', 'n');
    return;
  }

  $update_gpnp = 1 if defined($args{'u'});
  
  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.getfileattr(:src_path, :fileType, :fileSz, :blkSz);
     end;
     });

  # bind input params #
  $sth->bind_param( ":src_path", $src_path);

  # bind output params #
  $sth->bind_param_inout( ":fileType", \$fileType, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER);

  $ret = $sth->execute();

  if (!defined ($ret) && ($asmcmdglobal_hash{'mode'} eq 'n'))
  { 
    $asmcmdglobal_hash{'e'} = -1;
  }

  if (!defined $fileType || !defined $fileSz || !defined $blkSz
      || (($fileType != $spfile_type) && 
          ($fileType != $spbackup_type) && 
          ($fileType != $db_spfile_type)))
  {
    @eargs = ( $src_path );
    asmcmdshare_error_msg(8303, \@eargs);
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') if(defined($DBI::errstr));
    return;
  }

  # reconnect
  asmcmdbase_disconnect($dbh) if defined ($dbh);

  #undef since we connect to local asm instance
  $dbh = asmcmdbase_connect(undef);

  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.asmcopy(:src_path, :dst_name, :spfile_number, 
                              :fileType, :blkSz, :spfile_number2, 
                              :dst_ftype, :client_mode);
     exception when others then
       raise;
     end;
     });

  $sth->bind_param( ":src_path", $src_path);
  $sth->bind_param( ":dst_name", $dst_name);
  $sth->bind_param( ":spfile_number", $spfile_number);
  $sth->bind_param( ":fileType", $fileType);
  $sth->bind_param( ":blkSz", $blkSz);
  $sth->bind_param( ":spfile_number2", $spfile_number);
  $sth->bind_param( ":dst_ftype", $spfile_type);
  $sth->bind_param( ":client_mode", $client_mode);

  $ret = $sth->execute();
  if (!defined($ret))
  {
    if ($asmcmdglobal_hash{'mode'} eq 'n')	
    {	
      $asmcmdglobal_hash{'e'} = -1;	
    }	 
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
    return;
  }

  if ($update_gpnp)
  {

    $ret = asmcmdsys_spset($dbh, $dst_name);

    if (!defined($ret))
    {
      asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
      return;
    }
    asmcmdshare_trace(3, "NOTE: spfile $dst_name location has been set in"
                     ." gpnp profile ", 'n', 'n');
  }

  return;
}


########
# NAME
#   asmcmdsys_process_spmove
#
# DESCRIPTION
#   This function processes the asmcmd command spmove.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
#   The source file type can be ASM SP FILE 
#   When the source file is on a file system, one cannot distinguish between 
#   various SP file types and has to accept any one of them
########
sub asmcmdsys_process_spmove
{
  my ($dbh) = shift;
  my ($attr, $val);
  my ($sth);
  my ($src_path, $dst_path, $src_name, $dst_name,$spfile);
  my (%norm, $ret);
  my ($update_gpnp) = 0;
  my ($pl_sql_number) = 22;
  my ($spfile_number) = 253;
  my ($spfile_type)   = 31; # KSFD_ASMINIT
  my ($db_spfile_type) = 13; # KSFD_INIT
  my (%args);
  my ($fileType, $blkSz, $fileSz);
  my ($client_mode) = 0;
  my ($gname);
  my (@eargs);

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  # SPMOVE operation for ASM SPFILE requires SYSASM privilege
  if($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    asmcmdshare_error_msg (9485, undef);
    return;
  }

  $src_path = shift(@{$args{'spmove'}});
  $src_path =~ /(.*)/;
  $src_path = $1;

  $src_name = $src_path;
  #Match the last part in the src_path which is source file name
  #seperator is '/' for linux path and '\' for NT
  $src_name =~ s,(.*/|.*\\)(.*)$,$2,;

  $dst_path = shift(@{$args{'spmove'}});

  # complete relative paths
  # paths begining with "/"(linux) or containing ":\" (NT) are OS paths
  if($src_path !~ m':\\' and $src_path !~ m'^/')
  {
    $src_path = asmcmdshare_make_absolute($src_path);
  }

  if($dst_path !~ m':\\' and $dst_path !~ m'^/')
  {
    # Target is ASM
    asmcmdsys_get_target_name($dbh,$src_name,$dst_path,\$dst_name);
  }
  else
  {
    # target is OS
    # if path exists it must be a directory
    if ( -d $dst_path )
    {
      $dst_name = $dst_path . '/' . $src_name;
    }
    else
    {
      $dst_name = $dst_path;
    }
  }

  return if (!defined $src_path || !defined $dst_name);

  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.getfileattr(:src_path, :fileType, :fileSz, :blkSz);
     end;
     });

  # bind input params #
  $sth->bind_param( ":src_path", $src_path);

  # bind output params #
  $sth->bind_param_inout( ":fileType", \$fileType, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER);

  $ret = $sth->execute();
  if ((!defined ($ret))  && $asmcmdglobal_hash{'mode'} eq 'n')	
  {	
     $asmcmdglobal_hash{'e'} = -1;	
  }	
  
  if (!defined $fileType || !defined $fileSz || !defined $blkSz
      || (($fileType != $spfile_type) && 
          ($fileType != $db_spfile_type)))
  {
    @eargs = ( $src_path );
    asmcmdshare_error_msg(8303, \@eargs);
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') if(defined($DBI::errstr));
    return;
  }

  # reconnect
  asmcmdbase_disconnect($dbh) if defined ($dbh);

  #undef since we connect to local asm instance
  $dbh = asmcmdbase_connect(undef);

  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.asmcopy(:src_path, :dst_name, :spfile_number, 
                              :fileType, :blkSz, :spfile_number2,
			      :dst_ftype, :client_mode);
     exception when others then
       raise;
     end;
     });

  $sth->bind_param( ":src_path", $src_path);
  $sth->bind_param( ":dst_name", $dst_name);
  $sth->bind_param( ":spfile_number", $spfile_number);
  $sth->bind_param( ":fileType", $fileType);
  $sth->bind_param( ":blkSz", $blkSz);
  $sth->bind_param( ":spfile_number2", $spfile_number);
  $sth->bind_param( ":dst_ftype", $spfile_type);
  $sth->bind_param( ":client_mode", $client_mode);

  $ret = $sth->execute();
  if (!defined($ret))
  {
    if ($asmcmdglobal_hash{'mode'} eq 'n')
    {
       $asmcmdglobal_hash{'e'} = -1;
    }
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
    return;
  }

  # if the current spfile is the active one, update gpnp
  $spfile = asmcmdsys_dualget($dbh, 'asm_spfile');

  if (!defined($spfile) or $spfile ne $dst_name)
  {
    asmcmdshare_trace(3, "NOTE: The current spfile is the active one."
        ." Updating gpnp profile to $dst_name", 'n', 'n');
    $ret = asmcmdsys_spset($dbh, $dst_name);
    if (!defined($ret))
    {
      asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
     
      # UNDO the move if GPnP update is not successful 
      if($dst_name =~ m'^\+')  
      {
        %norm = asmcmdshare_normalize_path($dbh, $src_path, 0, \$ret);
        $gname = asmcmdshare_get_gname_from_ gnum ($dbh, $norm{'gnum'}->[0]);
        asmcmdbase_rm_sql($dbh, $gname, $norm{'path'}->[0], 0);
      }
      else
      {
        #os File
        #Untaint
        $dst_name =~ m/(.*)/;
        $dst_name = $1;
 
        unlink("$dst_name");
      }
      return;
    }
  }
  asmcmdbase_disconnect($dbh) if defined ($dbh);

  #undef since we connect to local asm instance
  $dbh = asmcmdbase_connect(undef);

  #delete the source file
  asmcmdshare_trace(3, "NOTE: Deleting the source file ", 'n', 'n');
  if ($src_path =~ m'^\+')
  {
    # asm spfile
    %norm = asmcmdshare_normalize_path($dbh, $src_path, 0, \$ret);  
    $gname = asmcmdshare_get_gname_from_gnum ($dbh, $norm{'gnum'}->[0]);
    asmcmdbase_rm_sql($dbh, $gname, $norm{'path'}->[0], 0);
  }
  else
  {
    #os spfile
    unlink($src_path);
  }
}


########
# NAME
#   asmcmdsys_process_spbackup
#
# DESCRIPTION
#   This function processes the asmcmd command spbackup.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_spbackup
{
  my ($dbh) = shift;
  my (%args, $ret);
  my ($src_path, $dst_path,$src_name,$dst_name);
  my ($sth, $fileType, $fileSz, $blkSz);
  my ($spfile_type)    = 31;
  my ($db_spfile_type) = 13;
  my ($spbackup_type)  = 33;
  my ($client_mode)    = 0;
  my (@eargs);

  # Get option parameters, if any.
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  # SPBACKUP operation for ASM SPFILE requires SYSASM privilege
  if($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    asmcmdshare_error_msg (9485, undef);
    return;
  }

  $src_path = shift(@{$args{'spbackup'}});
  $src_path =~ /(.*)/;
  $src_path = $1;

  $src_name = $src_path;
  #Match the last part in the src_path which is source file name
  #seperator is '/' for linux path and '\' for NT
  $src_name =~ s,(.*/|.*\\)(.*)$,$2,;

  $dst_path = shift(@{$args{'spbackup'}});
  
  # complete relative paths
  # paths begining with "/"(linux) or containing ":\" (NT) are OS paths
  if($src_path !~ m':\\' and $src_path !~ m'^/')
  {
    $src_path = asmcmdshare_make_absolute($src_path);
  }

  if($dst_path !~ m':\\' and $dst_path !~ m'^/')
  {
    asmcmdsys_get_target_name($dbh,$src_name,$dst_path,\$dst_name);
  }
  else
  {
      # target is OS
      # if path exists it must be a directory
      if ( -d $dst_path )
      {
        $dst_name = $dst_path . '/' . $src_name;
      }
      else
      {
        $dst_name = $dst_path;
      }
  }
 
  if (!defined $src_path || !defined $dst_name)
  {
    asmcmdshare_trace(3, "NOTE: Either src_path or dst_name is not defined",
                                                    'n', 'n');
    return;
  }

  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.getfileattr(:src_path, :fileType, :fileSz, :blkSz);
     end;
     });

  # bind input params #
  $sth->bind_param( ":src_path", $src_path);

  # bind output params #
  $sth->bind_param_inout( ":fileType", \$fileType, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER);
  $sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER);

  $ret = $sth->execute();

  if ((!defined ($ret))  && $asmcmdglobal_hash{'mode'} eq 'n')	
  {	
    $asmcmdglobal_hash{'e'} = -1;	
  }	

  if (!defined $fileType || !defined $fileSz || !defined $blkSz 
      || ($fileType != $spfile_type && $fileType != $db_spfile_type))
  {
    @eargs = ( $src_path );
    asmcmdshare_error_msg(8303, \@eargs);
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') if(defined($DBI::errstr));
    return;
  }
  
  # reconnect
  asmcmdbase_disconnect($dbh) if defined ($dbh);

  #undef since we connect to local asm instance
  $dbh = asmcmdbase_connect(undef);

  $sth = $dbh->prepare(q{
     begin
       dbms_diskgroup.asmcopy(:src_path, :dst_name, :spfile_number, 
                              :fileType, :blkSz, :dst_fnum, :dst_ftype,
			      :client_mode);
     exception when others then
       raise;
     end;
     });

  $sth->bind_param(":src_path", $src_path);
  $sth->bind_param(":dst_name", $dst_name);
  $sth->bind_param(":spfile_number", 0);
  $sth->bind_param(":fileType", $fileType);
  $sth->bind_param(":blkSz", $blkSz);
  $sth->bind_param(":dst_fnum", 0);
  $sth->bind_param(":dst_ftype", $spbackup_type);
  $sth->bind_param(":client_mode", $client_mode);

  $ret = $sth->execute();

  if (!defined($ret))
  {
    if($asmcmdglobal_hash{'mode'} eq 'n')	
    {	
      $asmcmdglobal_hash{'e'} = -1;	
    }	
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
    return;
  }

  asmcmdbase_disconnect($dbh) if defined ($dbh);

  #undef since we connect to local asm instance
  $dbh = asmcmdbase_connect(undef);
}



########
# NAME
#   asmcmdsys_get_target_name
#
# DESCRIPTION
#   This function gets the complete target file name when the
# destination is a directory or diskgroup for spcopy/spmove/spbackup.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   src_name (IN) - spfile name
#   dst_path (IN) - destination path
#   dst_name (IN/OUT) - destination pathname
#
# RETURNS
#   Null.
#
# NOTES
# 
########
sub asmcmdsys_get_target_name
{
  my ($dbh,$src_name,$dst_path,$dst_name) = @_;
  my (@paths, @ref_ids, @par_ids, @dg_nums, @entry_list, $name ,$ret);
  my (%norm);
  my (@eargs);
  
  # target is ASM
  # if target is a relative path, complete it to be a valid ASM path
  %norm = asmcmdshare_normalize_path($dbh, $dst_path, 1, \$ret);

  # path exists, must be a directory, and need to complete it with
  # the source name
  if ( $ret == 0 )
  {
    # complete the path
    $dst_path = $norm{'path'}->[0];
  
    @paths   = @{ $norm{'path'} };
    @ref_ids = @{ $norm{'ref_id'} };
    @par_ids = @{ $norm{'par_id'} };
    @dg_nums = @{ $norm{'gnum'} };
    $name = $paths[0];
    $name =~ s,.*/(.*)$,$1,;

    if($ref_ids[0] == -1)
    {
      @eargs = ($dst_path);
      asmcmdshare_error_msg(8014, \@eargs);
      return;
    }
    asmcmdshare_get_subdirs($dbh, \@entry_list, undef, $dg_nums[0],
                            $ref_ids[0], $par_ids[0], $name, undef, 0, 1);

    # if the directory is a diskgroup or a directory
    if ( ($ref_ids[0] != -1) && 
         (($ref_ids[0] == $dg_nums[0] << 24 ) ||
          ($entry_list[0]->{'alias_directory'} eq 'Y')))
    {
      if ($src_name =~ /^\S+\.\d+\.\d+$/)
      {
        asmcmdshare_trace(1, "ERROR: Unable to form destination file name.",
                          'y', 'y');
        asmcmdshare_trace(1, "ERROR: Source file name has file number and " .
                          "incarnation in it. Provide destination file name.",
                          'y', 'y');
        @eargs = ( $src_name );
        asmcmdshare_error_msg(8303, \@eargs);
        return ;
      }
      $$dst_name = $dst_path . '/' . $src_name;
    }
    elsif($entry_list[0]->{'alias_directory'} eq 'N')
    {
      $$dst_name = $dst_path;
    }
    else
    {
      @eargs = ($dst_path);
      asmcmdshare_error_msg(8014, \@eargs);
      
      return;
    }
   }
   else
   {
     $$dst_name = $dst_path;
   }
}


########
# NAME
#   asmcmdsys_process_spget
#
# DESCRIPTION
#   This function processes the asmcmd command spget.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_spget
{
  my ($dbh) = shift;
  my ($spfile);

  # SPGET operation for ASM SPFILE requires SYSASM privilege
  if($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    asmcmdshare_error_msg (9485, undef);
    return;
  }

  # If any options are added to the command, 
  # asmcmdsys_parse_int_args() should be called.
  # 
  # Get option parameters, if any.
  #$ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  #return unless defined ($ret);

  $spfile = asmcmdsys_dualget($dbh, 'asm_spfile'); 

  if (defined($spfile))
  {
    asmcmdshare_print($spfile ."\n");
  }
}


########
# NAME
#   asmcmdsys_process_dsset
#
# DESCRIPTION
#   This function processes the asmcmd command dsset.
#
#  USAGE
#   dsset [--normal] [--parameter] [--profile [-f]] pathname
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   second arg to gpnpsetds is false so that user supplied diskstring
#   gets pushed to gpnp profile.
#########
sub asmcmdsys_process_dsset
{
  
  my ($dbh) = @_;
  my ($ret, %args, $sth); 
  my ($forced);
  my ($qry);
  my ($gpnptool);
  my ($gpnp_sign);
  my ($gpnp_edit);
  my ($gpnp_seq);
  my ($prof_path);
  my ($peer_path);
  my ($seq_num);
  my ($buf) = "";
  my ($normal);
  my ($num);
  my ($test);
  my ($status) = "";
  my ($crs_home);
  my ($crsctl);
  my ($srvctl);
  my ($update);
  my ($path) = 0;
  my (@buf_arr);
  my ($check_siha);
  my (@eargs);

  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  $forced = 0;
  $normal = 0;

  if (defined($args{'normal'}) || 
      (!defined($args{'parameter'}) && !defined($args{'profile'})))
  {
    $normal = 1;
  }

  if (defined($args{'f'}))
  {
    $forced = 1;
  }
  $path = join(',', @{$args{'dsset'}});
  # asm_diskstring should be of the format 'path1','path2','path3',.....
  $path =~ s/,/','/g;

  #ASM instance is required for all the options other than '--profile -f'
  if (!defined($dbh))
  {
    if (!(defined($args{'profile'}) && $forced == 1))
    {
      asmcmdshare_error_msg(8102, undef);
      return;
    }
  }

  # Problem:  If dsset is used multiple times in a asmcmd session
  # pathname gets messed up. Reason being, the buffer cache used for
  # storing pathname in SQL is not cleared on every call.
  # This workaround could be removed if the PL/SQL buffer problem is 
  # fixed.
  if (defined($dbh))
  {
    if (!(defined($args{'profile'}) && $forced == 1 ))
    {
      # If connection to ASM exists, and "--profile -f" not specified, then
      # disconnect and connect.
      asmcmdbase_disconnect ($dbh);
      $dbh = asmcmdbase_connect (undef)
    }
  }

  if ($normal == 1)
  {
      
    $qry = "alter system set asm_diskstring='" . $path . "' SCOPE=BOTH";
      
    $ret = asmcmdshare_do_stmt($dbh,$qry);

    return;
  }

  if (defined($args{'profile'}))
  {
    if ($forced == 1)
    {
      $crsctl = "$ENV{'ORACLE_HOME'}/bin/crsctl";
      #The path in WIN is ORACLE_HOME/bin/crsctl.exe
      $crsctl .= ".exe" if ($^O =~ /win/i);

      # Make sure the crsctl binary exists and can be executed. 
      if (! -x $crsctl)
      {
        #If not, try at a second locaction.
        $crsctl = "$ENV{'ORACLE_HOME'}/rdbms/bin/crsctl";
        $crsctl .= ".exe" if ($^O =~ /win/i);

        if (! -x $crsctl)
        {
          @eargs = ($crsctl);
          asmcmdshare_error_msg(8313, \@eargs);
          return;
        }
      }

      #untaint crsctl
      $crsctl =~ /([^\n^\r^\t]+)/;
      $crsctl = $1;
     
      asmcmdshare_trace(3, "NOTE: Checking the status of cluster.. ", 'y', 'n');
      if (asmcmdshare_runcmd("$crsctl check css", \@buf_arr, 1, 0))
      {
        asmcmdshare_error_msg(8309, undef);
        return;
      }

      # Check for the number at CRS-xxxx: in the output.
      foreach (@buf_arr) 
      {
        $status = $2 if (/([^\d]+)([^:]+)/);
        last if (defined($status) and $status);
      }
      if (defined($status) and $status)
      {
        if ($status eq 4529)
        {
          #output is CRS-4529: Cluster Synchronization Services is online
          asmcmdshare_error_msg(8308, undef);
          return;
        }
      }
      else
      {
        asmcmdshare_error_msg(8309, undef);
        return;
      }

      $check_siha = "$crsctl status resource ora.crsd -init -g";
        
      if (asmcmdshare_runcmd($check_siha, \@buf_arr, 1, 0))
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }

      # Get the error code form the o/p
      foreach (@buf_arr) 
      {
        if (/([^\d]+)([^:]+)/)
        {
          $status = $2;
          last;
        }
      }
      if (!defined($status) or !$status)
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }

      # siha mode, crsd is not registered
      # CRS-212: Resource 'ora.crsd' is not registered.
      if ($status eq 212)
      {
        $srvctl = "$ENV{'ORACLE_HOME'}/bin/srvctl";
        #path in Win is ORACLE_HOME/bin/srvctl.bat
        $srvctl .= ".bat" if ($^O =~ /win/i);

        # Make sure the srvctl binary exists and can be executed. 
        if (! -x $srvctl)
        {
          #If not, try at a second locaction.
          $srvctl = "$ENV{'ORACLE_HOME'}/srvm/bin/srvctl";
          #path in Win is ORACLE_HOME/bin/srvctl.bat
          $srvctl .= ".bat" if ($^O =~ /win/i);

          if (! -x $srvctl)
          {
            @eargs = ($srvctl);
            asmcmdshare_error_msg(8316, \@eargs);
            return;
          }
        }

        $update = " $srvctl modify asm -d " . $path;
        $update =~ /([^\n^\r^\t]+)/;
        $update = $1;

        if (asmcmdshare_runcmd($update, \@buf_arr, 1, 1))
        {
          asmcmdshare_error_msg(8314, undef); 
        }

        return;
      }
      # cluster mode
      # CRS-4003 :Resource 'ora.crsd' is registered. 
      elsif ($status eq 4003)
      {
        #HOSTNAME should be defined
        $buf = asmcmdshare_get_host_name();
        if (!defined($buf))
        {
          $buf = "HOSTNAME";
          @eargs = ($buf);
          asmcmdshare_error_msg(8318, \@eargs);
          return;
        }

        $gpnptool = "$ENV{'ORACLE_HOME'}/bin/gpnptool"; 
        # Adjust path for win
        $gpnptool .= ".exe" if ($^O =~ /win/i);

        # Make sure the gpnptool binary exists and can be executed. 
        if (! -x $gpnptool)
        {
          #If not, try at a second locaction.
          $gpnptool = "$ENV{'ORACLE_HOME'}/has/bin/gpnptool";
          # Adjust path for win
          $gpnptool .= ".exe" if($^O =~ /win/i);

          if (! -x $gpnptool)
          {
            @eargs = ($gpnptool);
            asmcmdshare_error_msg(8305, \@eargs);
            return;
          }
        }

        # The location of profile.xml is different for production and
        # dev environment now. The current environment is being determined by
        # checking whethe ADE_VIEW_ROOT is set or not. Bug 8579922 is filed
        # for this issue.

        $test = "$ENV{'ORACLE_HOME'}/gpnp/".
                 asmcmdshare_get_host_name().
                "/profiles/peer/profile.xml";

        if ( -f $test) #production environment
        {
          $crs_home = "$ENV{'ORACLE_HOME'}";
        }
        else   # dev env
        {
          $test = "$ENV{'ORACLE_HOME'}/has_work/gpnp/".
                   asmcmdshare_get_host_name().
                  "/profiles/peer/profile.xml";
          if (-f $test)
          {
            $crs_home = "$ENV{'ORACLE_HOME'}/has_work";
          }
          else
          {
            asmcmdshare_error_msg(8319,undef);
            return;
          }
        }

        #if(!defined($ENV{'ADE_VIEW_ROOT'}))
        #{
        #  $crs_home = "$ENV{'ORACLE_HOME'}";
        #}
        #else
        #{
        #  $crs_home = "$ENV{'ORACLE_HOME'}/has_work";
        #}
        
        $prof_path = "$crs_home/gpnp/".
                      asmcmdshare_get_host_name ().
                     "/profiles/peer/profile.xml";

        #untaint prof_path
        $prof_path =~ /([^\n^\r^\t]+)/;
        $prof_path = $1;

        $peer_path = "$crs_home/gpnp/".
                      asmcmdshare_get_host_name().
                     "/wallets/peer";
        
        #untaint peer_path
        $peer_path =~ /([^\n^\r^\t]+)/;
        $peer_path = $1;

        # get the sequence number of the profile.
        $gpnp_seq = "$gpnptool getpval -p=$prof_path -prf_sq -o-";
        
        #untaint gpnp_seq
        $gpnp_seq =~ /([^\n^\r^\t]+)/;
        $gpnp_seq = $1;

        asmcmdshare_trace(3, "NOTE: Setting the gpnp profile sequence "
                                 ."number.. ", 'n', 'n'); 

        if (asmcmdshare_runcmd($gpnp_seq, \@buf_arr, 0, 0))
        {
          asmcmdshare_error_msg(8310, undef);
        }

        $seq_num = $buf_arr[0] + 3;

        $gpnp_edit = "$gpnptool edit -p=$prof_path -o=$prof_path -ovr " .
                      "-asm_dis=$path -prf_sq=$seq_num";
        
        #untaint gpnp_edit
        $gpnp_edit =~ /([^\n^\r^\t]+)/;
        $gpnp_edit = $1;

        asmcmdshare_trace(3, "NOTE: Editing the gpnp profile.. ", 'n', 'n');
        if (asmcmdshare_runcmd($gpnp_edit, \@buf_arr, 1, 1))
        {
          asmcmdshare_error_msg(8306, undef);
          return;
        }

        $gpnp_sign = "$gpnptool sign -p=$prof_path -o=$prof_path -ovr " .
                     "-w=file:$peer_path";

        #untaint gpnp_sign
        $gpnp_sign =~ /([^\n^\r^\t]+)/;
        $gpnp_sign = $1;

        asmcmdshare_trace(3, "NOTE: Signing the gpnp profile.. ", 'n', 'n');
        # sign the gpnp profile once its edited.
        if (asmcmdshare_runcmd($gpnp_sign, \@buf_arr, 0, 1))
        {
          asmcmdshare_error_msg(8307, undef);
          return;
        }
      }
      # could not determine mode
      else
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }
    }
    else
    {
      $sth = $dbh->prepare(q{
             begin
               dbms_diskgroup.gpnpsetds(:ds_path,0);
             exception when others then
                raise;
             end;
           });

      $sth->bind_param(":ds_path", $path);

      $ret = $sth->execute();  

      if (!defined ($ret))
      {
        if ($asmcmdglobal_hash{'mode'} eq 'n')
        {
          $asmcmdglobal_hash{'e'} = -1;
        }
        asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
      }
    }
    return;
  }

  #parameter option.
  if (defined($args{'parameter'}))
  {
    $qry = "alter system set asm_diskstring='" . $path . "' SCOPE=MEMORY";
      
    $ret = asmcmdshare_do_stmt($dbh,$qry);

    return;
  }
}

########
# NAME
#   asmcmdsys_process_dsget
#
# DESCRIPTION
#   This function processes the asmcmd command dsget.
#
# USAGE
#   dsget [[--normal] [--profile [-f]] [--parameter]]
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
#########
sub asmcmdsys_process_dsget
{
  my ($dbh) = shift;
  my ($diskstring, $sth, $ret, $row, $val);
  my (@what, @from, @where, @order, @binds);
  my ($num);
  my ($buf);
  my (@buf_arr);
  my ($test);
  my ($parameter);
  my ($profile);
  my ($forced);
  my ($status);
  my (%args);
  my ($gpnptool);
  my ($gpnp_exec);
  my ($prof_path);
  my ($crs_home);
  my ($crsctl);
  my ($srvctl);
  my ($get_dis);
  my (@eargs);

  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); 
  return unless defined ($ret);

  $num       = keys(%args);
  $forced    = 0;
  $parameter = 0;
  $profile   = 0;

  if (defined($args{'f'}))
  {
    $forced = 1;
  }

  # normal mode should return both profile and parameter settings.
  # normal is the default option
  if (defined($args{'normal'}) || 
      (!defined($args{'profile'}) && !defined($args{'parameter'})))
  {
    $parameter = 1;
    $profile = 1;
  }
  elsif (defined($args{'profile'}))
  {
    $profile = 1;
  }
  elsif (defined($args{'parameter'}))
  {
    $parameter = 1;
  }

  #ASM instance is required for all the options other than '--profile -f'
  if (!defined($dbh))
  {
    if (!(defined($args{'profile'}) && $forced == 1))
    {
      asmcmdshare_error_msg(8102, undef);
      return;
    }
  }

  if ($parameter == 1)
  {
    push(@what, 'value');
    push(@from, 'v$parameter');
    push(@where, 'name = ?');
    push(@binds, ['asm_diskstring', SQL_VARCHAR]);
  
    $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where,
                                           \@order, \@binds);

    $row = asmcmdshare_fetch($sth);
    asmcmdshare_finish($sth);

    $val = $row->{VALUE};

    #A row is always returned because asm_diskstring always exists.
    #If the value of row is NULL, then DBI returns undefined.
    #If the value of row is "", then DBI returns "".
    #It looks like empty string("") is being stored as NULL and hence DBI 
    #returns undefined value.
      
    #Adding code below to display empty string in dsget.
    $val = "" if !defined($val);
      
    if (defined($val))
    {
      asmcmdshare_print("parameter:" . $val . "\n");
    }
  }

  if ($profile == 1)
  { 
    # force with profile option.
    if ($forced == 1)
    {
      $crsctl = "$ENV{'ORACLE_HOME'}/bin/crsctl";
      #The path in WIN is ORACLE_HOME/bin/crsctl.exe
      $crsctl .= ".exe" if ($^O =~ /win/i);

      # Make sure the crsctl binary exists and can be executed. 
      if (! -x $crsctl)
      {
        #If not, try at a second locaction.
        $crsctl = "$ENV{'ORACLE_HOME'}/rdbms/bin/crsctl";
        #The path in WIN is ORACLE_HOME/bin/crsctl.exe
        $crsctl .= ".exe" if ($^O =~ /win/i);

        if (! -x $crsctl)
        {
          @eargs = ($crsctl);
          asmcmdshare_error_msg(8313, \@eargs);
          return;
        }
      }

      #untaint crsctl
      $crsctl =~ /([^\n^\r^\t]+)/;
      $crsctl = $1;

      asmcmdshare_trace(3, "NOTE: Checking the status of cluster.. ", 'y', 'n');
      if (asmcmdshare_runcmd("$crsctl check css", \@buf_arr, 1, 0))
      {
        asmcmdshare_error_msg(8309, undef);
        return;
      }

      # Check for the number at CRS-xxxx in the output.
      # Could not check cluster status if anything fails.
      foreach (@buf_arr) 
      {
        if (/([^\d]+)([^:]+)/) 
        {
          $status = $2;
          last;
        }
      }
      if (defined($status) and $status)
      {
        if($status eq 4529)
        {
          # CRS-4529: Cluster Synchronization Services is online
          asmcmdshare_error_msg(8308, undef);
          return;
        }
      }
      else
      {
        asmcmdshare_error_msg(8309, undef);
        return;
      }

      # gpnpd does not come up in SIHA mode
      if (asmcmdshare_runcmd("$crsctl status resource ora.crsd -init -g",
                              \@buf_arr, 1, 0))
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }

      #get the error code
      foreach (@buf_arr) 
      {
        if (/([^\d]+)([^:]+)/)
        {
          $status = $2;
          last;
        }
      }
      if (!defined($status) or !$status)
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }

      # siha mode, crsd is not registered        
      if($status eq 212)
      {
        $srvctl = "$ENV{'ORACLE_HOME'}/bin/srvctl";
        #path in Win is ORACLE_HOME/bin/srvctl.bat
        $srvctl .= ".bat" if ($^O =~ /win/i);

        # Make sure the srvctl binary exists and can be executed. 
        if (! -x $srvctl)
        {
          #If not, try at a second locaction.
          $srvctl = "$ENV{'ORACLE_HOME'}/srvm/bin/srvctl";
          #path in Win is ORACLE_HOME/bin/srvctl.bat
          $srvctl .= ".bat" if ($^O =~ /win/i);

          if (! -x $srvctl)
          {
            @eargs = ($srvctl);
            asmcmdshare_error_msg(8316, \@eargs);
            return;
          }
        }

        #untaint srvctl
        $srvctl =~ /([^\n^\r^\t]+)/;
        $srvctl = $1; 
       
        if (asmcmdshare_runcmd("$srvctl config asm", \@buf_arr, 1, 1))
        {
          asmcmdshare_error_msg(8317, undef);
          return;
        }

        foreach (@buf_arr) 
        {
          if (/discovery string:([^\n]+)/)
          {
            $buf = $1;
            last;
          }
        }
        # Extract the diskstring from the output
        if (defined($buf) and $buf)
        {
          asmcmdshare_print("profile:" . $buf . "\n");
        }
        else
        {
          asmcmdshare_error_msg(8317, undef);
        }
        return;
      }
      #cluster mode
      elsif ($status eq 4003)
      {
        #HOSTNAME should be defined
        $buf = asmcmdshare_get_host_name ();
        if (!defined($buf))
        {
          $buf = "HOSTNAME";
          @eargs = ($buf);
          asmcmdshare_error_msg(8318, \@eargs);
          return;
        }

        $gpnptool = "$ENV{'ORACLE_HOME'}/bin/gpnptool";
        # Adjust path for win
        $gpnptool .= ".exe" if ($^O =~ /win/i);

        # Make sure the gpnptool binary exists and can be executed. 
        if (! -x $gpnptool)
        {
          #If not, try at a second locaction.
          $gpnptool = "$ENV{'ORACLE_HOME'}/has/bin/gpnptool";
          # Adjust path for win
          $gpnptool .= ".exe" if ($^O =~ /win/i);

          if (! -x $gpnptool)
          {
            @eargs = ($gpnptool);
            asmcmdshare_error_msg(8305, \@eargs);
            return;
          }
        } 
        
        # The location of profile.xml is different for production and
        # dev environment now. The current environment is being determined by
        # checking whethe ADE_VIEW_ROOT is set or not. Bug 8579922 is filed
        # for this issue.

        $test = "$ENV{'ORACLE_HOME'}/gpnp/".
                asmcmdshare_get_host_name ().
                "/profiles/peer/profile.xml";

        if ( -f $test)  #production env
        {
          $crs_home = "$ENV{'ORACLE_HOME'}";
        }
        else    #dev env
        {
          $test = "$ENV{'ORACLE_HOME'}/has_work/gpnp/".
                  asmcmdshare_get_host_name().
                  "/profiles/peer/profile.xml";

          if (-f $test)
          {
            $crs_home = "$ENV{'ORACLE_HOME'}/has_work";
          }
          else
          {
            asmcmdshare_error_msg(8319, undef);
            return;
          }
        }     

        #if(!defined($ENV{'ADE_VIEW_ROOT'}))
        #{
        #    $crs_home = "$ENV{'ORACLE_HOME'}";
        #}
        #else
        #{
        #    $crs_home = "$ENV{'ORACLE_HOME'}/has_work";
        #}

        #untaint gpnptool
        $gpnptool =~ /([^\n^\r^\t]+)/;
        $gpnptool = $1;

        $prof_path = "$crs_home/gpnp/".asmcmdshare_get_host_name().
                     "/profiles/peer/profile.xml";
        
        #untaint prof_path
        $prof_path =~ /([^\n^\r^\t]+)/;
        $prof_path = $1;

        $gpnp_exec = "$gpnptool getpval -p=$prof_path -asm_dis -o-";
 
        $gpnp_exec =~ /([^\n^\r^\t]+)/;
        $gpnp_exec = $1;

        if (asmcmdshare_runcmd($gpnp_exec, \@buf_arr, 0, 0))
        {
          asmcmdshare_error_msg(8307, undef);
          return;
        }

        if (defined($buf_arr[0]) and $buf_arr[0])
        {
          asmcmdshare_print("profile:" . $buf_arr[0] . "\n");
        }
      }
      else
      {
        asmcmdshare_error_msg(8315, undef);
        return;
      }
    }
    else
    {
      $diskstring = asmcmdsys_dualget($dbh, 'asm_diskstring'); 
    
      #Adding code below to display empty string in dsget.
      $diskstring = "" if !defined($diskstring);
        
      if (defined($diskstring))
      {  
        asmcmdshare_print("profile:" . $diskstring . "\n");
      }
    }
  return;
  }
}

########
# NAME
#   asmcmdsys_dualget
#
# DESCRIPTION
#   This function returns the requested valure from sys_cluster_properties.
#
# USAGE
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null. 
#   $what (IN) - property/value requested
#   $val  (OUT) -
#
# RETURNS
#   Requested value querying from sys_context
#
#########

sub asmcmdsys_dualget
{
    my ($dbh, $what) = @_;
    my ($stmt, $ret, $val, $row);
=pod
    We use 4000 because asm_diskstring can be more than the default max(256)
    The default maximum size can be overriden by specifying the optional length 
    parameter, which must be a NUMBER or a value that can be implicitly 
    converted to NUMBER.The valid range of values is 1 to 4000 bytes. If you 
    specify an invalid value, then Oracle Database ignores it and uses the 
    default.For more details see SYS_CONTEXT in SQL Reference.
=cut
    $stmt = "select sys_context('sys_cluster_properties','" . $what . "',4000) ".
            "as val from dual";
    $ret = asmcmdshare_do_select($dbh, $stmt);

    $row = asmcmdshare_fetch($ret);
    asmcmdshare_finish($ret);
    $val = $row->{VAL};
    return $val;
}


########
# NAME
#   asmcmdsys_spset
#
# DESCRIPTION
#   This function sets the spfile location in the gPnP profile.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   spfile(IN) - spfile path.
#
# RETURNS
#   Path of the spfile.
#
# NOTES
#   The caller is responsible to check that the file exists.
#
########
sub asmcmdsys_spset
{  
  my ($dbh, $path) = @_;
  my ($ret, $sth);

  $sth = $dbh->prepare(q{
    begin
      dbms_diskgroup.gpnpsetsp(:spfile_path);
    exception when others then
      raise;
    end;
  });

  $sth->bind_param( ":spfile_path", $path);

  $ret = $sth->execute();

  return $ret;
}


########
# NAME
#   asmcmdsys_process_spset
#
# DESCRIPTION
#   This function processes the asmcmd command spset.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdsys_process_cmd() calls this function.
########
sub asmcmdsys_process_spset
{
  my ($dbh) = shift;
  my ($attr, $val);
  my ($ret, %args, $sth, $row, $dst_path);
  my ($spfile);
  my (%result);
  my (@eargs);
  my (@what , @from, @where, @order);

  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  ($spfile) = @{$args{'spset'}};

  # SPSET operation for ASM SPFILE requires SYSASM privilege
  if($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    asmcmdshare_error_msg (9485, undef);
    return;
  }

  #Normalize the path - if it is an OS path (starting with '/') 
  #it has to be absolute, otherwise it is either an absolute ASM path 
  #or a relative one

  if ( (($spfile !~ /^[a-z]:/i) && ($spfile !~ m'^[/\\\\]')) || ($spfile !~ m'^/'))
  {
    #It must be an ASM path. Normalize the ASM path
    %result = asmcmdshare_normalize_path ($dbh, $spfile, 0, \$ret);
    if ($ret != 0) 
    { 
      #Check if file exists
      @eargs = ($spfile);
      asmcmdshare_error_msg(8014, \@eargs);
      return;
    }
    ($spfile) = @{$result{'path'}};
  }
  else
  { 
    #It should be an absolute OS path. Check if file exists
    if ( !(-e $spfile) )
    {
      @eargs = ($spfile);
      asmcmdshare_error_msg(8014, \@eargs);
      return;
    }
    #$spfile =~ s/\\/\//g;
  }

    $ret = asmcmdsys_spset($dbh, $spfile);

    if (!defined($ret))
    {
      asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
      return;
    }

    return;
}

########
# NAME
#   asmcmdsys_get_asmdiskstr_fromprofile
#
# DESCRIPTION
#   This function returns asm_diskstring value from gpnp profile.
#
# PARAMETERS
#   NONE 
#
# RETURNS
#   asm_diskstring value if available ; "" otherwise.
########
sub asmcmdsys_get_asmdiskstr_fromprofile
{
  my ($kfod);
  my (@buf);
  my (@eargs);

  # if unable to query, return empty string
  my $asmdiskstr = "";

  my ($asm_discstr) = "";
 
  $kfod = catfile($ENV{'ORACLE_HOME'}, 'bin', 'kfod');
  $kfod .= ".exe" if($^O =~ /win/i);
  # Make sure the kfod binary exists and can be executed. 
  if (! -x $kfod)
  {
    @eargs = ($kfod);
    asmcmdshare_error_msg(9515, \@eargs);
    return;
  }

  asmcmdshare_runcmd("$kfod op=GPNPDSTR nohdr=true", \@buf, 0, 0);
    
  foreach my $line (@buf)
  {
    $asmdiskstr = $line;
  }

  # kfod returns one line o/p for asm_diskstring.
  # Last line contains the asm_diskstring.
  #
  # kfod may return 0 but asm_diskstring may
  # be 'Not Set' or 'Not Available'.
  #
  # If asm_diskstring was never setup at profile creation
  # time or later, the default value in profile.xml is
  # "++no-value-at-profile-creation--never-updated-through-ASM++"
  # This special string also needs to be handled well.
  # For those, return empty string.
  #
  # Empty string is interpreted as default discovery string
  # by afdboot/afdtool.
  #
  chomp($asmdiskstr);
  if( ($asmdiskstr eq "Not Set") ||
      ($asmdiskstr eq "Not Available") ||
      ($asmdiskstr eq "++no-value-at-profile-creation--never-updated-through-ASM++") )
  {
    $asmdiskstr = "";
  }

  asmcmdshare_trace(3, "Retrieved asm disk string from gpnp : \'$asmdiskstr\'",
                    'y', 'n');
  return $asmdiskstr;
} # end asmcmdsys_get_asmdiskstr_fromprofile

########
# NAME
#   asmcmdsys_is_help
#
# DESCRIPTION
#   This function is the help function for the ASMCMDSYS module.
#
# PARAMETERS
#   command     (IN) - display the help message for this command.
#
# RETURNS
#   1 if command found; 0 otherwise.
########
sub asmcmdsys_process_help 
{
  my ($command) = shift;       # User-specified argument; show help on $cmd. #
  my ($desc);                                # Command description for $cmd. #
  my ($succ) = 0;                         # 1 if command found, 0 otherwise. #

  if (asmcmdsys_is_cmd ($command)) 
  {                              # User specified a command name to look up. #
    $desc = asmcmdshare_get_help_desc($command);
    asmcmdshare_print "$desc\n";
    $succ = 1;
  }

  return $succ;
}

########
# NAME
#   asmcmdsys_is_cmd
#
# DESCRIPTION
#   This routine checks if a user-entered command is one of the known
#   ASMCMD internal commands that belong to the ASMCMDSYS module.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   True if $arg is one of the known commands, false otherwise.
########
sub asmcmdsys_is_cmd 
{
  my ($arg) = shift;

  return defined ( $asmcmdsys_cmds{ $arg } );
}


########
# NAME
#   asmcmdsys_is_wildcard_cmd
#
# DESCRIPTION
#   This routine determines if an ASMCMDSYS command allows the use 
#   of wild cards.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   True if $arg is a command that can take wildcards as part of its argument, 
#   false otherwise.
########
sub asmcmdsys_is_wildcard_cmd 
{
  my ($arg) = shift;
  my (%cmdhash); # Empty hash; no ASMCMDSYS command supports wildcards. # 

  return (asmcmdshare_get_cmd_wildcard($arg) eq "true");
}

########
# NAME
#   asmcmdsys_is_no_instance_cmd
#
# DESCRIPTION
#   This routine determines if a command can run without an ASM instance.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   1 if $arg is a command that can run without an ASM instance or it does not
#   belong to this module
#   0 if $arg is a command that needs to connect to an ASM instance
#   -1 if $arg is a command that may use an ASM instance.
#
# NOTES
#   The asmcmdsys module currently supports no command that can run 
#   without an ASM instance.
########
sub asmcmdsys_is_no_instance_cmd 
{
  my ($arg) = shift;
  my ($rc);

  return 1 unless defined($asmcmdsys_cmds{$arg});

  $rc = asmcmdshare_get_cmd_noinst($arg);
  if ($rc eq "true")
  {
    return 1;
  }
  elsif ($rc eq "undef")
  {
    return -1;
  }

  return 0;
}

########
# NAME
#   asmcmdsys_parse_int_args
#
# DESCRIPTION
#   This routine parses the arguments for flag options for ASMCMDSYS
#   internal commands.
#
# PARAMETERS
#   cmd      (IN)  - user-entered command name string.
#   args_ref (OUT) - hash of user-specified flag options for a command,
#                    populated by getopts().
#
# RETURNS
#   Zero on success; undefined on error.
#
# NOTES
#   $cmd must already be verified as a valid ASMCMDSYS internal command.
########
sub asmcmdsys_parse_int_args 
{
  my ($cmd, $args_ref) = @_;
  my ($key);
  my (@string);

  # Use Gsmcmdparser_parse_issued_command() from the asmcmdparser package to parse arguments for
  # internal commands.  These arguments are stored in @ARGV.
  if(!asmcmdparser_parse_issued_command($cmd, $args_ref, \@string))
  {
    # Print correct command format if syntax error. #
    asmcmdsys_syntax_error($cmd);
    return undef;
  }
  return 0;
}

########
# NAME
#   asmcmdsys_syntax_error
#
# DESCRIPTION
#   This function prints the correct syntax for a command to STDERR, used 
#   when there is a syntax error.  This function is responsible for 
#   only ASMCMDSYS commands.
#
# PARAMETERS
#   cmd   (IN) - user-entered command name string.
#
# RETURNS
#   1 if the command belongs to this module; 0 if command not found.
#
# NOTES
#   These errors are user-errors and not internal errors.  They are of type
#   record, not signal.  
# 
#   N.B. Functions in this module can call this function directly, without
#   calling the asmcmdshare::asmcmdshare_syntax_error equivalent.  The
#   latter is used only by the asmcmdcore module.
########
sub asmcmdsys_syntax_error 
{
  my ($cmd) = shift;
  my ($cmd_syntax);                               # Correct syntax for $cmd. #
  my ($succ) = 0;
  #display syntax only if the command belongs to this module
  if (asmcmdsys_is_cmd($cmd))
  {
    $cmd_syntax = asmcmdshare_get_help_syntax($cmd);  # Get syntax for $cmd. #
    $cmd_syntax = asmcmdshare_trim_str ($cmd_syntax);   # Trim blank spaces #
    if (defined ($cmd_syntax))
    {
      asmcmdshare_printstderr 'usage: ' . $cmd_syntax . "\n";
      asmcmdshare_printstderr 'help:  help ' . $cmd . "\n";
      $succ = 1;
    }
  }

  return $succ;
}

########
# NAME
#   asmcmdsys_get_asmcmd_cmds
#
# DESCRIPTION
#   This routine constructs a string that contains a list of the names of all 
#   ASMCMD internal commands and returns this string.
#
# PARAMETERS
#   None.
#
# RETURNS
#   A string contain a list of the names of all ASMCMD internal commands.
#
# NOTES
#   Used by the help command and by the error command when the user enters
#   an invalid internal command.
#
#   IMPORTANT: the commands names must be preceded by eight (8) spaces of
#              indention!  This formatting is mandatory.
########
sub asmcmdsys_get_asmcmd_cmds 
{
  return asmcmdshare_filter_invisible_cmds(%asmcmdsys_cmds);
}

########
# NAME
#   asmcmdsys_is_dsc_supported
#
# DESCRIPTION
#   This function determines whether it is being run on an operating system
#   on which Domain Services Clusters (DSCs) are supported.
#
# PARAMETERS
#   None
#
# RETURNS
#   1 if the operating system supports DSCs, 0 otherwise
#
# NOTES
#   As of 12.2, DSCs are not supported on Windows.
#
#   22817301: DSCs aren't supported on HPUX either for 12.2.0.1.
#
#   $$$ This function needs to eventually check whether it is being run in an
#   environment that has ASM/GIMR/TFA (mandatory DSC components) configured.
#   But the APIs needed to do this don't yet exist.
########
sub asmcmdsys_is_dsc_supported
{
  my $supported = 0;
  if (($^O =~ /linux/i) || ($^O =~ /solaris/i) || ($^O =~ /aix/i))
  {
    $supported = 1;
  }
  return $supported;
}

########
# NAME
#   asmcmdsys_is_ios_supported
#
# DESCRIPTION
#   This function determines whether it is being run on an operating system
#   on which IOServer is supported.
#
# PARAMETERS
#   None
#
# RETURNS
#   1 if the operating system supports IOServer, 0 otherwise
#
# NOTES
#   As of 12.2.0.1, IOServer is not supported on AIX. It follows that IOServer
#   is also not supported on the platforms on which DSC is not supported
#   (Windows and HPUX).
########
sub asmcmdsys_is_ios_supported
{
  my $supported = 0;
  if (($^O =~ /linux/i) || ($^O =~ /solaris/i))
  {
    $supported = 1;
  }
  return $supported;
}

########
# NAME
#   asmcmdsys_get_cluster_class
#
# DESCRIPTION
#   This function retrieves the cluster class by calling
#   'crsctl get cluster class'
#
# PARAMETERS
#   None
#
# RETURNS
#   The cluster class on success; undefined on error
#
# NOTES
########
sub asmcmdsys_get_cluster_class
{
  my $class;
  my $crsctlop = "get cluster class";
  # directories in which the utilties are found
  my @patharr = ("$ENV{'ORACLE_HOME'}/bin/",
                 "$ENV{'ORACLE_HOME'}/rdbms/bin/");

  asmcmdshare_trace(3, "Getting cluster class", 'y', 'n');
  my @result = asmcmdshare_execute_tool("crsctl", ".bat", $crsctlop,
                                        \@patharr);
  my $resultstr = join("", @result);
  asmcmdshare_trace(3, $resultstr, 'y', 'n');
  if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
  {
    asmcmdshare_trace(1, $resultstr, 'y', 'y');
  }
  else
  {
    if (scalar(grep(/Standalone/, @result)) > 0)
    {
      $class = CLUSTER_CLASS_STANDALONE;
    }
    elsif (scalar(grep(/Domain/, @result)) > 0)
    {
      $class = CLUSTER_CLASS_DOMAINSERVICES;
    }
    elsif (scalar(grep(/Member/, @result)) > 0)
    {
      $class = CLUSTER_CLASS_MEMBER;
    }
  }

  # Will be undef if the crsctl call failed
  return $class;
}

########
# NAME
#   asmcmdsys_is_gi_user
#
# DESCRIPTION
#   This function determines whether the user issuing the command is the Grid
#   Infrastructure (GI) user.
#
# PARAMETERS
#   None
#
# RETURNS
#   1 if the user is the GI user, 0 otherwise
#
# NOTES
#   Previously, we checked the owner of 'olsnodes' binary to retrieve the
#   GI user.
#   # path to the olsnodes binary
#   my ($olsnodesbin) = "$ENV{'ORACLE_HOME'}/bin/olsnodes";
#   my ($giuser)      = getpwuid((stat($olsnodesbin))[4]);
#
#   We check the olsnodes binary because it exists only in a GI home. This is
#   how we can be sure that $ORACLE_HOME is a GI home and not a DB home.
#
########
sub asmcmdsys_is_gi_user
{
  my $isgiuser = 0;
  my ($rc, $giuser, $gigroup);
  my $curruser = getpwuid($<);

  undef &ASMCMDGetGIUser;
  my $cdet = DynaLoader::dl_find_symbol($asmcmdglobal_hash{'asmperl'},
                                        "XS_ASMCMDCLNT_GetGIUser");
  DynaLoader::dl_install_xsub("ASMCMDGetGIUser", $cdet);

  # Get GI user and group from ASMCMDGetGIUser using PerlToC API
  ($rc, $giuser, $gigroup) = ASMCMDGetGIUser();
  # In case of error, record the $rc, and return as not a GI user
  if ($rc != 0)
  {
    asmcmdshare_trace (1, "Error $rc in ASMCMDGetGIUser().\n", 'y', 'n');
    return $isgiuser;
  }

  asmcmdshare_trace(3, "GI user $giuser and GI group $gigroup\n", 'y', 'n');

  if ($curruser eq $giuser)
  {
    $isgiuser = 1;
  }
  return $isgiuser;
}

########
# NAME
#   asmcmdsys_lsccfile_start
#
# DESCRIPTION
#   This function processes the start of XML tags in lscc --file.
#
# PARAMETERS
#   expat   (IN) - expat parser object
#   element (IN) - tag element name
#   attrs   (IN) - tag element attributes
#
# RETURNS
#   NULL
#
# NOTES
#   Only asmcmdsys_list_cmf_comps calls this function.
########
sub asmcmdsys_lsccfile_start
{
  my ($expat, $element, %attrs) = @_;
  my $attrkey;
  my $attrval;

  # the tags we're interested in look like
  # <clsxwrap:clsxSecNode id="ASM">
  if ($element eq "clsxwrap:clsxSecNode")
  {
    for $attrkey (keys %attrs)
    {
      # some component names show up as lowercase in the Cluster Manifest File,
      # so uppercase everything to be consistent
      $attrval = uc($attrs{$attrkey});

      if (($attrkey eq "id") && defined $lsccfile_comps{$attrval})
      {
        $lsccfile_comps{$attrval} = "YES";
      }
    }
  }
  # we are also interested in the payload of the ASM_attributes section in
  # order to find out the storage access
  elsif ($element eq "clsxwrap:clsxWrapPayload")
  {
    for $attrkey (keys %attrs)
    {
      if ($attrkey eq "ASM_ACCESS_MODE")
      {
        $attrval = $attrs{$attrkey};
        $attrval = ($attrval eq "1") ? "Indirect" : "Direct";
        $lsccfile_comps{"STORAGE ACCESS"} = $attrval;
      }
    }
  }
}

########
# NAME
#   asmcmdsys_list_cmf_comps
#
# DESCRIPTION
#   Parses a Cluster Manifest File and displays the components it contains
#
# PARAMETERS
#   $cmf      (IN) - path to Cluster Manifest File
#   $printhdr (IN) - flag indicating to print header
#
# RETURNS
#   NULL
#
# NOTES
#
########
sub asmcmdsys_list_cmf_comps
{
  my ($cmf, $printhdr) = @_;
  my $rc;
  my $handlers;
  my $parser;
  my @eargs;
  my $cmffilehandle;
  my $output         = "";
  my $iscmf          = 0;

  # valid components in a Cluster Manifest File
  my $asmname  = "ASM";                                  # ASM component name #
  my $gimrname = "GIMR";                                # GIMR component name #
  my $tfaname  = "TFA";                                  # TFA component name #
  my $acfsname = "ACFS";                                # ACFS component name #
  my $rhpname  = "RHP";                                  # RHP component name #
  my $gnsname  = "GNS";                                  # GNS component name #

  # storage access
  my $storageaccess = "STORAGE ACCESS";   # ASM access mode (direct/indirect) #

  # format strings, header, and trailer
  my $fmtstr  = "%3s %4s %3s %4s %3s %3s %14s";
  my $header  = '-'x80 ."\n" .
                sprintf($fmtstr, $asmname, $gimrname, $tfaname, $acfsname,
                        $rhpname, $gnsname, $storageaccess) . "\n" .
                '='x80 . "\n";
  my $trailer = '='x80 . "\n";

  # initialize to all NO (and storage access to "unknown")
  %lsccfile_comps = (
    $asmname       => "NO",
    $gimrname      => "NO",
    $tfaname       => "NO",
    $acfsname      => "NO",
    $rhpname       => "NO",
    $gnsname       => "NO",
    $storageaccess => "-",
  );

  # bail out if the user supplied a nonexistent file or a file on which there
  # are no read permissions
  $rc = asmcmdshare_xml_exists($cmf);
  return unless defined ($rc);

  # check that the file at least vaguely resembles a Cluster Manifest File by
  # seeing if it contains the text "clsxwrap:clsxWrap"
  open($cmffilehandle, "< $cmf");
  my @lines = <$cmffilehandle>;
  close $cmffilehandle;

  foreach (@lines)
  {
    if ($_ =~ m/clsxwrap:clsxWrap/)
    {
      $iscmf = 1;
    }
  }

  if (!$iscmf)
  {
    @eargs = ($cmf);
    # ASMCMD-9497: "File '%s' is not a Cluster Manifest File."
    asmcmdshare_error_msg(9497, \@eargs);
    return;
  }

  # $$$ Perl-C call to the cred API to determine if the XML the user supplied
  # is a valid credentials file

  # specify the handler callback and instantiate the parser: the information we
  # look for in the XML file is specifically located in the start tags and the
  # rest of the information may be ignored
  $handlers = {Start => \&asmcmdsys_lsccfile_start};
  $parser = XML::Parser->new(Handlers     => $handlers,
                             ErrorContext => 5);

  # parse!
  eval
  {
    $parser->parsefile($cmf);
  };

  # bail out if there were any parsing errors
  if (asmcmdxmlexceptions::catch())
  {
    my (@msg, $err);
    $err = asmcmdxmlexceptions::getErrmsg();
    @msg = split(/\n/, $err);
    if ($msg[$#msg] =~ m/Parser\.pm/)
    {
      pop @msg;
    }

    $err = join("\n", @msg) ."\n";
    @eargs = ($err);
    asmcmdshare_error_msg(9395, \@eargs);
    return;
  }

  # see if there are any components configured and add info to the "no header"
  # output string if there are
  if ($lsccfile_comps{$asmname} eq "YES")
  {
    $output .= "$asmname ($lsccfile_comps{$storageaccess} Storage Access),";
  }
  if ($lsccfile_comps{$gimrname} eq "YES")
  {
    $output .= "$gimrname,";
  }
  if ($lsccfile_comps{$tfaname} eq "YES")
  {
    $output .= "$tfaname,";
  }
  if ($lsccfile_comps{$acfsname} eq "YES")
  {
    $output .= "$acfsname,";
  }
  if ($lsccfile_comps{$rhpname} eq "YES")
  {
    $output .= "$rhpname,";
  }
  if ($lsccfile_comps{$gnsname} eq "YES")
  {
    $output .= "$gnsname,";
  }

  # remove the trailing comma
  $output =~ s/,$//;

  # error out if Cluster Manifest File doesn't contain any components
  if (!$output)
  {
    @eargs = ($cmf);
    # ASMCMD-9496: "The Cluster Manifest File '%s' does not contain any
    # components."
    asmcmdshare_error_msg(9496, \@eargs);
    return;
  }

  # output the result
  if ($printhdr)
  {
    asmcmdshare_print($header);
    asmcmdshare_print(sprintf($fmtstr, $lsccfile_comps{$asmname},
                      $lsccfile_comps{$gimrname}, $lsccfile_comps{$tfaname},
                      $lsccfile_comps{$acfsname}, $lsccfile_comps{$rhpname},
                      $lsccfile_comps{$gnsname},
                      $lsccfile_comps{$storageaccess}) . "\n");
    asmcmdshare_print($trailer);
  }
  else
  {
    asmcmdshare_print("$output\n");
  }
}

########
# NAME
#   asmcmdsys_process_mkcc
#
# DESCRIPTION
#   To create a client cluster for ASM, GIMR, TFA, ACFS, and RHP
#
# PARAMETERS
#   $dbh   (IN)  - initialized database handle, must be non-null.
#
# RETURNS
#   NULL
#
# NOTES
#   Requires SYSASM and GI user privileges to execute this operation
########
sub asmcmdsys_process_mkcc
{
  my $dbh = shift;                                            # get db handle #
  my %args;                               # arguments passed with the command #
  my $ret;                         # asmcmdbase_parse_int_args() return value #
  my $sth;

  # common to all components
  my $clustername;                                      # client cluster name #
  my $wrap;                                            # credential file name #
  my $version = '';                                  # client cluster version #
  my $guid    = '';                                     # client cluster guid #
  # directories in which the utilties are found
  my @patharr = ("$ENV{'ORACLE_HOME'}/bin/",
                 "$ENV{'ORACLE_HOME'}/rdbms/bin/");
  my $clusterclass;                                           # cluster class #
  my $clusterstate;                                           # cluster state #

  # components to configure
  my $asmname  = "asm";                                  # ASM component name #
  my $gimrname = "gimr";                                # GIMR component name #
  my $tfaname  = "tfa";                                  # TFA component name #
  my $acfsname = "acfs";                                # ACFS component name #
  my $rhpname  = "rhp";                                  # RHP component name #
  my %components = (          # hash indicating which components to configure #
    $asmname  => "true",
    $gimrname => "true",
    $tfaname  => "true",
    $acfsname => "true",
    $rhpname  => "true",
  );

  # tfactl and acfsutil error output by default goes to stderr; redirect it to
  # stdout in order to look for specific error messages
  my $redirect    = 1;

  # ASM-specific
  my $directacc;                         # direct access allowed or not (ASM) #
  my $kfodop;                                       # arguments to kfod (ASM) #
  # ORA-15365 "Client Cluster '%s' already configured"
  my $asmccexists = "ORA-15365";          # CC already configured error (ASM) #
  my $srvctlop;                                   # arguments to srvctl (ASM) #

  # GIMR-specific
  # token seen in output of 'srvctl config mgmtdb' if run on GIMR client
  my $gimrisclient = "server_cluster_name";
  my $mgmtcaop;                                  # arguments to mgmtca (GIMR) #
  # MGTCA-1149 "Client cluster '%s' already exists."
  my $gimrccexists = "MGTCA-1149";       # CC already configured error (GIMR) #

  # TFA-specific
  my $tfactlop;                                   # arguments to tfactl (TFA) #
  # TFA-00516 "This client is already registered in receiver"
  my $tfaccexists = "TFA-00516";          # CC already configured error (TFA) #
  # TFA-00519 "Oracle Trace File Analyzer (TFA) is not installed."
  my $tfanoconfig = "TFA-00519";           # tfa not installed in an ADE view #

  # ACFS-specific
  my $acfsutilop;                              # arguments to acfsutil (ACFS) #
  # prefix to all ACFS errors thrown by acfsutil
  my $acfserrprefix = "ACFS-";
  # ACFS-09818: "Unable to add an already existing cluster '%s'. Exporting the
  # existing credentials."
  my $acfsccexists  = "ACFS-09818";      # CC already configured error (ACFS) #
  # acfsutil is found in /sbin on unix platforms
  my @acfsutildir   = ("/sbin/");

  # RHP-specific
  my $rhpctlop;                                   # arguments to rhpctl (RHP) #
  # PRGR-128 "Site "%s" already exists."
  my $rhpccexists1 = "PRGR-128";    # (old) CC already configured error (RHP) #
  # PRGR-152 "Client "%s" already exists."
  my $rhpccexists2 = "PRGR-152";    # (new) CC already configured error (RHP) #
  # PRCR-1001 "Resource %s does not exist"
  my $rhpnoconfig = "PRCR-1001";             # rhpserver not configured error #

  # get option parameters
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  # options to specify which components to configure (if no components are
  # specified, the default is to generate credentials for all components for
  # which a service is configured)
  if (defined ($args{$asmname}) || defined ($args{$gimrname}) ||
      defined ($args{$tfaname}) || defined ($args{$acfsname}) ||
      defined ($args{$rhpname}))
  {
    if (!defined ($args{$asmname}))
    {
      $components{$asmname} = "false";
    }

    if (!defined ($args{$gimrname}))
    {
      $components{$gimrname} = "false";
    }

    if (!defined ($args{$rhpname}))
    {
      $components{$rhpname} = "false";
    }

    if (!defined ($args{$acfsname}))
    {
      $components{$acfsname} = "false";
    }

    if (!defined ($args{$tfaname}))
    {
      $components{$tfaname} = "false";
    }
  }

  # get the cluster name and credentials file name
  ($clustername, $wrap) = @{$args{'mkcc'}};

  # check if SYSASM privileges exist
  if ($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    # ASMCMD-9488: "operations on Client Cluster require SYSASM privilege"
    asmcmdshare_error_msg(9488, undef);
    return;
  }

  # check if Domain Services Cluster is supported on this OS
  if (!asmcmdsys_is_dsc_supported())
  {
    my @eargs = ($ASMCMDSYS_DSC_NAME);
    # ASMCMD-9493: "The '%s' feature is not supported on this operating
    # system."
    asmcmdshare_error_msg(9493, \@eargs);
    return;
  }

  # check if the user executing the command is the GI user
  if (!asmcmdsys_is_gi_user())
  {
    # ASMCMD-9546: "Insufficient permission to execute the command. The command
    # requires an Oracle Grid Infrastructure user."
    asmcmdshare_error_msg(9546, undef);
    return;
  }

  # check that the cluster is not in rolling upgrade or rolling patch
  $clusterstate = asmcmdbase_get_cluster_state($dbh);
  if (lc($clusterstate) ne "normal")
  {
    my @eargs = ($clustername, $clusterstate);
    # ASMCMD-9478: "Create or delete member cluster '%s' failed because the
    # Domain Services Cluster is in '%s' state."
    asmcmdshare_error_msg(9478, \@eargs);
    return;
  }

  # make additional checks for cluster class in production but in a development
  # environment, skip these checks so that tests continue to work
  if (!asmcmdshare_is_dev_env())
  {
    $clusterclass = asmcmdsys_get_cluster_class();

    # error out if we failed to get the cluster class
    if (!defined ($clusterclass))
    {
      return;
    }

    # error out if either or both of the following are true:
    #   1) configuring ASM + cluster class is Member
    #   2) configuring TFA or configuring GIMR + cluster class is not DSC
    if (($components{$asmname} eq "true" &&
         $clusterclass eq CLUSTER_CLASS_MEMBER) ||
        (($components{$gimrname} eq "true" ||
          $components{$tfaname} eq "true") &&
         $clusterclass ne CLUSTER_CLASS_DOMAINSERVICES))
    {
      # ASMCMD-9498: "The command is only supported on the Domain Services
      # Cluster."
      asmcmdshare_error_msg(9498, undef);
      return;
    }
  }

  # 21864926: version is an argument to the CLI for multiple components
  # optional parameter client cluster version
  if (defined ($args{'version'}))
  {
    $version = ($args{'version'});  # user specified client cluster version #

    # validate that the version is in a.b.c.d.e format where each of the five
    # digits are between 0 and 99
    if ($version !~ /^(\d{1,2}\.){4}\d{1,2}$/)
    {
      # ASMCMD-9499: "invalid member cluster version '%s'"
      my @eargs = ($version);
      asmcmdshare_error_msg(9499, \@eargs);
      return;
    }

    # now that we have a valid version, check that it is not lower than the
    # minimum member cluster version
    if (asmcmdshare_version_cmp($version, $ASMCMDSYS_MIN_MC_VER) < 0)
    {
      # the ASM version is equivalent to the DSC version (release version);
      # since we disallow this command during rolling migration, the
      # release/software/active versions must all be the same if we get here
      my $dsc_version = asmcmdshare_get_asm_version($dbh);
      my @eargs = ($version, $dsc_version);
      # ASMCMD-9500: "member cluster version '%s' is incompatible with Domain
      # Services Cluster version '%s'"
      asmcmdshare_error_msg(9500, \@eargs);
      return;
    }
  }

  if (defined ($args{'guid'}))
  {
    $guid = ($args{'guid'});  # user specified client cluster guid #

    # validate that the GUID is a 32-digit hexadecimal string
    if ($guid !~ /^[0-9a-fA-F]{32}$/)
    {
      # ASMCMD-9479: "invalid GUID '%s' specified"
      my @eargs = ($guid);
      asmcmdshare_error_msg(9479, \@eargs);
      return;
    }
  }

  # 22375430: check if the directory in the path to the credentials file
  # exists; this doesn't need to work on Windows because we would have already
  # errored out above on Windows
  my @splitwrapdir = split(/\//, $wrap, -1);
  pop(@splitwrapdir);
  my $wrapdir = join('/', @splitwrapdir);
  # if no directory was specified, then check the current working directory
  if (!$wrapdir)
  {
    $wrapdir = getcwd();
  }

  if (! -d $wrapdir)
  {
    my @eargs = ($wrapdir);
    # ASMCMD-9495: "Directory '%s' does not exist."
    asmcmdshare_error_msg(9495, \@eargs);
    return;
  }

  # 20675462: error out if we don't have write permissions on the above
  # directory; also error out if the credentials file already exists, and we
  # don't have write permissions on it
  if (! -w $wrapdir || (-e $wrap && (! -w $wrap)))
  {
    # ASMCMD-9463: "operation failed due to lack of write permissions"
    asmcmdshare_error_msg(9463, undef);
    return;
  }

  # ASM
  if ($components{$asmname} eq "true")
  {
    asmcmdshare_trace(3, "Configuring ASM Client Cluster", 'y', 'n');

    $directacc = 0;                                  # assume indirect access #
    # optional parameter direct (or indirect)
    if (defined ($args{'direct'}))
    {
      $directacc = 1;                # if specified direct access or indirect #
    }

    # Error out if the user wants indirect access and we are on an OS that
    # doesn't support IOServer
    if (!$directacc && !asmcmdsys_is_ios_supported())
    {
      my @eargs = ($ASMCMDSYS_IOS_NAME);
      # ASMCMD-9493: "The '%s' feature is not supported on this operating
      # system."
      asmcmdshare_error_msg(9493, \@eargs);
      return;
    }

    # query to create client cluster
    if (defined ($args{'version'}) && defined ($args{'guid'}))
    {
      $sth = $dbh->prepare(q{
        begin
          dbms_diskgroup.createclientcluster2 (:clname, :direct_access, :clver,
                                               :clguid);
        end;
      });

      # bind input parameters
      $sth->bind_param(":clname", $clustername);
      $sth->bind_param(":direct_access", $directacc);
      $sth->bind_param(":clver", $version);
      $sth->bind_param(":clguid", $guid);
    }
    elsif (defined ($args{'version'}))
    {
      $sth = $dbh->prepare(q{
        begin
          dbms_diskgroup.createclientcluster2 (:clname, :direct_access, :clver,
                                               '');
        end;
      });

      # bind input parameters
      $sth->bind_param(":clname", $clustername);
      $sth->bind_param(":direct_access", $directacc);
      $sth->bind_param(":clver", $version);
    }
    elsif (defined ($args{'guid'}))
    {
      $sth = $dbh->prepare(q{
        begin
          dbms_diskgroup.createclientcluster2 (:clname, :direct_access, '',
                                               :clguid);
        end;
      });

      # bind input parameters
      $sth->bind_param(":clname", $clustername);
      $sth->bind_param(":direct_access", $directacc);
      $sth->bind_param(":clguid", $guid);
    }
    else
    {
      $sth = $dbh->prepare(q{
        begin
          dbms_diskgroup.createclientcluster2 (:clname, :direct_access, '',
                                               '');
        end;
      });

      # bind input parameters
      $sth->bind_param(":clname", $clustername);
      $sth->bind_param(":direct_access", $directacc);
    }

    $ret = $sth->execute();
    if (!defined($ret))
    {
      # if we get an error saying the client cluster is already configured, we
      # want to continue and export the credentials
      if ($DBI::errstr =~ m,$asmccexists,)
      {
        # 21982652: don't let extraneous error messages go to stderr
        my @spliterr = split("\n", $DBI::errstr);
        my $row;
        foreach $row (@spliterr)
        {
          if ($row =~ m,$asmccexists,)
          {
            # stderr and trace file
            asmcmdshare_trace(1, $row, 'y', 'y');
          }
          else
          {
            # only trace file
            asmcmdshare_trace(1, $row, 'y', 'n');
          }
        }
      }
      # otherwise fail
      else
      {
        my @eargs = ("ASM", $clustername);
        # ASMCMD-9494: "failed to create component '%s' credentials for Member
        # Cluster '%s'"
        asmcmdshare_error_msg(9494, \@eargs);

        @eargs = ($clustername);
        # ASMCMD-9476: "failed to create the Cluster Manifest File for member
        # cluster '%s'" 
        asmcmdshare_error_msg(9476, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1;
        return;
      }
    }

    $kfodop = "op=credexport client_cluster=$clustername wrap=$wrap nohdr=TRUE";
    # Environment variable 'ORACLE_HOME' will be set, otherwise
    # ASMCMD init code will bail out.

    my @result = asmcmdshare_execute_tool("kfod", ".exe", $kfodop, \@patharr);

    # check if kfod succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      my @eargs = ("ASM", $clustername);
      # ASMCMD-9494: "failed to create component '%s' credentials for Member
      # Cluster '%s'"
      asmcmdshare_error_msg(9494, \@eargs);

      @eargs = ($clustername);
      # ASMCMD-9476: "failed to create the Cluster Manifest File for member 
      # cluster '%s'"
      asmcmdshare_error_msg(9476, \@eargs);
      asmcmdshare_trace(1, join("", @result), 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1;
      return;
    }

    # Start IOS if indirect access
    if ($directacc == 0)
    {
      $srvctlop = "start ioserver";

      my @result = asmcmdshare_execute_tool("srvctl", ".bat", $srvctlop, 
                                            \@patharr);
      # check if srvctl succeeded
      if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
      {
        # ignore error. write only to trace file
        asmcmdshare_trace(1, join("", @result), 'y', 'n');
      }
    }
  }

  # GIMR
  # $$$ if --gimrdir is specified, pass its value to the appropriate mgmtca
  # command.
  if ($components{$gimrname} eq "true")
  {
    # check if mgmtdb server is configured
    my @result = asmcmdshare_execute_tool("srvctl", ".bat",
                                          "config mgmtdb -inner 1", \@patharr);
    my $resultstr = join("", @result);
    asmcmdshare_trace(3, $resultstr, 'y', 'n');
    # $$$ This check can be removed once is_dsc_supported works properly.
    # srvctl will also exit with return code 0 if executed on an mgmtdb client;
    # also skip this step if we are on a mgmtdb client
    if ($asmcmdglobal_hash{'utilsucc'} eq 'true' &&
        $resultstr !~ m,$gimrisclient,)
    {
      asmcmdshare_trace(3, "Configuring GIMR Client Cluster", 'y', 'n');
      $mgmtcaop = "createRepos -wrap $wrap -client $clustername";

      # 24760407: support the use case where MC version != DSC version for GIMR
      if (defined ($args{'version'}))
      {
        $mgmtcaop .= " -version $version";
      }

      my @result = asmcmdshare_execute_tool("mgmtca", ".bat", $mgmtcaop,
                                            \@patharr);

      # if we get an error saying the client cluster is already configured, we
      # want to export the credentials because mgmtca internally doesn't do so
      if (join("", @result) =~ m,$gimrccexists,)
      {
        # 21982652: don't let Cause/Action go to stderr
        my $row;
        foreach $row (@result)
        {
          $row =~ s/\s+$//;
          if ($row =~ m,$gimrccexists,)
          {
            # stderr and trace file
            asmcmdshare_trace(1, $row, 'y', 'y');
          }
          else
          {
            # only trace file
            asmcmdshare_trace(1, $row, 'y', 'n');
          }
        }
        $mgmtcaop = "configRepos exportCred -wrap $wrap -client $clustername";
        @result = asmcmdshare_execute_tool("mgmtca", ".bat", $mgmtcaop,
                                           \@patharr);
      }

      # check if mgmtca succeeded
      if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
      {
        my @eargs = ("GIMR", $clustername);
        # ASMCMD-9494: "failed to create component '%s' credentials for Member
        # Cluster '%s'"
        asmcmdshare_error_msg(9494, \@eargs);
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1;
        return;
      }
    }
  }

  # TFA
  if ($components{$tfaname} eq "true")
  {
    # We will have already errored out above if the OS is Windows; tfa is not
    # supported on Windows, and currently the tfactl executable doesn't even
    # exist on Windows
    #
    # check if tfa is configured (i.e. running)
    $tfactlop = "print status";
    my @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                          $redirect);
    my $resultstr = join("", @result);
    asmcmdshare_trace(3, $resultstr, 'y', 'n');

    # skip this step only if ALL of the following are true:
    #   (1) tfa is not installed
    #   (2) we're in a development environment (ADE view)
    #   (3) the user didn't specify the --tfa flag
    if (!($resultstr =~ m,$tfanoconfig, && asmcmdshare_is_dev_env() &&
        !defined $args{$tfaname}))
    {
      asmcmdshare_trace(3, "Configuring TFA Client Cluster", 'y', 'n');
      $tfactlop = "client add $clustername -export $wrap";

      # optional version parameter
      if (defined($args{'version'}))
      {
        $tfactlop .= " -version $version";
      }

      @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                         $redirect);

      # if we get an error saying the client cluster is already configured, we
      # want to continue; tfactl internally already re-exports the credentials
      if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
      {
        if (join("", @result) =~ m,$tfaccexists,)
        {
          # Suppress extraneous outputs
          my $row;
          foreach $row (@result)
          {
            $row =~ s/\s+$//;
            if ($row =~ m,$tfaccexists,)
            {
              # stderr and trace file
              asmcmdshare_trace(1, $row, 'y', 'y');
            }
            else
            {
              # only trace file
              asmcmdshare_trace(1, $row, 'y', 'n');
            }
          }
        }
        # otherwise fail
        else
        {
          my @eargs = ("TFA", $clustername);
          # ASMCMD-9494: "failed to create component '%s' credentials for
          # Member Cluster '%s'"
          asmcmdshare_error_msg(9494, \@eargs);
          asmcmdshare_trace(1, join("", @result), 'y', 'y');
          $asmcmdglobal_hash{'e'} = -1;
          return;
        }
      }
    }
  }

  # ACFS
  if ($components{$acfsname} eq "true")
  {
    # check if ACFS is loaded
    my @result = asmcmdshare_execute_tool("acfsdriverstate", ".bat", "loaded",
                                          \@patharr);
    asmcmdshare_trace(3, join("", @result), 'y', 'n');
    if ($asmcmdglobal_hash{'utilsucc'} eq 'true')
    {
      asmcmdshare_trace(3, "Configuring ACFS Client Cluster", 'y', 'n');
      $acfsutilop = "cluster credential -g $clustername -o $wrap";

      # optional version parameter
      if (defined($args{'version'}))
      {
        $acfsutilop .= " -c $version";
      }

      @result = asmcmdshare_execute_tool("acfsutil", ".exe", $acfsutilop,
                                         \@acfsutildir, $redirect);
      my $resultstr = join("", @result);
      # Trim the command name prefix that acfsutil outputs
      $resultstr =~ s/.*$acfserrprefix/$acfserrprefix/g;

      @result = split("\n", $resultstr);

      # if we get an error saying the client cluster is already configured, we
      # want to continue; acfsutil internally already re-exports the
      # credentials
      if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
      {
        if (join("", @result) =~ m,$acfsccexists,)
        {
          # Suppress extraneous outputs
          my $row;
          foreach $row (@result)
          {
            $row =~ s/\s+$//;
            if ($row =~ m,$acfsccexists,)
            {
              # stderr and trace file
              asmcmdshare_trace(1, $row, 'y', 'y');
            }
            else
            {
              # only trace file
              asmcmdshare_trace(1, $row, 'y', 'n');
            }
          }
        }
        # otherwise fail
        else
        {
          my @eargs = ("ACFS", $clustername);
          # ASMCMD-9494: "failed to create component '%s' credentials for Member
          # Cluster '%s'"
          asmcmdshare_error_msg(9494, \@eargs);
          asmcmdshare_trace(1, $resultstr, 'y', 'y');
          $asmcmdglobal_hash{'e'} = -1;
          return;
        }
      }
    }
  }

  # RHP
  if ($components{$rhpname} eq "true")
  {
    # check if rhpserver is configured, since it isn't supported on some ports
    my @result = asmcmdshare_execute_tool("srvctl", ".bat", "config rhpserver",
                                          \@patharr);

    # we're done if rhpserver isn't configured
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # suppress the error message if the user didn't specify the --rhp flag
      # and the error tells that "rhpserver is not configured"
      if (!defined $args{$rhpname} && join("", @result) =~ m,$rhpnoconfig,)
      {
        asmcmdshare_trace(3, join("", @result), 'y', 'n');
      }
      # error out
      else
      {
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1 ;
      }
      return;
    }

    asmcmdshare_trace(3, "Configuring RHP Client Cluster", 'y', 'n');
    $rhpctlop = "add client -client $clustername -internalcred $wrap";

    # optional version parameter
    if (defined($args{'version'}))
    {
      $rhpctlop .= " -version $version";
    }

    # on Windows, rhpctl doesn't exist because rhpserver isn't supported
    @result = asmcmdshare_execute_tool("rhpctl", "", $rhpctlop,
                                       \@patharr);

    # if we get an error saying the client cluster is already configured, we
    # want to export the credentials because rhpctl internally won't do so in
    # this case
    if (join("", @result) =~ m,$rhpccexists1, ||
        join("", @result) =~ m,$rhpccexists2,)
    {
      # 21982652: rhpctl doesn't output extraneous messages when the rhp client
      # already exists, so we don't have to do any extra work here
      asmcmdshare_trace(1, join("", @result), 'y', 'y');
      $rhpctlop = "export client -client $clustername -internalcred $wrap";
      @result = asmcmdshare_execute_tool("rhpctl", "", $rhpctlop,
                                         \@patharr);
    }

    # check if rhpctl succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      my @eargs = ("RHP", $clustername);
      # ASMCMD-9494: "failed to create component '%s' credentials for Member
      # Cluster '%s'"
      asmcmdshare_error_msg(9494, \@eargs);
      asmcmdshare_trace(1, join("", @result), 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1;
      return;
    }
  }

  # at the end of a successful mkcc, output the components in the Cluster
  # Manifest File that was generated/updated
  asmcmdsys_list_cmf_comps($wrap, 1);
}

########
# NAME
#   asmcmdsys_process_rmcc
#
# DESCRIPTION
#   To delete a client cluster for ASM, GIMR, TFA, ACFS, and RHP
#
# PARAMETERS
#   NONE
#
# RETURNS
#   NULL
#
# NOTES
#   Requires SYSASM and GI user privileges to execute this operation
#
#   Unlike mkcc, where the user can specify which components to configure, rmcc
#   will just try to deconfigure all the components for which there is a server
#   configured
########
sub asmcmdsys_process_rmcc
{
  my $dbh = shift;                                            # get db handle #
  my %args;                               # arguments passed with the command #
  my $ret;                         # asmcmdbase_parse_int_args() return value #

  # common to all components
  my $clustername;                                      # client cluster name #
  my $clusterclass;                                           # cluster class #
  my $clusterstate;                                           # cluster state #
  # directories in which the utilties are found
  my @patharr   = ("$ENV{'ORACLE_HOME'}/bin/",
                   "$ENV{'ORACLE_HOME'}/rdbms/bin/");
  # ASMCMD-9490 "member cluster '%s' is not configured"
  my $nothingexists = "ASMCMD-9490";         # no components configured error #

  # tfactl and acfsutil error output by default goes to stderr; redirect it to
  # stdout in order to look for specific error messages
  my $redirect = 1;

  # ASM-specific
  my $kfodop;                                       # arguments to kfod (ASM) #
  # ORA-15367 "Client Cluster '%s' not configured"
  my $asmccnotexists = "ORA-15367";           # CC not configured error (ASM) #

  # GIMR-specific
  # token seen in 'srvctl config mgmtdb' output if run on a GIMR client
  my $gimrisclient    = "server_cluster_name";
  my $mgmtcaop;                                  # arguments to mgmtca (GIMR) #
  # MGTCA-1150 "Client cluster '%s' does not exist."
  my $gimrccnotexists = "MGTCA-1150";        # CC not configured error (GIMR) #

  # TFA-specific
  my $tfactlop;                                   # arguments to tfactl (TFA) #
  # prefix to all TFA errors thrown by tfactl
  my $tfaerrprefix   = "TFA-00";
  # TFA-00517 "This client does not exist"
  my $tfaccnotexists = "TFA-00517";           # CC not configured error (TFA) #

  # ACFS-specific
  my $acfsutilop;                              # arguments to acfsutil (ACFS) #
  # prefix to all ACFS errors thrown by acfsutil
  my $acfserrprefix   = "ACFS-";
  # ACFS-09819: "Unable to remove the '%s' cluster. It does not exist."
  my $acfsccnotexists = "ACFS-09819";        # CC not configured error (ACFS) #
  # acfsutil is found in /sbin on unix platforms
  my @acfsutildir     = ("/sbin/");

  # RHP-specific
  my $rhpctlop;                                   # arguments to rhpctl (RHP) #
  # PRGR-119 "Site "%s" does not exist."
  my $rhpccnotexists1 = "PRGR-119";     # (old) CC not configured error (RHP) #
  # PRGR-170 "Client "%s" does not exist."
  my $rhpccnotexists2 = "PRGR-170";     # (new) CC not configured error (RHP) #

  # get command parameters
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  # get cluster name
  $clustername = shift @{$args{'rmcc'}};

  # check if SYSASM privileges exist
  if ($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    # ASMCMD-9488: "operations on Client Cluster require SYSASM privilege"
    asmcmdshare_error_msg(9488, undef);
    return;
  }

  # check if Domain Services Cluster is supported on this OS
  if (!asmcmdsys_is_dsc_supported())
  {
    my @eargs = ($ASMCMDSYS_DSC_NAME);
    # ASMCMD-9493: "The '%s' feature is not supported on this operating
    # system."
    asmcmdshare_error_msg(9493, \@eargs);
    return;
  }

  # check if the user executing the command is the GI user
  if (!asmcmdsys_is_gi_user())
  {
    # ASMCMD-9546: "Insufficient permission to execute the command. The command
    # requires an Oracle Grid Infrastructure user."
    asmcmdshare_error_msg(9546, undef);
    return;
  }

  # check that the cluster is not in rolling upgrade or rolling patch
  $clusterstate = asmcmdbase_get_cluster_state($dbh);
  if (lc($clusterstate) ne "normal")
  {
    my @eargs = ($clustername, $clusterstate);
    # ASMCMD-9478: "Create or delete member cluster '%s' failed because the
    # Domain Services Cluster is in '%s' state."
    asmcmdshare_error_msg(9478, \@eargs);
    return;
  }

  # make additional checks for cluster class in production but in a development
  # environment, skip these checks so that tests continue to work
  if (!asmcmdshare_is_dev_env())
  {
    $clusterclass = asmcmdsys_get_cluster_class();

    # error out if we failed to get the cluster class
    if (!defined ($clusterclass))
    {
      return;
    }

    # error out if we are on a member cluster
    if ($clusterclass eq CLUSTER_CLASS_MEMBER)
    {
      # ASMCMD-9498: "The command is only supported on the Domain Services
      # Cluster."
      asmcmdshare_error_msg(9498, undef);
      return;
    }
  }

  # 22956099: check if any of the components exist for the cluster we are
  # attempting to remove and error out if not
  my @result = asmcmdshare_execute_tool("asmcmd", ".bat", "lscc $clustername",
                                        \@patharr, $redirect);
  if (join("", @result) =~ m,$nothingexists,)
  {
    my @eargs = ($clustername);
    # ASMCMD-9490: "member cluster '%s' is not configured"
    asmcmdshare_error_msg(9490, \@eargs);
    $asmcmdglobal_hash{'e'} = -1 ;
    return;
  }

  # ASM
  asmcmdshare_trace(3, "Deconfiguring ASM Client Cluster", 'y', 'n');

  # construct argument to kfod
  $kfodop = "op=creddelclus client_cluster=$clustername nohdr=TRUE";

  # adding optional parameters
  if (defined($args{'f'}))
  {
    $kfodop .= " force=TRUE";
  }

  @result = asmcmdshare_execute_tool("kfod", ".exe", $kfodop, \@patharr);

  # check if kfod succeeded
  if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
  {
    # if we get an error saying the client cluster is already deconfigured, or
    # if the force flag is set, we want to continue
    if (defined($args{'f'}) || join("", @result) =~ m,$asmccnotexists,)
    {
      # if the error is that the client cluster is already deconfigured, just
      # trace that message
      if (join("", @result) =~ m,$asmccnotexists,)
      {
        asmcmdshare_trace(1, join("", @result), 'y', 'n');
      }
      # output the entire error stack for all other failures
      else
      {
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
      }
    }
    # otherwise fail
    else
    {
      my @eargs = ($clustername);
      # ASMCMD-9477: "delete Client Cluster '%s' failed"
      asmcmdshare_error_msg(9477, \@eargs);
      asmcmdshare_trace(1, join("", @result), 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1 ;
      return;
    }
  }

  # GIMR
  # check if mgmtdb server is configured; if not, there can be no client to
  # deconfigure, so skip this step
  @result = asmcmdshare_execute_tool("srvctl", ".bat",
                                     "config mgmtdb -inner 1", \@patharr);
  my $resultstr = join("", @result);
  asmcmdshare_trace(3, $resultstr, 'y', 'n');
  # $$$ This check can be removed once is_dsc_supported works properly.  srvctl
  # will also exit with return code 0 if executed on an mgmtdb client; also
  # skip this step if we are on a mgmtdb client
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true' &&
      $resultstr !~ m,$gimrisclient,)
  {
    asmcmdshare_trace(3, "Deconfiguring GIMR Client Cluster", 'y', 'n');
    $mgmtcaop = "removeRepos -client $clustername";

    # optional force parameter
    if (defined($args{'f'}))
    {
      $mgmtcaop .= " -force";
    }

    my @result = asmcmdshare_execute_tool("mgmtca", ".bat", $mgmtcaop,
                                          \@patharr);

    # check if mgmtca succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # if we get an error saying the client cluster is already deconfigured,
      # or if the force flag is set, we want to continue
      if (defined($args{'f'}) || join("", @result) =~ m,$gimrccnotexists,)
      {
        # if the error is that the client cluster is already deconfigured, just
        # trace that message
        if (join("", @result) =~ m,$gimrccnotexists,)
        {
          asmcmdshare_trace(1, join("", @result), 'y', 'n');
        }
        # output the entire error stack for all other failures
        else
        {
          asmcmdshare_trace(1, join("", @result), 'y', 'y');
        }
      }
      # otherwise fail
      else
      {
        my @eargs = ($clustername);
        # ASMCMD-9477: "delete Client Cluster '%s' failed"
        asmcmdshare_error_msg(9477, \@eargs);
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1 ;
        return;
      }
    }
  }

  # TFA
  # We will have already errored out above if the OS is Windows; tfa is not
  # supported on Windows, and currently the tfactl executable doesn't even
  # exist on Windows
  #
  # check if tfa is configured (i.e. running); if not, there can be no tfa
  # clients, so skip this step
  $tfactlop = "print status";
  @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                     $redirect);
  $resultstr = join("", @result);
  asmcmdshare_trace(3, $resultstr, 'y', 'n');
  if ($resultstr !~ m,$tfaerrprefix,)
  {
    asmcmdshare_trace(3, "Deconfiguring TFA Client Cluster", 'y', 'n');
    $tfactlop = "client remove $clustername";

    # optional force parameter
    if (defined($args{'f'}))
    {
      $tfactlop .= " -f";
    }

    my @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                          $redirect);

    # check if tfactl succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # if we get an error saying the client cluster is already deconfigured,
      # or if the force flag is set, we want to continue
      if (defined($args{'f'}) || join("", @result) =~ m,$tfaccnotexists,)
      {
        # if the error is that the client cluster is already deconfigured, just
        # trace that message
        if (join("", @result) =~ m,$tfaccnotexists,)
        {
          asmcmdshare_trace(1, join("", @result), 'y', 'n');
        }
        # output the entire error stack for all other failures
        else
        {
          asmcmdshare_trace(1, join("", @result), 'y', 'y');
        }
      }
      # otherwise fail
      else
      {
        my @eargs = ($clustername);
        # ASMCMD-9477: "delete Client Cluster '%s' failed"
        asmcmdshare_error_msg(9477, \@eargs);
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1 ;
        return;
      }
    }
  }

  # check if ACFS is loaded; if not, there can be no client to deconfigure, so
  # skip this step
  @result = asmcmdshare_execute_tool("acfsdriverstate", ".bat", "loaded",
                                     \@patharr);
  asmcmdshare_trace(3, join("", @result), 'y', 'n');
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true')
  {
    asmcmdshare_trace(3, "Deconfiguring ACFS Client Cluster", 'y', 'n');
    $acfsutilop = "cluster credential -r $clustername";

    # optional force parameter
    if (defined($args{'f'}))
    {
      $acfsutilop .= " -f";
    }

    my @result = asmcmdshare_execute_tool("acfsutil", ".exe", $acfsutilop,
                                          \@acfsutildir, $redirect);
    my $resultstr = join("", @result);
    # Trim the command name prefix that acfsutil outputs
    $resultstr =~ s/.*$acfserrprefix/$acfserrprefix/g;

    # check if acfsutil succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # if we get an error saying the client cluster is already deconfigured,
      # or if the force flag is set, we want to continue
      if (defined($args{'f'}) || $resultstr =~ m,$acfsccnotexists,)
      {
        # if the error is that the client cluster is already deconfigured,
        # we just want to trace that message
        if (join("", @result) =~ m,$acfsccnotexists,)
        {
          my $row;
          foreach $row (@result)
          {
            $row =~ s/\s+$//;
            # trim the command name prefix that acfsutil outputs
            $row =~ s/.*$acfserrprefix/$acfserrprefix/;
            # only trace file
            asmcmdshare_trace(1, $row, 'y', 'n');
          }
        }
        # output the entire error stack for all other failures
        else
        {
          asmcmdshare_trace(1, $resultstr, 'y', 'y');
        }
      }
      # otherwise fail
      else
      {
        my @eargs = ($clustername);
        # ASMCMD-9477: "delete member cluster '%s' failed"
        asmcmdshare_error_msg(9477, \@eargs);
        asmcmdshare_trace(1, $resultstr, 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1 ;
        return;
      }
    }
  }

  # RHP
  # check if rhpserver is configured; if not, there can be no client to
  # deconfigure, so skip this step
  @result = asmcmdshare_execute_tool("srvctl", ".bat", "config rhpserver",
                                     \@patharr);
  asmcmdshare_trace(3, join("", @result), 'y', 'n');
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true')
  {
    asmcmdshare_trace(3, "Deconfiguring RHP Client Cluster", 'y', 'n');
    $rhpctlop = "delete client -client $clustername";

    # optional force parameter
    if (defined($args{'f'}))
    {
      $rhpctlop .= " -force";
    }

    # on Windows, rhpctl doesn't exist because rhpserver isn't supported
    my @result = asmcmdshare_execute_tool("rhpctl", "", $rhpctlop,
                                          \@patharr);

    # check if rhpctl succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # if we get an error saying the client cluster is already deconfigured,
      # we just want to trace that message
      if (join("", @result) =~ m,$rhpccnotexists1, ||
          join("", @result) =~ m,$rhpccnotexists2,)
      {
        asmcmdshare_trace(1, join("", @result), 'y', 'n');
      }
      else
      {
        my @eargs = ($clustername);
        # ASMCMD-9477: "delete Client Cluster '%s' failed"
        asmcmdshare_error_msg(9477, \@eargs);
        # 21982652: rhpctl doesn't output extraneous messages when the rhp
        # client does not exist, so we don't have to do any extra work here
        asmcmdshare_trace(1, join("", @result), 'y', 'y');
        $asmcmdglobal_hash{'e'} = -1 ;
        return;
      }
    }
  }
}

########
# NAME
#   asmcmdsys_process_lscc
#
# DESCRIPTION
#   To list the configured client clusters for ASM, GIMR, TFA, ACFS, and RHP
#
# PARAMETERS
#   NONE
#
# RETURNS
#   NULL
#
# NOTES
#   Requires SYSASM and GI user privileges to execute this operation
########
sub asmcmdsys_process_lscc
{
  my %args;                               # Arguments passed with the command #
  my $ret;                         # asmcmdbase_parse_int_args() return value #

  # common to all components
  my $clustername;                                      # client cluster name #
  my $clusterclass;                                           # cluster class #
  # directories in which the utilities are found
  my @patharr =("$ENV{'ORACLE_HOME'}/bin/",
                "$ENV{'ORACLE_HOME'}/rdbms/bin/");

  # hash storing the parsed results returned by the utilities
  # keys are lowercased cluster names
  # values are references to hashes, each of which is structured like the
  # following example, where the component names will only be defined if they
  # are configured
  #   "name"     => "Cc1" (case-preserved name)
  #   "version"  => "12.2.0.0.0"
  #   "guid"     => "19fc25ffe567cf0abf6a9f1562f7976e"
  #   "asm"      => "YES" (whether ASM Client Cluster is configured)
  #   "gimr"     => "YES" (whether GIMR Client Cluster is configured)
  #   "tfa"      => "YES" (whether TFA Client Cluster is configured)
  #   "rhp"      => "NO"  (whether RHP Client Cluster is configured)
  #   "acfs"     => "NO"  (whether ACFS Client Cluster is configured)
  #   "metacomp" => "ASM" (component from which we obtained the "master"
  #                        metadata, i.e. what gets stored in this hash, used
  #                        when reporting inconsistent configurations)
  my %output = ();

  # component-specific
  my $asmname  = "ASM";                                  # ASM component name #
  my $gimrname = "GIMR";                                # GIMR component name #
  my $tfaname  = "TFA";                                  # TFA component name #
  my $acfsname = "ACFS";                                # ACFS component name #
  my $rhpname  = "RHP";                                  # RHP component name #
  my $kfodop;                                       # arguments to kfod (ASM) #
  my $mgmtcaop;                                  # arguments to mgmtca (GIMR) #
  my $tfactlop;                                   # arguments to tfactl (TFA) #
  my $acfsutilop;                              # arguments to acfsutil (ACFS) #
  my $rhpctlop;                                   # arguments to rhpctl (RHP) #

  # tfactl and acfsutil error output by default goes to stderr; redirect it to
  # stdout in order to look for specific error messages
  my $redirect     = 1;

  # error codes
  # ASM-specific
  # KFOD-00332: "no Client Clusters configured"
  my $noasmcc      = "KFOD-00332";
  # KFOD-00333: "Client Cluster '%s' is not configured"
  my $nothisasmcc  = "KFOD-00333";

  # GIMR-specific
  # token seen in 'srvctl config mgmtdb' output if run on a GIMR client
  my $gimrisclient = "server_cluster_name";
  # MGTCA-1151: "There are no clients for the GIMR Server Cluster"
  my $nogimrcc     = "MGTCA-1151";
  # there is no mgmtca option to list only a specific client cluster

  # TFA-specific
  # prefix to all TFA errors thrown by tfactl
  my $tfaerrprefix = "TFA-00";
  # tfactl doesn't output anything (i.e. there is no error) when there are no
  # client clusters configured
  # TFA-00517: "This client does not exist"
  my $nothistfacc  = "TFA-00517";

  # ACFS-specific
  # prefix to all ACFS errors thrown by acfsutil
  my $acfserrprefix = "ACFS-";
  # ACFS-09809: "No Member Clusters have been registered."
  my $noacfscc      = "ACFS-09809";
  # ACFS-09810: "No credentials were found for the '%s' cluster name."
  my $nothisacfscc  = "ACFS-09810";
  # acfsutil is found in /sbin on unix platforms
  my @acfsutildir   = ("/sbin/");

  # RHP-specific
  # PRGR-119: "Site "%s" does not exist."
  my $nothisrhpcc = "PRGR-119";

  # format strings, headers, and trailer
  # the output format generally matches that of kfod
  my $hdrwidth   = 100;
  my $fmtstr     = "%15s %14s %-32s";
  my $longfmtstr = "%15s %14s %-32s %3s %4s %3s %4s %3s %14s";
  my $header     = '-'x$hdrwidth . "\n" .
                   sprintf($fmtstr, "NAME", "VERSION", "GUID") . "\n" .
                   '='x$hdrwidth . "\n";
  my $longheader = '-'x$hdrwidth . "\n" .
                   sprintf($longfmtstr, "NAME", "VERSION", "GUID", $asmname,
                           $gimrname, $tfaname, $acfsname, $rhpname,
                           "STORAGE ACCESS") . "\n" .
                   '='x$hdrwidth . "\n";
  my $trailer    = '='x$hdrwidth . "\n";

  my $configtrc  = "%s: version=%s guid=%s; %s: version=%s guid=%s";
  # versions normalized to 4 digits, e.g. 12.2.0.0
  # 22780177: we normalize versions to 4 digits before comparing them for
  # mismatches because:
  #   -> rhpctl masks the fifth digit of the version number in their output
  #   -> in development, the fifth digit of the version number may not be the
  #      same across labels at some given point in time
  my $compvsnnorm; # version of component currently being listed
  my $metavsnnorm; # version of "metacomp"

  # get option parameters
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  # --file option: it needs to work on a member cluster / without an ASM
  # instance
  if (defined($args{'file'}))
  {
    asmcmdsys_list_cmf_comps($args{'file'}, !defined($args{'suppressheader'}));
    return;
  }

  # check if SYSASM privileges exist
  if ($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    # ASMCMD-9488: "operations on Client Cluster require SYSASM privilege"
    asmcmdshare_error_msg(9488, undef);
    return;
  }

  # check if Domain Services Cluster is supported on this OS
  if (!asmcmdsys_is_dsc_supported())
  {
    my @eargs = ($ASMCMDSYS_DSC_NAME);
    # ASMCMD-9493: "The '%s' feature is not supported on this operating
    # system."
    asmcmdshare_error_msg(9493, \@eargs);
    return;
  }

  # check if the user executing the command is the GI user
  if (!asmcmdsys_is_gi_user())
  {
    # ASMCMD-9546: "Insufficient permission to execute the command. The command
    # requires an Oracle Grid Infrastructure user."
    asmcmdshare_error_msg(9546, undef);
    return;
  }

  # make additional checks for cluster class in production but in a development
  # environment, skip these checks so that tests continue to work
  if (!asmcmdshare_is_dev_env())
  {
    $clusterclass = asmcmdsys_get_cluster_class();

    # error out if we failed to get the cluster class
    if (!defined ($clusterclass))
    {
      return;
    }

    # error out if we are on a member cluster
    if ($clusterclass eq CLUSTER_CLASS_MEMBER)
    {
      # ASMCMD-9498: "The command is only supported on the Domain Services
      # Cluster."
      asmcmdshare_error_msg(9498, undef);
      return;
    }
  }

  # get cluster name
  $clustername = shift @{$args{'lscc'}};

  # ASM
  asmcmdshare_trace(3, "Querying for ASM Client Clusters", 'y', 'n');

  $kfodop = "op=lscc nohdr=TRUE";
  if (defined ($clustername))
  {
    $kfodop .= " client_cluster=$clustername";
  }

  # Environment variable 'ORACLE_HOME' will be set, otherwise
  # ASMCMD init code will bail out.
  my @result = asmcmdshare_execute_tool ("kfod", ".exe", $kfodop, \@patharr);
  my $resultstr = join("", @result);

  # check if kfod succeeded
  # continue if the reason for "failure" is trying to list a client cluster
  # that isn't configured
  if ($asmcmdglobal_hash{'utilsucc'} ne 'true' &&
      $resultstr !~ m,$nothisasmcc,)
  {
    # ASMCMD-9491: "failed to list the configured Client Clusters"
    asmcmdshare_error_msg(9491, undef);
    asmcmdshare_trace(1, $resultstr, 'y', 'y');
    $asmcmdglobal_hash{'e'} = -1;
    return 1;
  }

  # parse and store the kfod output, if there are (relevant) ASM client
  # clusters configured
  if ($resultstr !~ m,$noasmcc, && $resultstr !~ m,$nothisasmcc,)
  {
    my $row;
    foreach $row (@result)
    {
      my @splitrow = split(" ", $row);
      my %rowinfo = ();
      # normalize the case of the key, but preserve the case of the name
      my $key = lc($splitrow[0]);

      $rowinfo{"name"}     = $splitrow[0];
      $rowinfo{"version"}  = $splitrow[1];
      $rowinfo{"guid"}     = $splitrow[2];
      $rowinfo{"accmode"}  = $splitrow[3];
      $rowinfo{"asm"}      = "YES";
      $rowinfo{"gimr"}     = "NO";
      $rowinfo{"rhp"}      = "NO";
      $rowinfo{"acfs"}     = "NO";
      $rowinfo{"tfa"}      = "NO";
      $rowinfo{"metacomp"} = $asmname;
      $output{$key}        = \%rowinfo;
    }
  }

  # GIMR
  # check if mgmtdb is configured; if not, there can be no mgmtdb clients, so
  # skip this step
  @result = asmcmdshare_execute_tool("srvctl", ".bat",
                                     "config mgmtdb -inner 1", \@patharr);
  $resultstr = join("", @result);
  asmcmdshare_trace(3, $resultstr, 'y', 'n');
  # $$$ This check can be removed once is_dsc_supported works properly.  srvctl
  # will also exit with return code 0 if executed on an mgmtdb client; also
  # skip this step if we are on a mgmtdb client
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true' &&
      $resultstr !~ m,$gimrisclient,)
  {
    asmcmdshare_trace(3, "Querying for GIMR Client Clusters", 'y', 'n');

    # $$$ mgmtca queryRepos is changing their output to JSON format.
    # Temporarily pass the -legacy flag to continue getting the current output
    # so this doesn't break. We will switch to parsing their new output when it
    # is available.
    $mgmtcaop = "queryRepos -legacy";
    @result = asmcmdshare_execute_tool ("mgmtca", ".bat", $mgmtcaop,
                                        \@patharr);
    $resultstr = join("", @result);

    # check if mgmtca succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # ASMCMD-9491: "failed to list the configured Client Clusters"
      asmcmdshare_error_msg(9491, undef);
      asmcmdshare_trace(1, $resultstr, 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1;
      return 1;
    }

    # parse and store the mgmtca output, if there are GIMR client clusters
    # configured
    if ($resultstr !~ m,$nogimrcc,)
    {
      # remove the header, i.e. the first two rows that say
      # The client clusters for the Server Cluster <server cluster name> are:
      # CLUSTER NAME VERSION GUID
      @result = @result[2..$#result];

      my $row;
      foreach $row (@result)
      {
        my (@splitrow) = split(" ", $row);
        # normalize the case of the key, but preserve the case of the name
        my $key = lc($splitrow[0]);

        # if client cluster name is specified, only extract the row for this
        # particular cluster
        next if (defined ($clustername) && $key ne lc($clustername));

        # parse the row
        my %rowinfo = ();
        $rowinfo{"name"}    = $splitrow[0];
        $rowinfo{"version"} = $splitrow[1];
        $rowinfo{"guid"}    = $splitrow[2];

        # if we already have some info for this client cluster
        if (defined ($output{$key}))
        {
          # get the four-digit version numbers to compare
          $compvsnnorm = $rowinfo{"version"};
          $compvsnnorm =~ s/\.\d+$//;
          $metavsnnorm = $output{$key}{"version"};
          $metavsnnorm =~ s/\.\d+$//;

          # error out if the version doesn't match
          if (($compvsnnorm ne $metavsnnorm) ||
              ($rowinfo{"guid"} ne $output{$key}{"guid"}))
          {
            asmcmdshare_trace(3, sprintf($configtrc, $gimrname,
                                         $rowinfo{"version"},
                                         $rowinfo{"guid"},
                                         $output{$key}{"metacomp"},
                                         $output{$key}{"version"},
                                         $output{$key}{"guid"}),
                              'y', 'n');

            my @eargs = ($rowinfo{"name"}, $output{$key}{"metacomp"},
                         $gimrname);
            # ASMCMD-9492: "The configuration for Client Cluster '%s' is
            # inconsistent between components '%s' and '%s'."
            asmcmdshare_error_msg(9492, \@eargs);
            return 1;
          }

          # just indicate that GIMR is configured
          $output{$key}{"gimr"} = "YES";
        }
        # otherwise add a new entry
        else
        {
          $rowinfo{"asm"}      = "NO";
          $rowinfo{"accmode"}  = "-";
          $rowinfo{"gimr"}     = "YES";
          $rowinfo{"tfa"}      = "NO";
          $rowinfo{"acfs"}     = "NO";
          $rowinfo{"rhp"}      = "NO";
          $rowinfo{"metacomp"} = $gimrname;
          $output{$key}        = \%rowinfo;
        }
      }
    }
  }

  # TFA
  # We will have already errored out above if the OS is Windows; tfa is not
  # supported on Windows, and currently the tfactl executable doesn't even
  # exist on Windows
  #
  # check if tfa is configured (i.e. running); if not, there can be no tfa
  # clients, so skip this step
  $tfactlop = "print status";
  @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                     $redirect);
  $resultstr = join("", @result);
  asmcmdshare_trace(3, $resultstr, 'y', 'n');
  if ($resultstr !~ m,$tfaerrprefix,)
  {
    $tfactlop = "client list -details";
    if (defined ($clustername))
    {
      $tfactlop .= " -cluster $clustername";
    }
    @result = asmcmdshare_execute_tool("tfactl", "", $tfactlop, \@patharr,
                                       $redirect);
    $resultstr = join("", @result);

    # check if tfactl succeeded
    # continue if the reason for "failure" is trying to list a client cluster
    # that isn't configured
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true' &&
        $resultstr !~ m,$nothistfacc,)
    {
      # ASMCMD-9491: "failed to list the configured Client Clusters"
      asmcmdshare_error_msg(9491, undef);
      asmcmdshare_trace(1, $resultstr, 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1;
      return 1;
    }

    # parse and store the tfactl output, if there are (relevant) TFA client
    # clusters configured
    if ($resultstr !~ m,$nothistfacc,)
    {
      my $row;
      foreach $row (@result)
      {
        # skip rows that only have whitespace
        next if ($row =~ m/^\s*$/);

        my @splitrow = split(" ", $row);
        my %rowinfo = ();
        # normalize the case of the key, but preserve the case of the name
        my $key = lc($splitrow[0]);

        $rowinfo{"name"} = $splitrow[0];
        $rowinfo{"version"} = $splitrow[1];
        # $$$ currently, tfactl does not yet output a valid GUID, so don't
        # parse a GUID from its output
        $rowinfo{"guid"} = "";

        # if we already have some info for this client cluster
        if (defined ($output{$key}))
        {
          # get the four-digit version numbers to compare
          $compvsnnorm = $rowinfo{"version"};
          $compvsnnorm =~ s/\.\d+$//;
          $metavsnnorm = $output{$key}{"version"};
          $metavsnnorm =~ s/\.\d+$//;

          # error out if the version doesn't match
          # $$$ (eventually, we also want to check that the GUID matches)
          if ($compvsnnorm ne $metavsnnorm)
          {
            asmcmdshare_trace(3, sprintf($configtrc, $tfaname,
                                         $rowinfo{"version"},
                                         $rowinfo{"guid"},
                                         $output{$key}{"metacomp"},
                                         $output{$key}{"version"},
                                         $output{$key}{"guid"}),
                              'y', 'n');

            my @eargs = ($rowinfo{"name"}, $output{$key}{"metacomp"},
                         $tfaname);
            # ASMCMD-9492: "The configuration for Client Cluster '%s' is
            # inconsistent between components '%s' and '%s'."
            asmcmdshare_error_msg(9492, \@eargs);
            return 1;
          }

          # just indicate that TFA is configured
          $output{$key}{"tfa"} = "YES";
        }
        # otherwise add a new entry
        else
        {
          $rowinfo{"asm"}      = "NO";
          $rowinfo{"accmode"}  = "-";
          $rowinfo{"gimr"}     = "NO";
          $rowinfo{"tfa"}      = "YES";
          $rowinfo{"acfs"}     = "NO";
          $rowinfo{"rhp"}      = "NO";
          $rowinfo{"metacomp"} = $tfaname;
          $output{$key}        = \%rowinfo;
        }
      }
    }
  }

  # ACFS
  # check if ACFS is loaded; if not, there can be no clients, so skip this step
  @result = asmcmdshare_execute_tool("acfsdriverstate", ".bat", "loaded",
                                     \@patharr);
  asmcmdshare_trace(3, join("", @result), 'y', 'n');
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true')
  {
    asmcmdshare_trace(3, "Querying for ACFS Client Clusters", 'y', 'n');
    $acfsutilop = "cluster credential -n -l";

    if (defined ($clustername))
    {
      $acfsutilop .= " $clustername";
    }

    my @result = asmcmdshare_execute_tool("acfsutil", ".exe", $acfsutilop,
                                          \@acfsutildir, $redirect);
    my $resultstr = join("", @result);

    # check if acfsutil succeeded
    # continue if the reason for "failure" is trying to list a client cluster
    # that isn't configured
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true' &&
        $resultstr !~ m,$nothisacfscc,)
    {
      # trim the command name prefix that acfsutil outputs
      $resultstr =~ s/.*$acfserrprefix/$acfserrprefix/g;
      # ASMCMD-9491: "failed to list the configured member clusters"
      asmcmdshare_error_msg(9491, undef);
      asmcmdshare_trace(1, $resultstr, 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1;
      return 1;
    }

    # parse and store the acfsutil output, if there are (relevant) ACFS client
    # clusters configured
    if ($resultstr !~ m,$noacfscc, && $resultstr !~ m,$nothisacfscc,)
    {
      my $row;
      foreach $row (@result)
      {
        my @splitrow = split(" ", $row);
        my %rowinfo = ();
        # normalize the case of the key, but preserve the case of the name
        my $key = lc($splitrow[0]);

        $rowinfo{"name"}    = $splitrow[0];
        $rowinfo{"version"} = $splitrow[1];
        $rowinfo{"guid"}    = $splitrow[2];

        # if we already have some info for this client cluster
        if (defined ($output{$key}))
        {
          # get the four-digit version numbers to compare
          $compvsnnorm = $rowinfo{"version"};
          if ($compvsnnorm)
          {
            $compvsnnorm =~ s/\.\d+$//;
          }
          $metavsnnorm = $output{$key}{"version"};
          $metavsnnorm =~ s/\.\d+$//;
          # error out if the version doesn't match or if the GUID doesn't match
          if (($rowinfo{"guid"} &&
              ($rowinfo{"guid"} ne $output{$key}{"guid"})) ||
              ($compvsnnorm &&
              ($compvsnnorm ne $metavsnnorm)))
          {
            asmcmdshare_trace(3, sprintf($configtrc, $acfsname,
                                         $compvsnnorm,
                                         $rowinfo{"guid"},
                                         $output{$key}{"metacomp"},
                                         $metavsnnorm,
                                         $output{$key}{"guid"}),
                                         'y', 'n');
            my @eargs = ($rowinfo{"name"}, $output{$key}{"metacomp"},
                         $acfsname);
            # ASMCMD-9492: "The configuration for member cluster '%s' is
            # inconsistent between components '%s' and '%s'."
            asmcmdshare_error_msg(9492, \@eargs);
            return 1;
          }

          # just indicate that ACFS is configured
          $output{$key}{"acfs"} = "YES";
        }
        # otherwise add a new entry
        else
        {
          $rowinfo{"asm"}      = "NO";
          $rowinfo{"accmode"}  = "-";
          $rowinfo{"gimr"}     = "NO";
          $rowinfo{"rhp"}      = "NO";
          $rowinfo{"acfs"}     = "YES";
          $rowinfo{"tfa"}      = "NO";
          $rowinfo{"metacomp"} = $acfsname;
          $output{$key}        = \%rowinfo;
        }
      }
    }
  }

  # RHP
  # check if rhpserver is configured; if not, there can be no clients, so skip
  # this step
  @result = asmcmdshare_execute_tool("srvctl", ".bat", "config rhpserver",
                                     \@patharr);
  asmcmdshare_trace(3, (join("", @result)), 'y', 'n');
  if ($asmcmdglobal_hash{'utilsucc'} eq 'true')
  {
    asmcmdshare_trace(3, "Querying for RHP Client Clusters", 'y', 'n');
    $rhpctlop = "query client -internal 1";

    if (defined ($clustername))
    {
      $rhpctlop .= " -client $clustername";
    }

    # on Windows, rhpctl doesn't exist because rhpserver isn't supported
    my @result = asmcmdshare_execute_tool("rhpctl", "", $rhpctlop,
                                          \@patharr);

    my $resultstr = join("", @result);

    # check if rhpctl succeeded
    # continue if the reason for "failure" is trying to list a client cluster
    # that isn't configured
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true' &&
        $resultstr !~ m,$nothisrhpcc,)
    {
      # ASMCMD-9491: "failed to list the configured Client Clusters"
      asmcmdshare_error_msg(9491, undef);
      asmcmdshare_trace(1, $resultstr, 'y', 'y');
      $asmcmdglobal_hash{'e'} = -1 ;
      return;
    }

    # parse and store the rhpctl output, if there are (relevant) RHP client
    # clusters configured
    if ($resultstr !~ m,$nothisrhpcc,)
    {
      my $row;
      foreach $row (@result)
      {
        # guid and version might not be populated (they aren't populated in the
        # case where the rhpclient hasn't been started)
        #
        # Output format:
        # $rhpctl query client -inner 1
        # #@=result[0]: site={...} guid={...} version={...}
        # #@=result[1]: site={...} guid={...} version={...}
        # #@=result[2]: site={...} guid={...} version={...}
        #
        # $rhpctl query client -client myclient -inner 1
        # #@=result[0]: site={...} guid={...} version={...} ...
        if ($row =~ /site=\{(\S+)\} guid=\{(\S*)\} version=\{(\S*)\}/)
        {
          my %rowinfo = ();
          # normalize the case of the key, but preserve the case of the name
          my $key = lc($1);

          $rowinfo{"name"}    = $1;
          $rowinfo{"guid"}    = $2;
          $rowinfo{"version"} = $3;

          # if we already have some info for this client cluster
          if (defined ($output{$key}))
          {
            # get the four-digit version numbers to compare
            $compvsnnorm = $rowinfo{"version"};
            if ($compvsnnorm)
            {
              $compvsnnorm =~ s/\.\d+$//;
            }
            $metavsnnorm = $output{$key}{"version"};
            $metavsnnorm =~ s/\.\d+$//;

            # error out if the version is defined and doesn't match or if the
            # guid is defined and doesn't match (version will not be defined if
            # the user didn't specify it and rhpclient hasn't been started;
            # guid will not be defined if rhpclient hasn't been started)
            if (($rowinfo{"guid"} &&
                 ($rowinfo{"guid"} ne $output{$key}{"guid"})) ||
                ($compvsnnorm &&
                 ($compvsnnorm ne $metavsnnorm)))
            {
              asmcmdshare_trace(3, sprintf($configtrc, $rhpname,
                                           $rowinfo{"version"},
                                           $rowinfo{"guid"},
                                           $output{$key}{"metacomp"},
                                           $output{$key}{"version"},
                                           $output{$key}{"guid"}),
                                'y', 'n');

              my @eargs = ($rowinfo{"name"}, $output{$key}{"metacomp"},
                           $rhpname);
              asmcmdshare_error_msg(9492, \@eargs);
              return 1;
            }

            # just indicate that RHP is configured
            $output{$key}{"rhp"} = "YES";
          }
          # otherwise add a new entry
          else
          {
            $rowinfo{"asm"}      = "NO";
            $rowinfo{"accmode"}  = "-";
            $rowinfo{"gimr"}     = "NO";
            $rowinfo{"tfa"}      = "NO";
            $rowinfo{"acfs"}     = "NO";
            $rowinfo{"rhp"}      = "YES";
            $rowinfo{"metacomp"} = $rhpname;
            $output{$key}        = \%rowinfo;
          }
        }
      }
    }
  }

  # cases where there are no client clusters to list
  if (!%output)
  {
    # the specified client cluster is not configured
    if (defined ($clustername))
    {
      my @eargs = ($clustername);
      # ASMCMD-9490: "Client Cluster '%s' is not configured"
      asmcmdshare_error_msg(9490, \@eargs);
      return 1;
    }
    # no client clusters are configured
    else
    {
      # ASMCMD-9489: "no Client Clusters configured"
      asmcmdshare_error_msg(9489, undef);
      return 0;
    }
  }

  # print the header if it is not suppressed
  if (!defined ($args{"suppressheader"}))
  {
    if (defined ($args{'l'}))
    {
      asmcmdshare_print($longheader);
    }
    else
    {
      asmcmdshare_print($header);
    }
  }

  # print the rows of output; the output rows are in case-insensitive
  # lexicographic order
  my $key;
  foreach $key (sort(keys %output))
  {
    my $cluster;
    $cluster = $output{$key};
    my $accmode = $cluster->{"accmode"};
    my $outputrow = sprintf($fmtstr, $cluster->{"name"},
                                     $cluster->{"version"},
                                     $cluster->{"guid"});
    # include details
    if (defined ($args{'l'}))
    {
      if (defined ($args{'suppressheader'}))
      {
        # add a space
        $outputrow .= " ";

        # add info about which components are configured
        if ($cluster->{"asm"} eq "YES")
        {
          $outputrow .= "$asmname ($accmode Storage Access),";
        }
        if ($cluster->{"gimr"} eq "YES")
        {
          $outputrow .= "$gimrname,";
        }
        if ($cluster->{"tfa"} eq "YES")
        {
          $outputrow .= "$tfaname,";
        }
        if ($cluster->{"acfs"} eq "YES")
        {
          $outputrow .= "$acfsname,";
        }
        if ($cluster->{"rhp"} eq "YES")
        {
          $outputrow .= "$rhpname,";
        }

        # remove the trailing comma
        $outputrow =~ s/,$//;
      }
      else
      {
        # use the format string with the component columns instead; the storage
        # access mode should be displayed as the last column (i.e. after the
        # per-component information)
        $outputrow = sprintf($longfmtstr, $cluster->{"name"},
                                          $cluster->{"version"},
                                          $cluster->{"guid"},
                                          $cluster->{"asm"},
                                          $cluster->{"gimr"},
                                          $cluster->{"tfa"},
                                          $cluster->{"acfs"},
                                          $cluster->{"rhp"},
                                          $cluster->{"accmode"});
      }
    }

    asmcmdshare_print($outputrow . "\n");
  }

  # print the trailer if the header is not suppressed
  if (!defined ($args{"suppressheader"}))
  {
    asmcmdshare_print($trailer);
  }

  return 0;
}

########
# NAME
#   asmcmdsys_process_chcc
#
# DESCRIPTION
#   To modify a client cluster for ASM, GIMR, TFA, ACFS, and RHP
#
# PARAMETERS
#   $dbh   (IN)  - initialized database handle, must be non-null.
#
# RETURNS
#   NULL
#
# NOTES
#   Requires SYSASM and GI user privileges to execute this operation
########
sub asmcmdsys_process_chcc
{
  my $dbh = shift;                                            # get db handle #
  my %args;                               # arguments passed with the command #
  my $ret;                         # asmcmdbase_parse_int_args() return value #
  my $sth;

  # common to all components
  my $clustername;                                      # client cluster name #
  my $clusterclass;

  # ASM-specific
  my $directacc;                        # direct access to disks or not (ASM) #
  my $srvctlop;                  # arguments to srvctl for starting IOS (ASM) #

  # directories in which the utilties are found
  my @patharr   = ("$ENV{'ORACLE_HOME'}/bin/",
                   "$ENV{'ORACLE_HOME'}/rdbms/bin/");
  # ASMCMD-9490 "member cluster '%s' is not configured"
  my $nothingexists = "ASMCMD-9490";         # no components configured error #

  # for redirecting stderr to stdout in order to look for specific error
  # messages that go to stderr
  my $redirect = 1;

  # get command parameters
  $ret = asmcmdsys_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  # get cluster name
  $clustername = shift @{$args{'chcc'}};

  # check if SYSASM privileges exist
  if ($asmcmdglobal_hash{'contyp'} ne "sysasm")
  {
    # ASMCMD-9488: "operations on Client Cluster require SYSASM privilege"
    asmcmdshare_error_msg(9488, undef);
    return;
  }

  # check if Domain Services Cluster is supported on this OS
  if (!asmcmdsys_is_dsc_supported())
  {
    my @eargs = ($ASMCMDSYS_DSC_NAME);
    # ASMCMD-9493: "The '%s' feature is not supported on this operating
    # system."
    asmcmdshare_error_msg(9493, \@eargs);
    return;
  }

  # check if the user executing the command is the GI user
  if (!asmcmdsys_is_gi_user())
  {
    # ASMCMD-9546: "Insufficient permission to execute the command. The command
    # requires an Oracle Grid Infrastructure user."
    asmcmdshare_error_msg(9546, undef);
    return;
  }

  # make additional checks for cluster class in production but in a development
  # environment, skip these checks so that tests continue to work
  if (!asmcmdshare_is_dev_env())
  {
    $clusterclass = asmcmdsys_get_cluster_class();

    # error out if we failed to get the cluster class
    if (!defined ($clusterclass))
    {
      return;
    }

    # error out if we are on a member cluster
    if ($clusterclass eq CLUSTER_CLASS_MEMBER)
    {
      # ASMCMD-9498: "The command is only supported on the Domain Services
      # Cluster."
      asmcmdshare_error_msg(9498, undef);
      return;
    }
  }

  # check if the cluster we are attempting to modify exists and error out if
  # not
  my @result = asmcmdshare_execute_tool("asmcmd", ".bat", "lscc $clustername",
                                        \@patharr, $redirect);
  if (join("", @result) =~ m,$nothingexists,)
  {
    my @eargs = ($clustername);
    # ASMCMD-9490: "member cluster '%s' is not configured"
    asmcmdshare_error_msg(9490, \@eargs);
    $asmcmdglobal_hash{'e'} = -1 ;
    return;
  }

  # determine the requested disk access mode - the command-line usage will
  # enforce that the user specifies either direct or indirect, so assume
  # indirect unless the user specifies direct
  $directacc = 0;
  if (defined ($args{'direct'}))
  {
    $directacc = 1;
  }

  # Start IOS if indirect access
  if ($directacc == 0)
  {
    $srvctlop = "start ioserver";

    my @result = asmcmdshare_execute_tool("srvctl", ".bat", $srvctlop,
                                          \@patharr);
    # check if srvctl succeeded
    if ($asmcmdglobal_hash{'utilsucc'} ne 'true')
    {
      # ignore error. write only to trace file
      asmcmdshare_trace(1, join("", @result), 'y', 'n');
    }
  }

  # modify the configuration - if the user asked for a no-op, the API will
  # error out
  $sth = $dbh->prepare(q{
    begin
      dbms_diskgroup.changeclientcluster (:clname, :direct_access);
    end;
  });

  # bind input parameters
  $sth->bind_param(":clname", $clustername);
  $sth->bind_param(":direct_access", $directacc);

  $ret = $sth->execute();
  if (!defined($ret))
  {
    asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
    $asmcmdglobal_hash{'e'} = -1;
    return;
  }
}

1;

OHA YOOOO