# Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      asmcmdshare - ASM CoMmanD line interface (Shared Functionality Module)
#
#    DESCRIPTION
#      ASMCMD is a Perl utility that provides easy nagivation of files within
#      ASM diskgroups.  This module provides some basic functionalities
#      that are shared by multiple other modules, such as path, normalization,
#      SQL access, etc.
#
#    NOTES
#      usage: asmcmdcore [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    apfwkr     04/13/15 - Backport dacavazo_bug-20657441 from main
#    manuegar   08/19/13 - Backport manuegar_bug-13951456 from main
#    apfwkr     01/05/12 - Backport mchimang_c2 from main
#    moreddy    04/28/11 - fix lrg 5169801 on NT 
#    pvenkatr   04/11/11 - Backport pvenkatr_bug-11829123 from main
#    moreddy    01/13/11 - Backport moreddy_bug-8667038 from main
#    dacavazo   03/10/15 - 20657441: added optional argument to
#                          asmcmdshare_do_stmt to trim DBD ERROR from trace
#    amitroy    04/29/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 file
#    moreddy    03/22/10 - Adding more tracing
#    moreddy    01/19/10 - Adding tracing infrastructure
#    moreddy    12/17/09 - bug 9209614 ls --permission throws script error 
#    mproddut   11/19/09 - bug 9104887 uninitialised value with "du"
#    mproddut   10/13/09 - bug 8874854 lsdg -g throws error 
#    pvenkatr   09/03/09 - Help message from xml file.
#    danagara   08/06/09 - Cntrl + C handled properly bug-8646861
#    sanselva   06/26/09 - 'ls -L --permission' issue, include diskgroup_number
#                          in usergroupnumber2name
#    sanselva   06/03/09 - add asmcmdglobal_deprecated_options
#    sanselva   05/13/09 - corrected invalid column in usergroupnumber2name
#    heyuen     09/19/08 - fix signal handler for infinite commands
#    heyuen     08/26/08 - add voting file
#    heyuen     07/28/08 - add commands array, fix time format for debug
#    heyuen     05/22/08 - add get_ugnum_from_ugname
#    heyuen     04/15/08 - move unix_os table
#    heyuen     03/30/08 - enable ls -p
#    heyuen     11/08/07 - add do_construct_select
#    heyuen     09/12/07 - flush print line
#    heyuen     08/10/07 - refresh from main
#    hqian      06/04/07 - Fix three stragglers that are still using
#                          v$asm_diskgroup (should use _stat)
#    heyuen     05/25/07 - add return codes for errors
#    dfriedma   05/24/07 - Remove unbalanced column
#    heyuen     04/17/07 - untaint the file to log debugging info
#    hqian      03/05/07 - Add asmcmdshare_version_cmp; update version checks
#    hqian      03/02/07 - modify asmcmdshare_get_dg to include _stat and gv$
#                          options
#    hqian      07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks 
#    hqian      06/15/06 - Move asmcmdbase_ls_calc_min_col_wid to this module 
#    hqian      01/25/06 - Split off asmcmdshare.pm from asmcmdshare.pm
#    hqian      01/19/06 - More modularization 
#    hqian      01/18/06 - #4939032: remove the main() and shell() commands
#    hqian      01/18/06 - #4939032: format asmcmdbase.pm into a module 
#    hqian      01/18/06 - Rename asmcmdcore to asmcmdbase.pm, inherit history
#    hqian      01/18/06 - #4939032: split up asmcmdcore into modules
#    hqian      07/19/05 - Remove RCS header
#    hqian      06/23/05 - #4450221: support wildcards for CD
#    hqian      05/18/05 - Mention 'missing view attributes' in help ls 
#    hqian      05/03/05 - #4329688: improve SQL efficiency 
#    hqian      04/13/05 - ls_get_file_info() -> ls_process_file()
#    hqian      04/08/05 - Improve implementation of ls 
#    hqian      04/08/05 - Improve help documentation
#    hqian      04/07/05 - LRG 1843355: include seconds in mod-time 
#    hqian      04/01/05 - #4261342: use asmcmd messages for certain errors 
#    hqian      02/28/05 - #4204122: change NLS date format for minute to 'MI' 
#    hqian      10/27/04 - hqian_asmcmd_13306_linux_3
#    hqian      10/19/04 - Rename asmcmd0 to asmcmdcore
#    hqian      08/03/04 - hqian_asmcmd_13306_linux_2
#    hqian      07/28/04 - Add % as wildcard char in addition to *. 
#    hqian      07/13/04 - Add implementation of find [-t <type>].
#    hqian      06/30/04 - Make code that uses BigInt work for both Perl 5.6.1 
#                          and Perl 5.8.3; take out -c <connect_string> 
#                          functionality.
#    hqian      06/29/04 - Fix 10gR1 compatibility issues; fix alias name
#                          case-sensitive bug, should be case insensitive
#                          but case retentive. 
#    hqian      06/25/04 - Rename the main program from asmcmd to asmcmd0, so
#                          that we can name the wrapper script asmcmd; rename
#                          global constants, global variables, and function 
#                          names so that they are prefixed by 'asmcmd0_',
#                          as per coding style standards; fix ORA-15128 bug.
#    hqian      06/23/04 - Inaccurate error message bug: add error message
#                          8004, do not print error when '*' matches nothing;
#                          fix rm -rf * double error message bug; fix find 
#                          diskgroup bug; fix print header in empty directory
#                          bug; fix space in alias name bug.
#    hqian      06/22/04 - Give the option to turn off the functionality of 
#                          the -c flag; add constants for better code design. 
#    hqian      06/09/04 - Fix bugs; improve comments. 
#    hqian      06/07/04 - Organize code for better maintenance; code review 
#                          changes (suggested by Dave Friedman).
#    hqian      06/01/04 - Fix some bugs.
#    hqian      05/24/04 - Implement rm [-rf] and rm *; some code review fixes.
#    hqian      05/20/04 - Implement help, instance_type security check, 
#                        - connection error checks, and debug mode. 
#    hqian      05/18/04 - Implement ls flags and lsct. 
#    hqian      05/14/04 - hqian_asmcmd_13306
#    hqian      04/21/04 - Creation
#
#
#
#############################################################################
#
############################ Functions List #################################
#
# Parameter Parsing Routines
#   asmcmdshare_is_wildcard_cmd
#
# Error Routines
#   asmcmdshare_error_msg
#   asmcmdshare_signal_exception
#   asmcmdshare_assert
#   asmcmdshare_signal_handler
#
# Normalization Routines
#   asmcmdshare_normalize_path
#   asmcmdshare_validate_path
#   asmcmdshare_validate_path_recurse
#   asmcmdshare_validate_dir
#   asmcmdshare_validate_dg
#   asmcmdshare_make_absolute
#   asmcmdshare_cdup_update_ids
#
# Misc Routines
#   asmcmdshare_ls_calc_min_col_wid
#   asmcmdshare_version_cmp
#   asmcmdshare_get_asm_version
#
# SQL Routines
#   asmcmdshare_cur_julian_date
#   asmcmdshare_get_insttype
#   asmcmdshare_get_file
#   asmcmdshare_get_subdirs
#   asmcmdshare_get_subdir_simple
#   asmcmdshare_run_find_sql
#   asmcmdshare_get_par_id
#   asmcmdshare_get_dg
#   asmcmdshare_get_redund
#   asmcmdshare_get_gnum_from_gname
#   asmcmdshare_get_gname_from_gnum
#   asmcmdshare_do_select
#   asmcmdshare_fetch
#   asmcmdshare_finish
#   asmcmdshare_do_stmt
#   asmcmdshare_do_prepare
#   asmcmdshare_do_validate_sth
#   asmcmdshare_do_execute
#
# XML Parsing Routines
#   asmcmdshare_xml_start_handler
#   asmcmdshare_xml_char_handler
#   asmcmdshare_xml_end_handler
#
# Strng Trimming Routines
#   asmcmdshare_trim_str
#############################################################################

package asmcmdshare;
require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(asmcmdshare_is_wildcard_cmd
                 asmcmdshare_is_no_instance_cmd
                 asmcmdshare_error_msg
                 asmcmdshare_signal_exception
                 asmcmdshare_assert
                 asmcmdshare_signal_handler
                 asmcmdshare_normalize_path
                 asmcmdshare_make_absolute
                 asmcmdshare_cur_julian_date
                 asmcmdshare_ls_calc_min_col_wid
                 asmcmdshare_version_cmp
                 asmcmdshare_get_asm_version
                 asmcmdshare_get_insttype
                 asmcmdshare_get_file
                 asmcmdshare_get_subdirs
                 asmcmdshare_get_subdir_simple
                 asmcmdshare_run_find_sql
                 asmcmdshare_get_par_id
                 asmcmdshare_get_dg
                 asmcmdshare_get_redund
                 asmcmdshare_get_gnum_from_gname
                 asmcmdshare_get_gname_from_gnum
                 asmcmdshare_do_construct_select
                 asmcmdshare_do_select
                 asmcmdshare_fetch
                 asmcmdshare_finish
                 asmcmdshare_do_stmt
                 asmcmdshare_do_prepare
                 asmcmdshare_do_validate_sth
                 asmcmdshare_do_execute
                 asmcmdshare_getpswd
                 asmcmdshare_getchr_noecho
                 asmcmdshare_usergroupnumber2name
                 asmcmdshare_usernumber2name
                 asmcmdshare_get_ugnum_from_ugname
                 asmcmdshare_print_cmds
                 asmcmdshare_check_option_consistency
                 asmcmdshare_handle_deprecation
		 asmcmdshare_get_help_desc
                 asmcmdshare_get_help_syntax
                 asmcmdshare_trace
                 asmcmdshare_trim_str
                 %asmcmdshare_unix_os
                 %asmcmdshare_trace_levels
                );

use strict;
use DBI;
use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat);

use asmcmdglobal qw(%asmcmdglobal_hash
                    %asmcmdglobal_options
                    %asmcmdglobal_deprecated_options
                    @asmcmdglobal_is_wildcard_callbacks
                    @asmcmdglobal_syntax_error_callbacks
                    @asmcmdglobal_no_instance_callbacks
                    @asmcmdglobal_error_message_callbacks
                    @asmcmdglobal_signal_exception_callbacks
                    $ASMCMDGLOBAL_WCARD_CHARS
                    $ASMCMDGLOBAL_VER_10gR1
                    $ASMCMDGLOBAL_VER_10gR2
                    $ASMCMDGLOBAL_VER_11gR1
                    $ASMCMDGLOBAL_VER_11gR2
                    $asmcmdglobal_trace_path
                    $asmcmdglobal_clsecho
                    $asmcmdglobal_product
                    $asmcmdglobal_facility
                    );
use POSIX qw(:termios_h);

############################ Global Constants ###############################
my ($ASMCMDSHARE_CSET) = '[\w .\-#$]';  # C-set for alphanumeric, '_', ' ', #
                                                    #  '.', '-', '#', '$' . #

my ($ASMCMDSHARE_N_CSET) = '[^\w .\-#$]';  # Char-set for all chars except  #
                                             #  those in $ASMCMDSHARE_CSET. #

my ($ASMCMDSHARE_CSET_W_WCARD) = '[\w %*.\-#$]';        # Char-set for all  #
                               # $ASMCMDSHARE_CSET chars plus $WCARD_CHARS. #

my ($ASMCMDSHARE_N_CSET_W_WCARD) = '[^\w %*.\-#$]'; # C-set for all except  #
                                               # $ASMCMDSHARE_CSET_W_WCARD. #

my ($ASMCMDSHARE_MAXPASSWD) = 256;      # Max length of user passwd input   #

# List of possible platforms.
our (%asmcmdshare_unix_os) = ( aix          => 'aix',
                               bsdos        => 'bsdos',
                               dgux         => 'dgux',
                               dynixptx     => 'dynixptx',
                               freebsd      => 'freebsd',
                               linux        => 'linux',
                               hpux         => 'hpux',
                               irix         => 'irix',
                               openbsd      => 'openbsd',
                               dec_osf      => 'dec_osf',
                               sco_sv       => 'sco_sv',
                               svr4         => 'svr4',
                               unicos       => 'unicos',
                               unicosmk     => 'unicosmk',
                               solaris      => 'solaris',
                               sunos        => 'sunos',
                             );

our (%asmcmdshare_trace_levels) = ( errors     => '1',
                                    warnings   => '2',
                                    normal     => '3',
                                    info       => '4',
                                    debug      => '5',
                                  );
                      
######
# Globals used for xml parsing.
######
my ($xmlparser) ="";
my ($xmlfile)="";
my ($xmlparsed) = 0 ;               # to parse xml file only once.
my ($cmdName)="";
my ($text)= "" ;
my ($helpcmd) = "";
my ($cmdNode) = "command";
my ($inCmdNode) = "FALSE";
#my ($inOptDesc) = "FALSE";
#my ($optDesc) = "";
#my ($helpstr) = "";

# hash table of data for each commands
my (%hCmdDesc) = ();
my (%hSyntax) = () ;
my (%hExample) = () ;
my (%hOptDesc) = ();

# Help msg topics
my ($synopStr) = "Synopsis";
my ($descStr)  = "Description";
my ($exampStr) = "Example";


######################### Parameter Parsing Routines #########################
########
# NAME
#   asmcmdshare_is_wildcard_cmd
#
# DESCRIPTION
#   This routine determines if a command allows the use of wild cards.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   1 if $arg is a command that can take wildcards as part of its argument, 
#   0 otherwise.
#
# NOTES
#   This routine calls the callbacks from each module to check if $arg
#   supports the use of wildcards.  It asserts that the command is found
#   in only one module.
########
sub asmcmdshare_is_wildcard_cmd 
{
  my ($arg) = shift;
  my ($module);
  my ($use_wildcard) = 0;
  my ($count) = 0;
  my (@eargs);

  foreach $module (@asmcmdglobal_is_wildcard_callbacks)
  {
    if ($module->($arg))
    {
      $use_wildcard = 1;
      $count++;
    }
  }

  # Assert here that 0 <= count <= 1.
  @eargs = ("asmcmdshare_is_wildcard_cmd_05", $arg, $count);
  asmcmdshare_assert( (($count >= 0) && ($count <= 1)), \@eargs);

  return $use_wildcard;
}


########
# NAME
#   asmcmdshare_is_no_instance_cmd
#
# DESCRIPTION
#   This routine is shared/general routine that 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
#   does not exist, 0 otherwise.
#
# NOTES
#   This routine calls the callbacks from each module to check if $arg
#   can run without an ASM instance.  It asserts that the command is found
#   in only one module.
########
sub asmcmdshare_is_no_instance_cmd 
{
  my ($arg) = shift;
  my ($module);
  my ($no_instance_necessary) = 1;
  my ($count) = 0;
  my (@eargs);

  foreach $module (@asmcmdglobal_no_instance_callbacks)
  {
    if (!$module->($arg))
    {
      $no_instance_necessary = 0;
      last;
    }
  }

  return $no_instance_necessary;
}


############################# Error Routines #################################
########
# NAME
#   asmcmdshare_error_msg
#
# DESCRIPTION
#   This function provides the main interface for recorded errors.  All
#   modules must call this function to record an error.
#   This routine prints error and exception messages to STDERR using clsecho.
#
# PARAMETERS
#   err_num   (IN) - ASMCMD internal error number.
#   args_ref  (IN) - (Optional) Reference to array of error arguments
#
# RETURNS
#   Null.
########
sub asmcmdshare_error_msg 
{
  my ($err_num, $args_ref) = @_;
  my ($module);
  my (@eargs);                                   # Array of error arguments. #
  my ($argument, $stack, $buf);
  my ($clsecho_mesg) = '';
  my ($exit) = 0;      # Whether to exit ASMCMD at the end of this function. #
 
  # Assert that $err_num is not within 8000-9400, inclusive.
  @eargs = ("asmcmdshare_error_msg_05", $err_num);
  asmcmdshare_assert( (($err_num > 8000) && ($err_num < 9400)) , \@eargs);
 
  if ($err_num == 8103)
  {
    if (defined($args_ref) && defined($args_ref->[0]))
    {
      $clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
      ."$asmcmdglobal_facility -m $err_num '$args_ref->[0]'";
    }
    else
    {
      $clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
      ."$asmcmdglobal_facility -m $err_num undefined";
    }
  }

  # In the following cases, $DBI::errstr should already hold the appropriate
  # error message, as it's null statement handle or null database handle
  # exception.  Thus, no need to run SQL.  Return immediately after printing
  # $DBI::errstr.

  # Error 8100, select statement failed.
  # Error 8101, see ORA-message for details.
  # Exception 8200, ORA-03113: end-of-file on communication channel
  elsif (($err_num == 8100) || ($err_num == 8101) || ($err_num == 8200)) 
  {
    $buf = "$DBI::errstr";

    # Bug-5007830: exit if we have ORA-03113 or ORA-03114.
    if (($DBI::errstr =~ m,311[34],) || ($DBI::errstr =~ m,3135,))
    {
      $exit = 1;
    }
  }
  
  # Exception 8202, asmcmd internal error.  Note that the Oracle equivalent
  # here is ORA-00600, but there is no predictable way to force that error, 
  # so print asmcmd message instead.
  elsif ($err_num == 8202) 
  {
    $clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
    ."$asmcmdglobal_facility -m $err_num '";
    foreach $argument (@{$args_ref})
    {
      $clsecho_mesg .= "[$argument] ";
    }
    $clsecho_mesg .= "'";
  }

  else
  { 
    $clsecho_mesg ="$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
    ."$asmcmdglobal_facility -m $err_num";

    foreach $argument (@{$args_ref})
    {
      if($^O =~ /win/i)
      {
        $argument      =~ s/\"/\"\"/g;
        $clsecho_mesg .= " \"$argument\"";
      }
      else
      {
        $clsecho_mesg .= " '$argument'";
      }
    }
  }

  if ($clsecho_mesg ne '')
  {
    
    if ($clsecho_mesg =~ /\^/)
    {
      $clsecho_mesg =~ /([^\r^\t]+)\^([^\r^\t]+)/; 
      $clsecho_mesg="$1\^$2";
    }
    else
    {
      $clsecho_mesg =~ /([^\r^\t]+)/;
      $clsecho_mesg = $1;
    }
   
    eval
    {
      $buf = `$clsecho_mesg`;
    };
  }

  if ($@ ne '')
  { 
    print "$@";
    return;
  }

  elsif ($buf ne '')
  { 
    #removing the new line char in $buf
    chomp($buf); 
    # Error 8020: could not write into trace file. Hence, print it only on console.
    if ($err_num == 8020)
    {
      print STDERR "$buf\n";
    }  
    else
    { 
      asmcmdshare_trace(1,"$buf", 'y', 'y');
      if($asmcmdglobal_hash{'verbose'} eq 'debug')
      {
        $stack = Carp::longmess("The stack trace");
        $stack =~ s/\t//g;
        asmcmdshare_trace(5, "$stack", 'y', 'n');
      }
    }
  }
 
  if ($asmcmdglobal_hash{'mode'} eq 'n')
  {
   $asmcmdglobal_hash{'e'} = -1;
   $|++;
  }

  if ($exit)
  {
    exit 1;
  }
  return;
}

########
# NAME
#   asmcmdshare_signal_exception
#
# DESCRIPTION
#   This function provides the main interface for signaled exceptions.
#   All modules must call this function to record an error.
#
# PARAMETERS
#   exception_num   (IN) - ASMCMD internal error/exception number.
#   args_ref        (IN) - (Optional) Reference to array of error arguments
#
# RETURNS
#   Never returns; always exits 1.
#
# NOTES
#   Only call this routine for exceptions.  This routine always exits 1.
#
#   Usually, each error type has a fixed number of error arguments that are 
#   displayed, if the error is an external error.  If the error is internal, 
#   then arbitrary number of arguments can be included.
########
sub asmcmdshare_signal_exception 
{
  my ($exception_num, $args_ref) = @_;
  my ($module);

  # Assert that $exception_num is within 8200-8299, inclusive.
  if (($exception_num < 8000) || ($exception_num > 9400))
  {
    asmcmdshare_trace(1, "asmcmd: 8202: [asmcmdshare_signal_exception_05] "
               ."[$exception_num] ", 'y', 'y');
    die "\n";
  }
 
  asmcmdshare_error_msg($exception_num, $args_ref);

  # All exceptions end session.
  exit 1;
}

########
# NAME
#   asmcmdshare_assert
#
# DESCRIPTION
#   This function assert that first argument is true, or signals exception.
#
# PARAMETERS
#   is_true     (IN) - assert that this argument is TRUE.
#   args_ref    (IN) - (Optional) Reference to array of assert arguments
#
# RETURNS
#   Null if is_true is TRUE; signals internal error otherwise.
#
# NOTES
#   The assert error arguments get displayed when the error is signaled.  The
#   following is the convention:
#     argument 0 - the function name plus a number, e.g. asmcmdshare_assert_05
#     argument 1 and onward - values of variables that are being evaluated
#                             for truth.
########
sub asmcmdshare_assert
{
  my ($is_true, $args_ref) = @_;

  # Assert that this is true or signal exception.
  if (!$is_true)
  {
    my @eargs = ($args_ref);
    asmcmdshare_signal_exception(8202, \@eargs);
  }

  return;
}

########
# NAME
#   asmcmdshare_signal_handler
#
# DESCRIPTION
#   This routine catches and handles OS signals.
#
# PARAMETERS
#   sigtype (IN) - string: type of signal caught.
#
# RETURNS
#   Null.
#
# NOTES
#   Currently, this routine catches only SIGINT.
########
sub asmcmdshare_signal_handler
{
  my ($sigtype) = shift;

  if ($sigtype eq 'INT')
  {
    if ($asmcmdglobal_hash{'running'} == 1)
    {
      $asmcmdglobal_hash{'running'}=0;
      return;
    }

    if (defined ($asmcmdglobal_hash{'sth'}))
    {
      $asmcmdglobal_hash{'sth'}->cancel;
      asmcmdshare_finish($asmcmdglobal_hash{'sth'});
      $asmcmdglobal_hash{'sth'} = undef;
    }
 
    print STDERR "\n";
    die;
  }
}
##############################################################################




########################### Normalization Routines ###########################
# NOTE:
#   ASMCMD defines the parent and reference indices for the directory
#   '+' to be both -1.  ASMCMD defines the reference index of a diskgroup
#   to be (group_number * 2^24) and its parent index to be -1.
#
########
# NAME
#   asmcmdshare_normalize_path
#
# DESCRIPTION
#   This routine is the top-level normalization routine.  It calls 
#   asmcmdshare_make_absolute() to get an absolute path to $path, if it's not 
#   already absolute.  It then calls asmcmdshare_validate_path() to check if 
#   every directory level of the path exists.  
#
# PARAMETERS
#   dbh      (IN)  - initialized database handle, must be non-null.
#   path     (IN)  - the path to be normalized.
#   spprserr (IN)  - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#   ret_ref  (OUT) - reference to return flag: 0 if path normalized; -1 if
#                    path failed to normalize.
#
# RETURNS
#   A hash of references to four parallel arrays:
#     $ret{'path'}   - reference to array of normalized paths.
#     $ret{'ref_id'} - reference to array of reference indices for the files
#                      or directories specified by the paths.
#     $ret{'par_id'} - reference to array of parent indices for the files or
#                      directories specified by the paths.
#     $ret{'gnum'}   - reference to array of group numbers for the files or 
#                      directories specified by the paths.
#
# NOTES
#   ASMCMD defines the reference index, the parent index, and the group 
#   number of '+' to be -1.  ASMCMD also defines the reference index of a 
#   diskgroup to be (group_number * 2^24).
########
sub asmcmdshare_normalize_path 
{
  my ($dbh, $path, $spprserr, $ret_ref) = @_;

  my ($full_path);                                          # Absolute path. #
  my (%ret);      # Hash of references to parallel arrays; see RETURN above. #
  my (@paths);     # Array of normalized path(s), plural if using wildcards. #
  my (@ref_ids);                      # Parallel array of reference indices. #
  my (@par_ids);     # Parallel array of parent indices, parallel to @paths. #
  my (@dg_nums);        # Parallel array of diskgroup numbers for the paths. #

  $full_path = asmcmdshare_make_absolute($path);

  $full_path =~ s/\/{2,}/\//g;       # Remove all duplicate forward slashes. #
  $full_path =~ s,^\+/,\+,;          # '+/' should become just '+'.          #
  $$ret_ref = 0;                     # Zero by default.                      #

  # If the full path is '+', then there is no need to call 
  # asmcmdshare_validate_path().  ASMCMD defines the reference and parent 
  # indices of '+' to be -1.
  if ($full_path eq '+') 
  {
    push (@paths, '+');
    push (@ref_ids, -1);
    push (@par_ids, -1);
    push (@dg_nums, -1);

    $ret{'path'} = \@paths;
    $ret{'ref_id'} = \@ref_ids;
    $ret{'par_id'} = \@par_ids;
    $ret{'gnum'} = \@dg_nums;

    return %ret;
  }

  # Validate the path.
  %ret = asmcmdshare_validate_path ($dbh, $full_path, $spprserr);

  # Nothing returned; then set -1. #
  $$ret_ref = -1 if (! defined ($ret{'ref_id'}) ||
                     (@{ $ret{'ref_id'} } == 0));

  return %ret;
}

########
# NAME
#   asmcmdshare_validate_path
#
# DESCRIPTION
#   This routine is a wrapper routine that calls 
#   asmcmdshare_validate_path_recurse().  It pre-processes the parameters 
#   before calling asmcmdshare_validate_path_recurse().
#
# PARAMETERS
#   dbh        (IN) - initialized database handle, must be non-null.
#   work_path  (IN) - path to be validated; must be absolute path.
#   spprserr   (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
#   A hash of references to four parallel arrays:
#     $ret{'path'}   - reference to array of validated paths.
#     $ret{'ref_id'} - reference to array of reference indices for the files
#                      or directories specified by the paths.
#     $ret{'par_id'} - reference to array of parent indices for the files or
#                      directories specified by the paths.
#     $ret{'gnum'}   - reference to array of group numbers for the files or 
#                      directories specified by the paths.
#
# NOTES
#   
########
sub asmcmdshare_validate_path 
{
  my ($dbh, $work_path, $spprserr) = @_;
  my (%ret);      # Hash of references to parallel arrays; see RETURN above. #
  my ($cont) = 0;                    # Whether to continue loop after        #
                                     # recursive call.                       #

  my ($wpath_upd) = '';              # Used to update $work_path in a lower  #
                                     # level from a higher level recursive   #
                                     # call when the call completes before   #
                                     # $work_path is fully parsed, i.e. 
                                     # $cont == 1.                           #
  my ($i);

  $work_path =~ s,^\+,,; # Remove '+' for asmcmdshare_validate_path_recurse. #
  $asmcmdglobal_hash{'nfnd'} = 0;    # Must reset not found flag at start.   #

  %ret = asmcmdshare_validate_path_recurse ($dbh, $work_path, \$cont, 
                                           \$wpath_upd, -1, -1, 0, $spprserr);

  # Now we put the '+' back for every path that's returned.
  if (defined ($ret{'path'}))
  {
    for ($i = 0; $i < @{ $ret{'path'} }; $i++) 
    {
      $ret{'path'}->[$i] = '+' . $ret{'path'}->[$i] 
        unless ($ret{'path'}->[$i] =~ m,^\+,);
    }
  asmcmdshare_trace(4, "NOTE: Completed validating the path $work_path",
                            'n', 'n');
  }

  return %ret;
}

########
# NAME
#   asmcmdshare_validate_path_recurse
#
# DESCRIPTION
#   This recursive routine parses every level of $work_path to check if it
#   is a valid entry in v$asm_alias.
#
# PARAMETERS
#   dbh            (IN)     - initialized database handle, must be non-null.
#   work_path      (IN)     - abolute path to be validated; no '+' prefix.
#   cont_ref       (IN/OUT) - internal state for recursive function: 1 means
#                             to continue while loop after recursive call
#                             returns; 0 means to terminate loop after its
#                             return.
#   wpath_upd_ref  (OUT)    - the unparsed portion of $work_path passed back
#                             to the calling function by the recursively 
#                             called function; this scenario can happen when
#                             in the scenario of having '*/..' in a path; 
#                             $cont_ref must be set to 1 in conjunction, and
#                             the calling function will finish parsing 
#                             $work_path.
#   cur_ref_id     (IN)     - the reference index of the level of directory
#                             that is currently processed
#   cur_par_id     (IN)     - the parent index of the level of directory
#                             that is currently processed
#   level          (IN)     - the level of recursive call; top level starts
#                             at 0; $level increments by 1 for each new level
#                             of recursive call.
#   spprserr       (IN)     - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
#   A hash of references to four parallel arrays:
#     $ret{'path'}   - reference to array of validated paths.
#     $ret{'ref_id'} - reference to array of reference indices for the files
#                      or directories specified by the paths.
#     $ret{'par_id'} - reference to array of parent indices for the files or
#                      directories specified by the paths.
#     $ret{'gnum'}   - reference to array of group numbers for the files or 
#                      directories specified by the paths.
#
# NOTES
#   This routine does not call itself recursively, but calls 
#   asmcmdshare_validate_dir() and asmcmdshare_validate_dg(), which call 
#   asmcmdshare_validate_path_recurse() recursively.  If a level of directory is
#   a diskgroup, we call asmcmdshare_validate_dg(); if a level of directory is 
#   an alias directory, we call asmcmdshare_validate_dir().
#
#   This routine handles all special cases that a path can have, including
#   the pseudo-directories '.' and '..', and the wildcard '*'.  An interesting
#   and special scenario may be '+dgroup/dir/*/../../dir', which should 
#   normalize to '+dgroup/dir' if '+dgroup/dir' exists and if at least one 
#   sub-directory exists under '+dgroup/dir'.  
########
sub asmcmdshare_validate_path_recurse 
{
  my ($dbh, $work_path, $cont_ref, $wpath_upd_ref, 
      $cur_ref_id, $cur_par_id, $level, $spprserr) = @_;

  my (@subdirs);  # Array of tokenized levels of directories, obtained from  #
                                      # $work_path by separating by the '/'. #
  my ($dir);                  # One level of directory, taken from @subdirs. #
  my (%ret);      # Hash of references to parallel arrays; see RETURN above. #
  my (%empty);  # Empty hash equivalent of %ret, used when validation fails. #
  my (@vld_path_a);    # Array of validated directory tokens from $vld_path. #
  my ($vld_path) = '';         # String for the subset of $work_path that's  #
                                                         # validated so far. #
  my ($wpath_cpy) = '+'. $work_path;      # Copy of $work_path that has '+'  #
                                                             # prefix added. #
  my ($wpath_slash);                      # Copy of $work_path that has '/'  #
                                                             # prefix added. #
  my ($vld_path_slash);           # Copy of $vld_path with '/' suffix added. #
  my (@eargs);                                   # Array of error arguments. #

  @subdirs = split (/\//, $work_path);         # Tokenize $work_path by '/'. #

  # We validate the path one directory level at a time, starting from the top
  # level directory.
  while (defined ($dir = shift(@subdirs))) 
  {
    $work_path = join ('/', @subdirs); # $work_path is not stripped of $dir. #

    next if ($dir eq '.');

    if ($dir eq '..') 
    {
      if (@vld_path_a > 0) 
      {
        pop (@vld_path_a);
        $vld_path = join ('/', @vld_path_a);

        # Call asmcmdshare_cdup_update_ids() to shift the indices up a level.
        ($cur_ref_id, $cur_par_id) = asmcmdshare_cdup_update_ids($dbh, 
                                                                 $cur_par_id);

        # Update indices here.  
        $ret{'path'}->[0]   = $vld_path;
        $ret{'ref_id'}->[0] = $cur_ref_id;
        $ret{'par_id'}->[0] = $cur_par_id;
        $ret{'gnum'}->[0]   = $asmcmdglobal_hash{'gnum'};

        next;
      }
      else 
      { # @vld_path_a <= 0

        # @vld_path_a has 0 elements here, so either we're in '+' or we'll
        # in a recursive call right after parsing a '*/..'.

        if ($level > 0) 
        { # If we're here that means this scenario:
          # cdup in case of +dgName/dir/*/../ . . .

          # Here is the special case where a recursive call can terminate prior
          # to $work_path being entirely parsed.  Thus, the calling function
          # needs to continue the loop after recursion returns.  Set OUT
          # values below so the caller knows to continue to parse the 
          # remainder of $work_path that's not finished here.
          $$cont_ref = 1;
          $$wpath_upd_ref = $work_path;
          
          return %empty;
        }

        # Already in '+'; cd '..' in '+' is still '+'.
        next;
      }
    }
    elsif ($dir =~ /$ASMCMDSHARE_N_CSET_W_WCARD/) 
    {
      # Bad charset, no match.

      if ($level == 0) 
      {
        # Top level, if no match, then none found.
        $asmcmdglobal_hash{'nfnd'} = 1;

        # Print error only if none found.
        if (@vld_path_a == 0) 
        {
          @eargs = ($dir);
          asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
        }
        else 
        {                                                 # @vld_path_a != 0 #
          @eargs = ($dir);
          asmcmdshare_error_msg(8004, \@eargs) unless ($spprserr);
        }
      }

      return %empty;                                            # Non-found. #
    }
    elsif ($dir =~ $ASMCMDGLOBAL_WCARD_CHARS) 
    {
      # It's the $ASMCMDSHARE_CSET_W_WCARD.

      if (asmcmdshare_is_wildcard_cmd($asmcmdglobal_hash{'cmd'})) 
      {
        # Wildcard supported; call recursively.

        if (($level == 0) && (@vld_path_a == 0)) 
        {
          # $dir must be diskgroup name.

          %ret = asmcmdshare_validate_dg ($dbh, $dir, $work_path, $cont_ref,
                                      $wpath_upd_ref, $level, 1, $spprserr);
        }
        else 
        {                                  # $level != 0 || @vld_path_a != 0 #

          # $dir is a normal directory within a diskgroup.

          %ret = asmcmdshare_validate_dir ($dbh, $dir, $work_path, $cont_ref,
                                      $wpath_upd_ref, $vld_path, $cur_ref_id, 
                                      $level, 1, $spprserr);
        }

        # Normally, the loop should terminate after a return from a recursive
        # call, as the recursive function should have finished parsing the
        # rest of $work_path.  However, the one exception is when the 
        # recursive call encounters a '..' after a '*', e.g. '*/..', or 
        # even '*/blah/../..', which simplifies to '*/..', assuming 'blah'
        # exists.  In this exception, the $cont reference is set to 1.
        if ($$cont_ref == 1) 
        {
          $$cont_ref = 0;                                           # Reset. #
          @subdirs = split (/\//, $$wpath_upd_ref); # Get updated work_path. #
          $ret{'path'}->[0]   = $vld_path;
          $ret{'ref_id'}->[0] = $cur_ref_id;
          $ret{'par_id'}->[0] = $cur_par_id;
          $ret{'gnum'}->[0]   = $asmcmdglobal_hash{'gnum'};

          # So do not stop loop, hence no return statement.
        }
        else 
        {                                                  # $$cont_ref != 1 #
          if ( ( @{ $ret{'path'} } == 0 ) && ($level == 0) ) 
          {
            # Since we're on top level, an empty path array means we've 
            # exhausted all possible matches for the wildcard and still no 
            # match.  Thus, print 'not found' message.

            $asmcmdglobal_hash{'nfnd'} = 1;
            $wpath_slash = '';
            $wpath_slash = '/' . $work_path if ($work_path ne '');
            $vld_path_slash = '';
            $vld_path_slash = $vld_path . '/' if ($vld_path ne '');

            if (($vld_path ne '') || ($work_path ne ''))
            {                          # Trying to match an alias; use 8002. #
              @eargs = ($dir . $wpath_slash, '+' . $vld_path_slash);
              asmcmdshare_error_msg(8002, \@eargs) unless ($spprserr);
            }
            else
            {                       # Trying to match a diskgroup; use 8001. #
              @eargs = ($dir . $wpath_slash);
              asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
            }
          }

          # Since the recursive function call has finished parsing $work_path, 
          # the caller can terminte its loop and return.
          return %ret;
        }
      }
      else 
      {    # asmcmdshare_is_wildcard_cmd($asmcmdglobal_hash{'cmd'}) == FALSE #

        # Wildcard not supported; so the fact we see a wildcard in $work_path
        # means that the path fails to validate for sure, as there is no
        # real alias name that contains a '*'.  Thus no match.

        if ($level == 0) 
        {
          # Top level, if no match, then validate fails.
          $asmcmdglobal_hash{'nfnd'} = 1;

          if (@vld_path_a == 0) 
          {                             # Invalid diskgroup name with a '*'. # 
            @eargs = ($dir);
            asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
          }
          else 
          {                                 # Invalid alias name with a '*'. #
            @eargs = ($dir);
            asmcmdshare_error_msg(8004, \@eargs) unless ($spprserr);
          }
        }

        return %empty;                     # No match, so return empty hash. #
      }
    }
    else 
    {                                                         # $dir !~ /\*/ #
      # Normal $ASMCMDSHARE_CSET; process as normal directory.

      if (($level == 0) && (@vld_path_a == 0)) 
      {
        # $dir must be diskgroup name.

        %ret = asmcmdshare_validate_dg ($dbh, $dir, undef, undef, undef,
                                       $level, 0, $spprserr);
      }
      else 
      {                                    # $level != 0 || @vld_path_a != 0 #

        # $dir is a normal directory within a diskgroup.

        %ret = asmcmdshare_validate_dir ($dbh, $dir, undef, undef, undef, 
                                     $vld_path, $cur_ref_id, $level, 0,
                                     $spprserr);
      }

      if ( (defined ($ret{'path'})) && ( @{ $ret{'path'} } == 1 ) )
      {                  
        # Found match.

        # Update indices.
        $cur_ref_id = $ret{'ref_id'}->[0];
        $cur_par_id = $ret{'par_id'}->[0];

        # Update validate path and its tokens array.
        @vld_path_a = split (/\//, $ret{'path'}->[0]);
        $vld_path = join ('/', @vld_path_a);
      }
      else 
      {
        # Dir not found, so stop loop.

        if ($level == 0) 
        {
          # Since we're on top level, an empty path array means we've
          # exhausted all possible matches for the wildcard and still no  
          # match.  Thus, print 'not found' message.

          $asmcmdglobal_hash{'nfnd'} = 1;

          if (@vld_path_a == 0) 
          {
            @eargs = ($dir);
            asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
          }
          else 
          {
            @eargs = ($dir, '+' . $vld_path . '/');
            asmcmdshare_error_msg(8002, \@eargs) unless ($spprserr);
          }
        }

        last;        # No need to finish parsing $work_path, since no match. #
      }
    }
  }                                                                # while() #

  if (($level == 0) && (! $asmcmdglobal_hash{'nfnd'})) 
  {
    if (@vld_path_a == 0) 
    {
      # This area should not be reached unless the path simplifies to
      # '+' after a series of '..', and no 'not found' occurred at $level
      # equal to 0.  If that's the case, then $work_path has normalized to 
      # simply '+'.  So update return hash to hold the values for '+'.
      $ret{'path'}->[0]   = '+';
      $ret{'ref_id'}->[0] = -1;
      $ret{'par_id'}->[0] = -1;
      $ret{'gnum'}->[0]   = -1;
    }
  }

  return %ret;
}

########
# NAME
#   asmcmdshare_validate_dir
#
# DESCRIPTION
#   This routine calls the appropriate functions to validate the existence 
#   of $dir in v$asm_alias.
#
# PARAMETERS
#   dbh            (IN)  - initialized database handle, must be non-null.
#   dir            (IN)  - the directory alias to be validated. 
#   work_path      (IN)  - remainder of the path to be validated in case $dir
#                          contains wildcard and need to call 
#                          asmcmdshare_validate_path_recurse() recursively.
#   cont_ref       (OUT) - internal state for recursive function: 1 means
#                          to continue while loop after recursive call
#                          returns; 0 means to terminate loop after its
#                          return.
#   wpath_upd_ref  (OUT) - the unparsed portion of $work_path passed back
#                          to the calling function by the recursively 
#                          called function; this scenario can happen when
#                          in the scenario of having '*/..' in a path; 
#                          $cont_ref must be set to 1 in conjunction, and
#                          the calling function will finish parsing 
#                          $work_path.
#   vld_path       (IN)  - String for the subset of $work_path that's validated
#                           so far. 
#   old_ref_id     (IN)  - the parent index of $dir.
#   level          (IN)  - the level of recursive call; top level starts
#                          at 0; $level increments by 1 for each new level
#                          of recursive call.
#   wildcard       (IN)  - boolean: true iff $dir contains wildcard '*'.
#   spprserr       (IN)  - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
#   A hash of references to four parallel arrays:
#     $ret{'path'}   - reference to array of validated paths.
#     $ret{'ref_id'} - reference to array of reference indices for the files
#                      or directories specified by the paths.
#     $ret{'par_id'} - reference to array of parent indices for the files or
#                      directories specified by the paths.
#     $ret{'gnum'}   - reference to array of group numbers for the files or 
#                      directories specified by the paths.
#
# NOTES
#   This routine calls asmcmdshare_get_subdir_simple() and get_subdir() to 
#   validate the existence of $dir.  If $dir contains a wildcard, then this 
#   routine calls asmcmdshare_validate_path_recurse() recursively on every
#   match of $dir returned by asmcmdshare_get_subdirs().
########
sub asmcmdshare_validate_dir 
{
  my ($dbh, $dir, $work_path, $cont_ref, $wpath_upd_ref, $vld_path, 
      $old_ref_id, $level, $wildcard, $spprserr) = @_;

  my (%results);              # Hash of refs to parallel arrays returned by  #
                                      # asmcmdshare_validate_path_recurse(). #
  my (%ret);      # Hash of references to parallel arrays; see RETURN above. #
  my (@vld_dirs);                                # Array of validated paths. # 
  my (@vld_ref_ids); # Array of reference indices for the file or directory  #
                                                   # specified by the paths. #
  my (@vld_par_ids);    # Array of parent indices for the file or directory  #
                                                   # specified by the paths. #
  my (@vld_gnums);       # Array of group numbers for the file or directory  #
                                                   # specified by the paths. #
  my (@subdirs); # Array of alias names that match wildcard expression $dir. #
  my ($new_ref_id);           # Reference index of $dir, $wildcard is FALSE. #
  my ($tmp);        # Temp string to hold a path from @{ $results{'path'} }. #
  my ($iter);
  my ($i);

  if (! $wildcard) 
  {               # $dir contains no wildcard; process it as a normal alias. #
    # Obtain reference index from parent index and alias name.
    $new_ref_id = asmcmdshare_get_subdir_simple($dbh, $old_ref_id, $dir);

    # If $dir exists, i.e. reference index is defined, then save them as
    # a validated entry.
    if (defined ($new_ref_id)) 
    {
      push (@vld_dirs, $vld_path . '/' . $dir);
      push (@vld_ref_ids, $new_ref_id);
      push (@vld_par_ids, $old_ref_id);
      push (@vld_gnums, $asmcmdglobal_hash{'gnum'});
    }
  }
  else 
  {                                               # $dir contains wildcards. #
    # Obtain all entries with alias names that match the wildcard expression
    # $dir.
    asmcmdshare_get_subdirs($dbh, \@subdirs, $asmcmdglobal_hash{'gnum'}, 
                            undef, $old_ref_id, $dir, undef, 0, 0);

    if (@subdirs > 0) 
    {                                     # If there is one or more matches. #
      foreach $iter (@subdirs)                         # Process each match. #
      {
        if ($work_path ne '')  # Call recursively only if more path left to  #
        {                                                        # validate. #

          # Call recursively to parse the remainder of $work_path; e.g. if
          # $dir matchs, 'a', 'b', and 'c'; and $work_path is 'foo', we 
          # we need to validate 'a/foo', 'b/foo', and 'c/foo'.
          %results = asmcmdshare_validate_path_recurse ($dbh, $work_path, 
                     $cont_ref, $wpath_upd_ref, $iter->{'reference_index'}, 
                     $iter->{'parent_index'}, $level + 1, $spprserr);

          # If it's a '*/..', then there's no need to loop through all
          # matches of '*' here.
          last if ($$cont_ref == 1);

          # Make sure that the hash has defined elements before attempting
          # to do any dereferencing.
          if (defined($results{'path'})) 
          {
            # Add all the results from %results to the @vld* arrays. 
            for ($i = 0; $i < @{ $results{'path'} }; $i++) 
            {
              $tmp = $results{'path'}->[$i];

              # We need to add each path from %results to $vld_path to get
              # full validated paths.  e.g. we have $vld_path as
              # '+dgroup/new', and $iter->{'name'} as 'a', and %results as
              # 'foo/bar', 'foo/candy', then we need to add the paths
              # '+dgroup/new/a/foo/candy' and
              # '+dgroup/new/a/foo/bar to @vld_dirs.

              # This if clause is present only to prevent the concatenation
              # of a possibly null string $tmp, which would result in a 
              # Perl warning if using perl -w.
              if ($tmp ne '') 
              {
                $tmp =~ s,^/,,;                 # Remove leading '/' if any. #
                $tmp = $vld_path . '/' . $iter->{'name'} . '/' . $tmp;
              }
              else 
              {
                $tmp = $vld_path . '/' . $iter->{'name'};
              }

              # Now save each entry. 
              push (@vld_dirs, $tmp);
              push (@vld_ref_ids, $results{'ref_id'}->[$i]);
              push (@vld_par_ids, $results{'par_id'}->[$i]);
              push (@vld_gnums, $results{'gnum'}->[$i]);
            }
          }
        }
        else 
        {                                                  # $workpath eq '' #
          # No need to call asmcmdshare_validate_path_recurse(); simply append 
          # each match of $dir to $vld_path and add the resulting string to 
          # @vld_dirs.
          $tmp = $vld_path . '/' . $iter->{'name'};

          push (@vld_dirs, $tmp);
          push (@vld_ref_ids, $iter->{'reference_index'});
          push (@vld_par_ids, $iter->{'parent_index'});
          push (@vld_gnums, $asmcmdglobal_hash{'gnum'});
        }
      }
    }
  }

  # Prepare return hash.
  $ret{'path'} = \@vld_dirs;
  $ret{'ref_id'} = \@vld_ref_ids;
  $ret{'par_id'} = \@vld_par_ids;
  $ret{'gnum'} = \@vld_gnums;

  return %ret;
}

########
# NAME
#   asmcmdshare_validate_dg
#
# DESCRIPTION
#   This routine calls the appropriate functions to validate the existence 
#   of $gname in v$asm_diskgroup.
#
# PARAMETERS
#   dbh            (IN)  - initialized database handle, must be non-null.
#   work_path      (IN)  - remainder of the path to be validated in case $gname
#                          contains wildcard and need to call 
#                          asmcmdshare_validate_path_recurse() recursively.
#   cont_ref       (OUT) - internal state for recursive function: 1 means
#                          to continue while loop after recursive call
#                          returns; 0 means to terminate loop after its
#                          return.
#   wpath_upd_ref  (OUT) - the unparsed portion of $work_path passed back
#                          to the calling function by the recursively 
#                          called function; this scenario can happen when
#                          in the scenario of having '*/..' in a path; 
#                          $cont_ref must be set to 1 in conjunction, and
#                          the calling function will finish parsing 
#                          $work_path.
#   level          (IN)  - the level of recursive call; top level starts
#                          at 0; $level increments by 1 for each new level
#                          of recursive call.
#   wildcard       (IN)  - boolean: true iff $gname contains wildcard '*'.
#   spprserr       (IN)  - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
#   A hash of references to four parallel arrays:
#     $ret{'path'}   - reference to array of validated paths.
#     $ret{'ref_id'} - reference to array of reference indices for the file
#                      or directory specified by the paths.
#     $ret{'par_id'} - reference to array of parent indices for the file or
#                      directory specified by the paths.
#     $ret{'gnum'}   - reference to array of group numbers for the file or 
#                      directory specified by the paths.
#
# NOTES
#   If $gname contains a wildcard, then this routine calls 
#   asmcmdshare_validate_path_recurse() recursively on every match of $gname 
#   returned by asmcmdshare_get_dg().
########
sub asmcmdshare_validate_dg 
{
  my ($dbh, $gname, $work_path, $cont_ref, $wpath_upd_ref, 
      $level, $wildcard, $spprserr) = @_;

  my (@dg_list); # Array of diskgroup hashes returned by asmcmdshare_get_dg. #
  my (%results);              # Hash of refs to parallel arrays returned by  #
                                      # asmcmdshare_validate_path_recurse(). # 
  my (%ret);      # Hash of references to parallel arrays; see RETURN above. #
  my (@vld_dirs);                                # Array of validated paths. #
  my (@vld_ref_ids);             # Array of reference indices for the paths. #
  my (@vld_par_ids);                # Array of parent indices for the paths. #
  my (@vld_gnums);                   # Array of group numbers for the paths. #
  my ($gnum);          # The group number for $gname, if $wildcard is FALSE. #
  my ($tmp);        # Temp string to hold a path from @{ $results{'path'} }. #
  my ($iter);
  my ($i);

  if (! $wildcard) 
  {                                        # No wildcard, process normally.  #
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);
    
    # If group number exists for the group name, then the group name must be
    # valid.
    if (defined ($gnum)) 
    {
      # Save validated diskgroup information.
      push (@vld_dirs, $gname);
      push (@vld_ref_ids, $gnum << 24);
      push (@vld_par_ids, -1);
      push (@vld_gnums, $gnum);

      # Update globals for default diskgroup name and number.
      $asmcmdglobal_hash{'gname'} = $gname;
      $asmcmdglobal_hash{'gnum'} = $gnum;
    }
  }
  else 
  {                                            # Wildcard, call recursively. #
    @dg_list = asmcmdshare_get_dg($dbh, $gname, 0, 0);
                                                  # Get matching diskgroups. #
                                           # the wildcard expression $gname. #

    if (@dg_list > 0) 
    {                                               # At least one dg found. #
      foreach $iter (@dg_list)                # Process one match at a time. #
      {
        # Update global current diskgroup information.
        $asmcmdglobal_hash{'gname'} = $iter->{'name'};
        $asmcmdglobal_hash{'gnum'} = $iter->{'group_number'};

        if ($work_path ne '')  # Call recursively only if more path left to  #
        {                                                        # validate. #
          # Call recursively to parse the remainder of $work_path.
          %results = asmcmdshare_validate_path_recurse ($dbh, $work_path, 
                     $cont_ref, $wpath_upd_ref, $iter->{'group_number'} << 24, 
                     -1, $level + 1, $spprserr);

          # If it's a '*/..', then there's no need to loop through all
          # matches of '*' here.
          last if ($$cont_ref == 1);

          # Make sure that the hash has defined elements before attempting
          # to do any dereferencing.
          if (defined($results{'path'})) 
          {
            # Add all the results from %results to the @vld* arrays. 
            for ($i = 0; $i < @{ $results{'path'} }; $i++) 
            {
              # Build fully validated paths.
              $tmp = $results{'path'}->[$i];
              $tmp =~ s,^/,,;                   # Remove leading '/' if any. #
              $tmp = $iter->{'name'} . '/' . $tmp;

              # Now save each entry.
              push (@vld_dirs, $tmp);
              push (@vld_ref_ids, $results{'ref_id'}->[$i]);
              push (@vld_par_ids, $results{'par_id'}->[$i]);
              push (@vld_gnums, $results{'gnum'}->[$i]);
            }
          }
        }
        else                                              # $work_path eq '' #
        {
          # No need to call asmcmdshare_validate_path_recurse(); simply add 
          # each matching group name to @vld_dirs.
          push (@vld_dirs, $iter->{'name'});
          push (@vld_ref_ids, $iter->{'group_number'} << 24);
          push (@vld_par_ids, -1);
          push (@vld_gnums, $iter->{'group_number'});    
        }
      }
    }
  }

  # Prepare return hash.
  $ret{'path'} = \@vld_dirs;
  $ret{'ref_id'} = \@vld_ref_ids;
  $ret{'par_id'} = \@vld_par_ids;
  $ret{'gnum'} = \@vld_gnums;

  return %ret;
}

########
# NAME
#   asmcmdshare_make_absolute
#
# DESCRIPTION
#   This routine checks to see if $path is an absolute path; if not, it 
#   attaches the current directory to the front of $path and returns the
#   full path.
#
# PARAMETERS
#   path   (IN) - user-entered relative or absoluate path.
#
# RETURNS
#   $path if it is absolute; $asmcmdglobal_hash{'cwdnm'} . '/'. $path 
#   otherwise.
########
sub asmcmdshare_make_absolute 
{
  my ($path) = shift;
  my ($full_path) = $path;
 
  if ($path !~ m,^\+,)            # An absolute path always starts with '+'. #
  {                # If not, attach current directory to the front of $path. #
    $full_path = $asmcmdglobal_hash{'cwdnm'} . '/'. $path;
     asmcmdshare_trace(5, "asmcmdshare_make_absolute(): Converted to absolute"
                     ." path $path", 'n', 'n');
  }

  return $full_path;
}

########
# NAME
#   asmcmdshare_cdup_update_ids
#
# DESCRIPTION
#   Given a parent index of an alias, this function returns the reference and
#   parent indices that belong the parent of that alias.
#
# PARAMETERS
#   dbh     (IN) - initialized database handle, must be non-null.
#   par_id  (IN) - the parent index of the child alias.
#
# RETURNS
#   Two-element array: 
#     Element 1: reference index of parent alias.
#     Element 2: parent index of parent alias.
########
sub asmcmdshare_cdup_update_ids 
{
  my ($dbh, $par_id) = @_;
  my ($ref_id) = $par_id;                    # Ref id always becomes par id. #

  if ($par_id == -1) 
  {                                            # cdup from '+' or '+dgName'. #
    return ($ref_id, $par_id);
  }
  elsif ($par_id == ($asmcmdglobal_hash{'gnum'} << 24)) 
  {                                              # cdup from '+dgName/dir1'. #
    return ($ref_id, -1);
  }
  else 
  {                            # All other cases, i.e. 2+ levels of subdirs. #
    $par_id = asmcmdshare_get_par_id($dbh, $par_id);
    return ($ref_id, $par_id);
  }
}
##############################################################################





############################## MISC Routines #################################
########
# NAME
#   asmcmdshare_ls_calc_min_col_wid
#
# DESCRIPTION
#   This formatting routine calculates the miminum column width of each 
#   displayed attribute based on the values from v$asm_alias and v$asm_file.
#
# PARAMETERS
#   list_ref        (IN)     - reference to list of hashes, each hash holding 
#                              values for attributes of one alias entry.
#   min_col_wid_ref (IN/OUT) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   The hashes in @{$list_ref} must be populated with values from v$asm_alias
#   and v$asm_file if the entry is a file.  Thus, asmcmdshare_get_subdirs()
#   must be called first.  Important: these hashes contains keys that are not 
#   attribute names.  These extra keys are internal to ASMCMD and do not 
#   correspond to a column name in v$asm_alias or v$asm_file.  One example 
#   is 'name_print', which is used to store the value to be printed under 
#   the name column, which can be the same as the value for the 'name' key, 
#   but it doesn't have to be.
#
#   %{$min_col_wid_ref} must be initialized with the default minimum column 
#   widths before calling this routine.  Thus, asmcmdbase_ls_init_col_wid() or 
#   asmcmdbase_lsdg_init_col_wid() must be called first.  All keys in this 
#   hash are attribute names from v$asm_alias and/or v$asm_file.
#
#   Important note: the hashes in @{$list_ref} can contain keys that are not
#   in %{$min_col_wid_ref} and vice versa.  Do not assume that if a key exists
#   in one then it must in the other.  Always use defined() to check, or else
#   perl -w will complain.
########
sub asmcmdshare_ls_calc_min_col_wid 
{
  my ($list_ref, $min_col_wid_ref) = @_;

  my ($entry);   # Reference to one hash entry of attribute values in @list. #
  my ($key);                # One hash key of %{$entry}, used for iteration. #

  # For the values of each attribute in the list of entries, find the one with 
  # the longest length.  That length is the minimum column width for that
  # attribute. 
  foreach $entry (@{$list_ref}) 
  {
    foreach $key (keys %{$entry}) 
    {
      if (defined ($min_col_wid_ref->{ $key }) && 
          defined ($entry->{ $key })           &&
          (length($entry->{ $key }) > $min_col_wid_ref->{ $key })) 
      {
        # Save the longest length so far.
        $min_col_wid_ref->{ $key } = length($entry->{ $key });
      }
    }
  }

  return;
}

########
# NAME
#   asmcmdshare_version_cmp
#
# DESCRIPTION
#   This function determines if the Oracle Database version $version1 is
#   less than, equal to, or greater than Oracle Database version $version2.
#
# PARAMETERS
#   version1        (IN)     - Oracle Database version number one
#   version2        (IN)     - Oracle Database version number two
#
# RETURNS
#   A negative number if $version1 is smaller or earlier than $version2;
#   zero of $version1 equals $version2; and a positive number if $version1
#   is greater or later than $version2.
#
# NOTES
#   This function asserts that the version numbers are in the format
#   a.b.c.d.e .
#
#   We assume this order of significance for the five sub-version numbers:
#   a is most significant, then b, then c, and then d. e is least
#   significant.
########
sub asmcmdshare_version_cmp
{
  my ($version1, $version2) = @_;

  # Major database release number, database maintenance release number,
  # application server release number, component-specific release number,
  # platform-specific release number.
  my ($v1_maj, $v1_maint, $v1_appserv, $v1_comp, $v1_plat);
  my ($v2_maj, $v2_maint, $v2_appserv, $v2_comp, $v2_plat);

  # Error array.
  my (@eargs);

  # Split $version1 into the five sub-version parts.
  ($v1_maj, $v1_maint, $v1_appserv, $v1_comp, $v1_plat) 
    = split (/\./, $version1, 5);

  # Split $version2 into the five sub-version parts.
  ($v2_maj, $v2_maint, $v2_appserv, $v2_comp, $v2_plat)
    = split (/\./, $version2, 5);

  # Assert that we have all five sub-version parts for both versions.
  @eargs = ('asmcmdshare_version_cmp05');
  asmcmdshare_assert(defined($v1_maj), \@eargs);
  @eargs = ('asmcmdshare_version_cmp07');
  asmcmdshare_assert(defined($v1_maint), \@eargs);
  @eargs = ('asmcmdshare_version_cmp09');
  asmcmdshare_assert(defined($v1_appserv), \@eargs);
  @eargs = ('asmcmdshare_version_cmp11');
  asmcmdshare_assert(defined($v1_comp), \@eargs);
  @eargs = ('asmcmdshare_version_cmp13');
  asmcmdshare_assert(defined($v1_plat), \@eargs);
  @eargs = ('asmcmdshare_version_cmp15');
  asmcmdshare_assert(defined($v2_maj), \@eargs);
  @eargs = ('asmcmdshare_version_cmp17');
  asmcmdshare_assert(defined($v2_maint), \@eargs);
  @eargs = ('asmcmdshare_version_cmp19');
  asmcmdshare_assert(defined($v2_appserv), \@eargs);
  @eargs = ('asmcmdshare_version_cmp21');
  asmcmdshare_assert(defined($v2_comp), \@eargs);
  @eargs = ('asmcmdshare_version_cmp23');
  asmcmdshare_assert(defined($v2_plat), \@eargs);

  # Compare major release number.
  if ($v1_maj != $v2_maj)
  {
    return ($v1_maj - $v2_maj);
  }

  # Compare maintenance release number.
  if ($v1_maint != $v2_maint)
  {
    return ($v1_maint - $v2_maint);
  }

  # Compare application server release number.
  if ($v1_appserv != $v2_appserv)
  {
    return ($v1_appserv - $v2_appserv);
  }

  # Compare component release number.
  if ($v1_comp != $v2_comp)
  {
    return ($v1_comp - $v2_comp);
  }

  # Compare platform release number.
  return ($v1_plat - $v2_plat);
}

########
# NAME
#   asmcmdshare_get_asm_version
#
# DESCRIPTION
#   This function retrieves the ASM instance software version number from
#   the ASM instance.
#
# PARAMETERS
#   dbh         (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   The ASM instance sofware version number.
########
sub asmcmdshare_get_asm_version
{
  my ($dbh) = shift;                                      # Database handle. #
  my ($sth);                                         # SQL statement handle. #
  my ($version);                                     # ASM instance version. #
  my ($query);                                        # SQL query statement. #
  my ($row);                                         # Row of query results. #

  # SQL for getting the ASM instance software version.
  $query = 'select version from v$instance';

  # Execute the SQL.
  $sth = asmcmdshare_do_select($dbh, $query);

  # Fetch the only row of results.
  $row = asmcmdshare_fetch($sth);

  # Get the version number.
  $version = $row->{'VERSION'};

  # Close the statement handle.
  asmcmdshare_finish($sth);

  return $version;
}

########
# NAME
#   asmcmdshare_trace
#
# DESCRIPTION
#   This function prints the messages either in alert logs or trace levels 
#   depending on the trace level associated with the message. clsecho is a
#   tool which is used to write the messages in files. The default level of 
#   tracing is set to 'normal'.
#
# USAGE
# asmcmd -v [ errors| warnings| normal| info| debug ]
#
# PARAMETERS
#   msg_level (IN) - The trace level associated with the message.
#   msg       (IN) - The message to be printed into files.
#   timestamp (IN) - The timestamp is printed along with msg if set to 'y'
#                    The timestamp is not printed along with msg if set to 'n'
#   console   (IN) - If set to 'y' the message is printed to console as well.
#   
# RETURNS
#   Null.
#
# NOTES
# There are 5 levels of tracing.
# Level 1 - ERRORS - Tracing errors in execution path.
# Level 2 - WARNINGS - Tracing warnings in execution path.
# Level 3 - NORMAL - Tracing normal messages such as success statements and
#                    sql queries.
# Level 4 - INFO - Tracing decision statements and loops.
# Level 5 - DEBUG - Tracing for debugging purposes.
#
# The messages in logfile are differentiated using NOTE, SUCCESS, ERROR and 
# WARNING. For error messages and sql statements, timestamps are also recorded
# in the logfiles along with the message. For each message printed in the trace 
# file i.e for levels INFO and DEBUG, the function name precedes the message.
########
sub asmcmdshare_trace
{
  my($msg_level, $msg, $timestamp, $console ) = @_;
  my($clsecho_write);
  my($buf, $set_level);
  
  $set_level = $asmcmdshare_trace_levels{$asmcmdglobal_hash{'verbose'}}; 
  if ($timestamp eq 'y')
  {
    $timestamp = '-t';
  }
  else
  {
    $timestamp = ' ';
  }
  
  if ($set_level >= $msg_level)
  {
    if ($console eq 'y')
    {
      print STDERR "$msg\n";
    }
    $msg=~ s/\"/\"'\"'\"/g;
    if($set_level <= 3)
    {
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/alert/alert.log \"$msg\"";
    }
    else
    {
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/trace/trace.trc \"$msg\"";
    }
    if ($msg =~ /\^/)
    {
      $clsecho_write =~ /([^\r^\t]+)\^([^\r^\t]+)/; 
      $clsecho_write="$1\^$2";
    }
    else
    {
      $clsecho_write =~ /([^\r^\t]+)/;
      $clsecho_write = $1;
    }
    eval
    {
      $buf=`$clsecho_write`;
    };
    if ($@ ne '')
    {
      print "$@";
      asmcmdshare_error_msg(8020, undef);
    } 
  }    
}

##############################################################################





############################## SQL Routines ##################################
########
# NAME
#   asmcmdshare_cur_julian_date
#
# DESCRIPTION
#   This routine constructs the SQL used to retrieve the current Julian Date.
#   It calls asmcmdshare_do_select() to execute it.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   The current Julian Date as a number in string format.
########
sub asmcmdshare_cur_julian_date 
{
  my ($dbh) = shift;
  my ($sth, $qry, $row);
  my ($jul_date);   # Return string for the current Julian Date: an integer. #

  $qry = 'select to_char(current_date, \'J\') "JULIAN_DATE" from dual';

  $sth = asmcmdshare_do_select($dbh, $qry);
  $row = asmcmdshare_fetch($sth);
  $jul_date = $row->{'JULIAN_DATE'};
  asmcmdshare_finish($sth);

  return $jul_date;
}

########
# NAME
#   asmcmdshare_get_insttype
#
# DESCRIPTION
#   This routine constructs the SQL used to get the instance type of the
#   connected Oracle instance.  It calls asmcmdshare_do_select() to execute it.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   The instance type as a string: either 'ASM' or 'RDBMS'.
########
sub asmcmdshare_get_insttype 
{
  my ($dbh) = shift;
  my ($sth, $qry, $row);
  my ($insttype);              # Return string that holds the instance type. #

  $qry = 'select value from v$parameter where name=\'instance_type\'';

  $sth = asmcmdshare_do_select($dbh, $qry);
  $row = asmcmdshare_fetch($sth);
  $insttype = $row->{'VALUE'};
  asmcmdshare_finish($sth);

  return $insttype;
}

########
# NAME
#   asmcmdshare_get_file
#
# DESCRIPTION
#   This routine constructs the SQL used to retrieve all the attributes
#   in v$asm_file for a file, given its group number and file number.  It 
#   calls asmcmdshare_do_select() to execute it.
#
# PARAMETERS
#   dbh            (IN)     - initialized database handle, must be non-null.
#   gnum           (IN)     - group number for the file in question.
#   fnum           (IN)     - file number for the file in question.
#   file_info_ref  (IN/OUT) - reference to hash of file informationt to be
#                             returned.
#
# RETURNS
#   A hash of all the attribute values, hashed by attribute names.
#
# NOTES
#   Not all hash keys in %file_info are attributes from v$asm_file.  The keys
#   MOD_TIME, MOD_DATE, JULIAN_DATE, and JULIAN_TIME are differen time 
#   formats of the attribute 'modification_date'.
########
sub asmcmdshare_get_file 
{
  my ($dbh, $gnum, $fnum, $file_info_ref) = @_;
  my ($sth, $qry, $row, $cmpd_ind);

  $cmpd_ind = $gnum * (1 << 24) + $fnum;
 
  $qry = 'select group_number, file_number, incarnation, block_size, ' .
         'blocks, bytes, space, type, redundancy, striped, creation_date, ' .
         'user_number, usergroup_number, permissions, ' .
         'to_char(modification_date, \'MON DD HH24:MI:SS\') "MOD_TIME", ' .
         'to_char(modification_date, \'MON DD YYYY\') "MOD_DATE", ' .
         'to_char(modification_date, \'J HH24 MI SS\') "JULIAN_TIME", ' .
         'to_char(modification_date, \'J\') "JULIAN_DATE" ' .
         'from v$asm_file where compound_index = ' . $cmpd_ind . ' ORDER BY ' .
         'GROUP_NUMBER, FILE_NUMBER, USER_NUMBER, USERGROUP_NUMBER '
         ;

  $sth = asmcmdshare_do_select($dbh, $qry);
  $row = asmcmdshare_fetch($sth);

  # Copy values into hash.
  $file_info_ref->{'group_number'} = $row->{'GROUP_NUMBER'};
  $file_info_ref->{'file_number'} = $row->{'FILE_NUMBER'};
  $file_info_ref->{'incarnation'} = $row->{'INCARNATION'};
  $file_info_ref->{'block_size'} = $row->{'BLOCK_SIZE'};
  $file_info_ref->{'blocks'} = $row->{'BLOCKS'};
  $file_info_ref->{'bytes'} = $row->{'BYTES'};
  $file_info_ref->{'space'} = $row->{'SPACE'};
  $file_info_ref->{'type'} = $row->{'TYPE'};
  $file_info_ref->{'redundancy'} = $row->{'REDUNDANCY'};
  $file_info_ref->{'striped'} = $row->{'STRIPED'};
  $file_info_ref->{'creation_date'} = $row->{'CREATION_DATE'};
  $file_info_ref->{'mod_time'} = $row->{'MOD_TIME'};
  $file_info_ref->{'mod_date'} = $row->{'MOD_DATE'};
  $file_info_ref->{'julian_time'} = $row->{'JULIAN_TIME'};
  $file_info_ref->{'julian_date'} = $row->{'JULIAN_DATE'};
  $file_info_ref->{'user_number'} = $row->{'USER_NUMBER'};
  $file_info_ref->{'usergroup_number'} = $row->{'USERGROUP_NUMBER'};
  if (defined($row->{'USER_NUMBER'}))
  {
    $file_info_ref->{'user'} = asmcmdshare_usernumber2name($dbh, 
                                         $row->{'USER_NUMBER'},
                                         $row->{'GROUP_NUMBER'});
  } 
  else
  {
    $file_info_ref->{'user'} = '';
  }

  if (defined($row->{'USERGROUP_NUMBER'}) && defined($row->{'GROUP_NUMBER'}))
  {
    $file_info_ref->{'group'} = asmcmdshare_usergroupnumber2name($dbh, 
                                         $row->{'USERGROUP_NUMBER'},
                                         $row->{'GROUP_NUMBER'});
  }
  else
  {
    $file_info_ref->{'group'} = '';
  }
 
  if (defined($row->{'PERMISSIONS'}))
  {
    $file_info_ref->{'perm'} = $row->{'PERMISSIONS'};
  }
  else
  {
    $file_info_ref->{'perm'} = '';
  }
  
  # Combine date and time into one sortable number by removing the spaces.
  if (defined($row->{'JULIAN_TIME'}))
  {
    $file_info_ref->{'julian_time'} =~ s,\s+,,g;
  }
  else
  {
    $file_info_ref->{'julian_time'} = '';
  }

  asmcmdshare_finish($sth);

  return;
}

########
# NAME
#   asmcmdshare_get_subdirs
#
# DESCRIPTION
#   This routine constructs the SQL used to select one or more rows from 
#   v$asm_alias.  
#
# PARAMETERS
#   dbh           (IN)     - initialized database handle, must be non-null.
#   list_ref      (IN/OUT) - reference to array of hashes, each representing
#                            a single row of results.
#   gnum          (IN)     - optional: limit select by this group number.
#   ref_id        (IN)     - optional: limit select by this reference index. 
#   par_id        (IN)     - optional: limit select by this parent index.
#   name          (IN)     - optional: limit select by this alias name.
#   type          (IN)     - optional: limit select by this file type.
#   sys_non_dir_only  (IN) - boolean: limit select by 1) only system-created
#                            aliases and 2) only non-directories, iff this 
#                            boolean is true.
#   get_file_info (IN)     - boolean: query v$asm_file iff true.
#
# RETURNS
#   Undefined if the following requirement in NOTES is not met; otherwise
#   an array of hashes, each containing the values of attributes for one
#   row of v$asm_alias, returned by the query.
#
# NOTES
#   You must specify at least one of $gnum or $name.  Note also that we do 
#   not select every element in v$asm_alias. 
########
sub asmcmdshare_get_subdirs 
{
  my ($dbh, $list_ref, $gnum, $ref_id, $par_id, $name, $type,
      $sys_non_dir_only, $get_file_info) = @_;

  my ($sth, $qry_alias, $row, $hash_key);
  my (%tmphash);

  # Substitute all '*' with '%', as the latter is the wildcard character in
  # SQL.
  $name =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g if (defined ($name));

  # Construct query against v$asm_alias.
  $qry_alias = 'select name,
                       group_number,
                       file_number,
                       reference_index,
                       parent_index, 
                       alias_directory, 
                       system_created
                  from v$asm_alias';

  # Add conditions to the SQL statement, based on which parameter is present.
  # Note that either group number or the name is required.
  if (defined ($gnum)) 
  {
    $qry_alias .= ' where group_number=' . $gnum;
    $qry_alias .= ' and upper(name) like \'' . uc($name) . '\'' 
              if (defined ($name));
  }
  elsif (defined ($name)) 
  {
    $qry_alias .= ' where upper(name) like \'' . uc($name) . '\'';
    $qry_alias .= ' group_number=' . $gnum if (defined ($gnum));
  }
  else 
  {
    return undef;               # Neither $gnum nor $name is present; error. #
  }

  $qry_alias .= ' and reference_index=' . $ref_id if (defined ($ref_id));
  $qry_alias .= ' and parent_index=' . $par_id if (defined ($par_id));

  if ($sys_non_dir_only) 
  {                # We want only system-created aliases to only files here. #
    $qry_alias .= " and alias_directory='N' and system_created='Y'";
  }

  $sth = asmcmdshare_do_select($dbh, $qry_alias);

  # Fetch results row by row and store each row in %entry_info, and reference
  # each %entry_info in @{$list_ref}.
  while (defined ($row = asmcmdshare_fetch($sth))) 
  {
    my (%entry_info);                    # Allocate fresh hash for next row. #

    # v$asm_alias entries
    $entry_info{'group_number'} = $row->{'GROUP_NUMBER'};
    $entry_info{'file_number'} = $row->{'FILE_NUMBER'};
    $entry_info{'reference_index'} = $row->{'REFERENCE_INDEX'};
    $entry_info{'parent_index'} = $row->{'PARENT_INDEX'};
    $entry_info{'alias_directory'} = $row->{'ALIAS_DIRECTORY'};
    $entry_info{'system_created'} = $row->{'SYSTEM_CREATED'};
    $entry_info{'name'} = $row->{'NAME'};

    # Append to v$asm_file query
    if ($get_file_info && $entry_info{'alias_directory'} eq 'N')
    {
      # Hash key consists of group_number, file_number, and 
      # the system_created flag.
      $hash_key = $entry_info{'group_number'} * (1 << 24) +
                  $entry_info{'file_number'} . 
                  $entry_info{'system_created'};
      $tmphash{$hash_key} = \%entry_info;
    }

    push (@{$list_ref}, \%entry_info);
  }
  asmcmdshare_finish($sth);

  # Here we query v$asm_file once for each file.  This method may seem
  # excessive, but it is actually the fastest way.  There are two 
  # alternatives:
  # A) Build a single query to query all files.  
  # B) Join v$asm_file against a filtered v$asm_alias after a subquery.
  #
  # A) is not good because such a query can be too long and may require
  # a lot of memory on the server side.
  # B) is not good because it runs in O(N) time if querying a single 
  # file entry, versus O(1) time as implemented here.
  foreach (keys (%tmphash)) 
  {
    asmcmdshare_get_file($dbh, $tmphash{$_}->{'group_number'},
                        $tmphash{$_}->{'file_number'},
                        $tmphash{$_});
  }

###############################

  return;
}

########
# NAME
#   asmcmdshare_get_subdir_simple
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the reference index for the
#   alias that has the parent index $par_id, the name $name, and the group
#   number $asmcmdglobal_hash{'gnum'}.
#
# PARAMETERS
#   dbh     (IN) - initialized database handle, must be non-null.
#   par_id  (IN) - limit select by this parent index.
#   name    (IN) - limit select by this alias name.
#
# RETURNS
#   The reference index as a string.
########
sub asmcmdshare_get_subdir_simple 
{
  my ($dbh, $par_id, $name) = @_;

  my ($sth, $qry, $row);
  my ($ref_id);                 # The return string for the reference index. #

  $qry = 'select reference_index from v$asm_alias where group_number=' . 
    $asmcmdglobal_hash{'gnum'} . ' and parent_index=' . $par_id .
      ' and upper(name)=\'' . uc($name) . '\'';

  $sth = asmcmdshare_do_select($dbh, $qry);
  $row = asmcmdshare_fetch($sth);
  $ref_id = $row->{'REFERENCE_INDEX'};
  asmcmdshare_finish($sth);

  return $ref_id;
}

########
# NAME
#   asmcmdshare_run_find_sql
#
# DESCRIPTION
#   This routine constructs and executes SQL to find all the aliases 
#   in v$asm_alias that match the pattern given by $name.  $par_id
#   and $type can further limit the results.
#
# PARAMETERS
#   dbh           (IN)     - initialized database handle, must be non-null.
#   list_ref      (IN/OUT) - reference to array of hashes, each representing
#                            a single row of results.
#   par_id        (IN)     - optional: limit select by this parent index.
#   name          (IN)     - limit select by this alias name.
#   type          (IN)     - optional: limit select by this file type.
#   sys_non_dir_only  (IN) - boolean: limit select by 1) only system-created
#                            aliases and 2) only non-directories, iff this 
#                            boolean is true.
#
# NOTES
#   asmcmdshare_find_int() uses this SQL routine, which optimizes for 
#   searches, while asmcmdshare_get_subdirs() optimizes for ls.
########
sub asmcmdshare_run_find_sql
{
  my ($dbh, $list_ref, $par_id, $name, $type, $sys_non_dir_only) = @_;
  my ($sth, $qry, $qry2, $row, $cmpd_ind, $entry);
  my (@tmparray);

  # Set wildcard as '%' and contruct v$asm_alias query.
  $name =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g if (defined ($name));
  $qry = 'select name,
                 group_number,
                 file_number,
                 reference_index, 
                 parent_index,
                 alias_directory,
                 system_created 
            from v$asm_alias 
           where upper(name) like \'' . uc($name) . '\'';

  if (defined ($type))
  {
    $qry .= ' and alias_directory=\'N\'';
  }

  if (defined ($par_id))
  {
    $qry .= ' and parent_index=' . $par_id;
  }

  if ($sys_non_dir_only)
  {                # We want only system-created aliases to only files here. #
    $qry .= " and alias_directory='N' and system_created='Y'";
  }

  $sth = asmcmdshare_do_select($dbh, $qry);
  while (defined ($row = asmcmdshare_fetch($sth))) 
  {
    my (%entry_info);                    # Allocate fresh hash for next row. #

    # v$asm_alias entries
    $entry_info{'group_number'} = $row->{'GROUP_NUMBER'};
    $entry_info{'file_number'} = $row->{'FILE_NUMBER'};
    $entry_info{'reference_index'} = $row->{'REFERENCE_INDEX'};
    $entry_info{'parent_index'} = $row->{'PARENT_INDEX'};
    $entry_info{'alias_directory'} = $row->{'ALIAS_DIRECTORY'};
    $entry_info{'system_created'} = $row->{'SYSTEM_CREATED'};
    $entry_info{'name'} = $row->{'NAME'};

    push (@tmparray, \%entry_info);
  }
  asmcmdshare_finish($sth);

  foreach $entry (@tmparray)
  {
    # If we're qualifying by file type, then we need to query v$asm_file.
    if (defined ($type))
    {
      $cmpd_ind = $entry->{'group_number'} * (1 << 24) +
                  $entry->{'file_number'};

      $qry2 = 'select compound_index from v$asm_file where
                      compound_index=? and
                      upper(type)=\'' . uc($type) . '\'';

      $sth = asmcmdshare_do_prepare($dbh, $qry2);
      $sth->bind_param(1,$cmpd_ind);
      asmcmdshare_do_execute($sth);
      $row = asmcmdshare_fetch($sth);

      # Add entry to results only if there is a match.
      if (defined ($row))
      {
        push (@{$list_ref}, $entry);
      }
      asmcmdshare_finish($sth);
    }
    else      # If we're not searching by type, then always return the entry #
    {
      push (@{$list_ref}, $entry);
    }
  }
}

########
# NAME
#   asmcmdshare_get_par_id
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the parent index of the
#   alias that has the group number $asmcmdglobal_hash{'gnum'} and 
#   reference_index $ref_id.
#
# PARAMETERS
#   dbh     (IN) - initialized database handle, must be non-null.
#   ref_id  (IN) - limit select by this reference index.
#
# RETURNS
#   The parent index as a string.
#
# NOTES
#   The reference index specified by $ref_id must be unique; it must not be
#   the reference index of a file alias, as those are not unique.
########
sub asmcmdshare_get_par_id 
{
  my ($dbh, $ref_id) = @_;

  my ($sth, $qry, $row);
  my ($par_id);                # The return string holding the parent index. #

  $qry = 'select parent_index from v$asm_alias where group_number=?' .
         ' and reference_index=?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$asmcmdglobal_hash{'gnum'});
  $sth->bind_param(2,$ref_id);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $par_id = $row->{'PARENT_INDEX'};
  asmcmdshare_finish($sth);

  return $par_id;
}

########
# NAME
#   asmcmdshare_get_dg
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch a list of row(s) from  
#   v$asm_diskgroup.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gname  (IN) - optional: limit select by this group number, can contain
#                 the wildcard '*'.
#
# RETURNS
#   An array containing zero or one or more rows from v$asm_diskgroup.  The 
#   values of attributes for each row are stored in a hash, the reference to 
#   which is indexed in the array.
########
sub asmcmdshare_get_dg 
{
  my ($dbh, $gname, $discovery, $global) = @_;
  my ($sth, $qry, $row);
  my (@dg_list);            # The return array of hashes; see RETURNS above. #
  my ($view);                                      # The ASM view to querry. #

  # If Oracle Database version is less than 10gR2, then always do 
  # discovery, because the *_stat views are not available in 10gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) < 0 )
  {
    $discovery = 1;
  }

  if ($discovery && $global)
  {
    $view = 'gv$asm_diskgroup';
  }
  elsif ($discovery && !$global)
  {
    $view = 'v$asm_diskgroup';
  }
  elsif (!$discovery && $global)
  {
    $view = 'gv$asm_diskgroup_stat';
  }
  else
  {
    $view = 'v$asm_diskgroup_stat';
  }

  $qry = 'select * from ' . $view;

  # Narrow select if $gname is specified. 
  if (defined ($gname)) 
  {
    $gname =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g;
    $qry = $qry . ' where name like \'' . uc($gname) . '\'';
  }

  $sth = asmcmdshare_do_select($dbh, $qry);


  # Fetch results row by row and storeeach row in %dg_info, and reference
  # each %dg_info in @dg_list.
  while (defined ($row = asmcmdshare_fetch($sth))) 
  {
    my (%dg_info);                       # Allocate fresh hash for next row. #

    $dg_info{'inst_id'} = $row->{'INST_ID'};
    $dg_info{'group_number'} = $row->{'GROUP_NUMBER'};
    $dg_info{'name'} = $row->{'NAME'};
    $dg_info{'path'} = $row->{'NAME'};                # For a diskgroup, path is equal to name. This is needed so that 
                                                      # asmcmdbase_name_forward doesnot error out saying that path is uninitialized.
    $dg_info{'sector_size'} = $row->{'SECTOR_SIZE'};
    $dg_info{'block_size'} = $row->{'BLOCK_SIZE'};
    $dg_info{'allocation_unit_size'} = $row->{'ALLOCATION_UNIT_SIZE'};
    $dg_info{'state'} = $row->{'STATE'};
    $dg_info{'type'} = $row->{'TYPE'};
    $dg_info{'total_mb'} = $row->{'TOTAL_MB'};
    $dg_info{'free_mb'} = $row->{'FREE_MB'};

    # This attribute is available only in 10gR2 and after.
    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_10gR2) >= 0 )
    {
      $dg_info{'required_mirror_free_mb'} = $row->{'REQUIRED_MIRROR_FREE_MB'};
      $dg_info{'usable_file_mb'} = $row->{'USABLE_FILE_MB'};
      $dg_info{'offline_disks'} = $row->{'OFFLINE_DISKS'};
    }

    # This attribute is available only in 11gR2 and after.
    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_11gR2) >= 0 )
    {
      $dg_info{'voting_files'} = $row->{'VOTING_FILES'};
    }

    # It is possible that type is undefined if diskgroup is unmounted.  In 
    # that case we assign it an empty still so that we won't run into problems
    # of trying to concatenate undefined strings later.
    $dg_info{'type'} = '' unless (defined ($dg_info{'type'}));
    push (@dg_list, \%dg_info);
  }

  asmcmdshare_finish($sth);

  return (@dg_list);
}

########
# NAME
#   asmcmdshare_get_redund
#
# DESCRIPTION
#   This routine constructs the SQL used to the redundancy information for 
#   diskgroup $gnum (type attribute in v$asm_diskgroup).
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   gnum  (IN) - group number for the diskgroup in question.
#
# RETURNS
#   1 if diskgroup has external redundancy, 2 if normal redundancy, and 3
#   if high redundancy; 1 is default if diskgroup not found.
########
sub asmcmdshare_get_redund 
{
  my ($dbh, $gnum) = @_;

  my ($sth, $qry, $row);
  my ($redund);           # Return string for redundancy; see RETURNS above. #
  my ($view);                                           # The view to query. #

  # If Oracle Database version is less than 10gR2, then always do
  # discovery, because the *_stat views are not available in 10gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) < 0 )
  {
    $view = 'v$asm_diskgroup';
  }
  else
  {
    $view = 'v$asm_diskgroup_stat';
  }

  # Get diskgroup redundancy from group number.
  $qry = 'select type from ' . $view . ' where group_number=?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $redund = $row->{'TYPE'};
  asmcmdshare_finish($sth);

  return 1 if ($redund eq 'EXTERN');
  return 2 if ($redund eq 'NORMAL');
  return 3 if ($redund eq 'HIGH');
  return 1;
}

########
# NAME
#   asmcmdshare_get_gnum_from_gname
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the group number of the 
#   diskgroup that has the name $gname, iff it is mounted.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gname  (IN) - the name for the diskgroup for which we need the group 
#                 number.
#
# RETURNS
#   The group number as a string if the diskgroup is mounted; undefined 
#   otherwise.
########
sub asmcmdshare_get_gnum_from_gname 
{
  my ($dbh, $gname) = @_;

  my ($sth, $qry, $row);
  my ($gnum);                # Group number return value; see RETURNS above. #
  my ($state);                   # The state attribute from v$asm_diskgroup. #
  my ($view);                                           # The view to query. #

  # If Oracle Database version is less than 10gR2, then always do
  # discovery, because the *_stat views are not available in 10gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) < 0 )
  {
    $view = 'v$asm_diskgroup';
  }
  else
  {
    $view = 'v$asm_diskgroup_stat';
  }

  # Get diskgroup number from group name.
  $qry = 'select group_number, state from ' . $view . ' where name=\'' .
    uc($gname) . '\'';

  $sth = asmcmdshare_do_select($dbh, $qry);
  $row = asmcmdshare_fetch($sth);
  $gnum = $row->{'GROUP_NUMBER'};
  $state = $row->{'STATE'};
  asmcmdshare_finish($sth);

  # Return undefined if dismounted, so that caller knows not to 'cd' to this
  # diskgroup.
  return undef if (defined ($state) && ($state eq 'DISMOUNTED'));
  return $gnum;
}

########
# NAME
#   asmcmdshare_get_gname_from_gnum
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the name of the diskgroup
#   that has the group number $gnum.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   gnum  (IN) - the group number for the diskgroup for which we need the
#                name.
#
# RETURNS
#   The diskgroup name as a string.
########
sub asmcmdshare_get_gname_from_gnum 
{
  my ($dbh, $gnum) = @_;

  my ($sth, $qry, $row);
  my ($gname);                 # Group name return value; see RETURNS above. #
  my ($view);                                           # The view to query. #

  # If Oracle Database version is less than 10gR2, then always do
  # discovery, because the *_stat views are not available in 10gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) < 0 )
  {
    $view = 'v$asm_diskgroup';
  }
  else
  {
    $view = 'v$asm_diskgroup_stat';
  }

  # Get diskgroup name from group number.
  $qry = 'select name from ' . $view . ' where group_number=?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $gname = $row->{'NAME'};
  asmcmdshare_finish($sth);

  return $gname;
}

########
# NAME
# asmcmdshare_get_ugnum_from_ugname 
#
# DESCRIPTION
#   This routine gets the usergroup number from the usergroup name, diskgroup number
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   dgnum (IN) - disk group number.
#   ugname(IN) - user group name.
#
# RETURNS
#   The usergroup number.
########
sub asmcmdshare_get_ugnum_from_ugname 
{
  my ($dbh, $dgnum, $ugname) = @_;

  my ($sth, $qry, $row);
  my ($ugnum);                # Group name return value; see RETURNS above. #

  # Get usergroup name from usergroup number.
  $qry = 'select usergroup_number from v$asm_usergroup where group_number=?' .
         ' and name = ' . "\'$ugname\'";

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$dgnum);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $ugnum = $row->{'USERGROUP_NUMBER'};
  asmcmdshare_finish($sth);

  return $ugnum;

}


########
# NAME
#   asmcmdshare_do_construct_select
#
# DESCRIPTION
#   This routine executes select SQL queries specified by $qry.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   what  (IN) - array of columns to select
#   from  (IN) - array of tables to select from
#   where (IN) - array of conditions 
#   order (IN) - array of order by
#
# RETURNS
#   The initialized SQL statement handle.
#
# NOTES
#   This routine constructs the statement and calls asmcmdshare_do_select.
########
sub asmcmdshare_do_construct_select
{
  my ($dbh, $what, $from, $where, $order) = @_;
  my ($stmt);

  if (!@$what || !@$from)
  {
    asmcmdshare_error_msg(8100, undef);
  }

  $stmt = "SELECT " . join(', ', @$what);
  $stmt .= " FROM " . join(', ', @$from);

  if (defined($where) && @$where)
  {
    $stmt .= " WHERE " . join(' AND ', @$where);
  }

  if (defined($order) && @$order)
  {
    $stmt .= " ORDER BY " . join(', ', @$order);
  }

  return asmcmdshare_do_select($dbh, $stmt);
}


########
# NAME
#   asmcmdshare_do_select
#
# DESCRIPTION
#   This routine executes select SQL queries specified by $qry.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   qry   (IN) - the SQL query to be executed.
#
# RETURNS
#   The initialized SQL statement handle.
#
########
sub asmcmdshare_do_select 
{
  my ($dbh, $qry) = @_;
  my ($sth, $rv);

  $qry = '/* ASMCMD */ ' . $qry;                       # Add ASMCMD comment. #
  $sth = $dbh->prepare($qry);

  if (!defined ($sth))
  {                                           # Null $sth, must be an error. #
    if ($DBI::errstr =~ /ORA-03113/)
    {          # ORA-03113 means connection lost of foreground died, signal  #                                                        # exception and exit. #
      asmcmdshare_signal_exception (8200, undef);     }
    else
    {          # Connect not lost but probably SQL syntax error caused this. #
      asmcmdshare_error_msg(8100, undef);
      asmcmdshare_trace(5, "asmcmdshare_do_select():  $DBI::errstr", 'y', 'n');
    }

    return undef;
  }

  $asmcmdglobal_hash{'sth'} = $sth;
  $rv = $sth->execute;
  $asmcmdglobal_hash{'sth'} = undef;
  return undef unless(defined $rv);        # Return null if execution fails. #

  return $sth;
}

########
# NAME
#   asmcmdshare_fetch
#
# DESCRIPTION
#   This routine routine returns one row of results from an already executed
#   SQL query.  
#
# PARAMETERS
#   sth   (IN) - initialized SQL statement handle.
#
# RETURNS
#   A hash with attribute values as its values and attribute names as its
#   keys, for one row of results; undefined if $sth is undefined.
########
sub asmcmdshare_fetch 
{
  my $sth = shift;
  return undef unless(defined $sth);
  return $sth->fetchrow_hashref;
}

########
# NAME
#   asmcmdshare_finish
#
# DESCRIPTION
#   This routine closes the SQL statement handle $sth.
#
# PARAMETERS
#   sth   (IN) - initialized SQL statement handle.
#
# RETURNS
#   Undefined if $sth is undefined; otherwise returns the return value of
#   $sth->finish().
########
sub asmcmdshare_finish 
{
  my $sth = shift;
  return undef unless(defined $sth);
  return $sth->finish;
}

########
# NAME
#   asmcmdshare_do_stmt
#
# DESCRIPTION
#   This routine executes non-select SQL statements.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   stmt  (IN) - SQL statement string to query.
#   trim  (IN) - OPTIONAL: trim DBD ERROR from trace (default: 0).
#
# RETURNS
#   The return value of $sth->do($stmt).
#
# NOTES
#   
########
sub asmcmdshare_do_stmt 
{
  my ($dbh, $stmt) = @_;
  my ($trim) = defined $_[2] ? $_[2] : 0;
  my ($comment) = '/* ASMCMD */';

  if (!defined ($dbh)) 
  {
    asmcmdshare_signal_exception(8200, undef);
  }
  asmcmdshare_trace(3, "$comment . $stmt", 'y', 'n');

  return $dbh->do($comment . $stmt);
}


########
# NAME
#   asmcmdshare_do_prepare
#
# DESCRIPTION
#   This routine prepares select SQL queries.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   qry   (IN) - the SQL query to be prepared.
#
# RETURNS
#   The prepared SQL statement handle.
#
########
sub asmcmdshare_do_prepare
{
  my ($dbh, $qry) = @_;
  my ($sth, $rv);

  $qry = '/* ASMCMD */ ' . $qry;                       # Add ASMCMD comment. #
  $sth = $dbh->prepare($qry);

  asmcmdshare_do_validate_sth($sth);

  return $sth;
}


########
# NAME
#   asmcmdshare_do_validate_sth
#
# DESCRIPTION
#   This routine validates the SQL handle.
#
# PARAMETERS
#   sth   (IN) - The SQL statement handle.
#
# RETURNS
#   The SQL statement handle.
#
# NOTES
#   This routine call can hit two errors:
#     1) 8100: bad statement handle, probably bad SQL syntax in $qry.
#     2) 8200: lost connection to ASM instance or foreground died.
#   It will raise an asmcmd exception if the statement could not be executed
#   successfully.
#   It will raise an asmcmd exception if the statement could not be executed
#   successfully.
#   It will either execute the statement successfully and return the statement 
#   handle, OR raise an exception which ought to be caught and handled
#   in asmcmdcore_main()
#
########
sub asmcmdshare_do_validate_sth
{
  my ($sth) = @_;

  if (!defined ($sth))
  {                                           # Null $sth, must be an error. #
    if ($DBI::errstr =~ /ORA-03113/)
    {          # ORA-03113 means connection lost of foreground died, signal  #
                                                       # exception and exit. #
      asmcmdshare_signal_exception (8200, undef);
    }
    else
    {          # Connect not lost but probably SQL syntax error caused this. #
      asmcmdshare_error_msg(8100, undef);
      asmcmdshare_trace(5, "asmcmdshare_do_prepare():  $DBI::errstr", 'y', 'n');
    }

    return undef;
  }

  return $sth;
}


########
# NAME
#   asmcmdshare_do_execute
#
# DESCRIPTION
#   This routine executes select SQL queries previously
#   prepared.
#
# PARAMETERS
#   sth   (IN) - The prepared SQL statement handle.
#
# RETURNS
#   The initialized SQL statement handle.
#
########
sub asmcmdshare_do_execute
{
  my ($sth) = @_;
  my ($rv); 
  
  asmcmdshare_do_validate_sth($sth);
  
  $asmcmdglobal_hash{'sth'} = $sth;
  $rv = $sth->execute;
  $asmcmdglobal_hash{'sth'} = undef;
  
  if (!defined ($rv))
  {
    # trace the error
    asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') unless defined ($sth);
    # throw this exception up to catch and handle
    asmcmdexceptions::throw("asmcmdexceptions");
  }

  return $sth;
}


##############################################################################


########
# NAME
#   asmcmdshare_getpswd()
#
# DESCRIPTION
#   This routine prompts the user for a password when the user uses a connect
#   string with the -c option but does not specify the password as part of 
#   the connect string.
#
# PARAMETERS
#   None.
#
# RETURNS
#   The user entered password in a string.
#
# NOTES
########
sub asmcmdshare_getpswd 
{
  my ($msg) = shift;
  my ($pswd) = '';
  my ($chrgot);
  my ($maxstringput) = $ASMCMDSHARE_MAXPASSWD;
  my ($valid) = 0 ;

  $| = 1;

  while ($valid == 0)
  {
    $msg = 'Enter password: ' if (!defined($msg));

    print $msg;
    $maxstringput = $ASMCMDSHARE_MAXPASSWD ;
 
    while ($maxstringput--)
    {
      $chrgot = asmcmdshare_getchr_noecho();
   
      # Accept All alpha-numeric, printable characters.
      # Stop - LineFeed/CR - stop accepting character
      if ($chrgot =~ m'\n')
      {
        last;
      }
      else
      { 

        ## No standard way to detect BACKSPACE pressed in LINUX/WINDOWS
        ## checks with '\b' does not work on LINUX, works good on Windows
        ## Also with -w it gives more warnings.
        ##
        if (ord($chrgot) == 127 ) # BACKSPACE pressed
        {
          if (length($pswd) > 0)
          {
            $pswd = substr $pswd, 0, (length ($pswd) - 1);

            ### NOTE: For BACKSPACE we are not erasing the last '*' 
            ### from display.

            ## BACKSPACE pressed, one char removed.  We can add one more.
            $maxstringput = $maxstringput + 2;
          }
        }
        elsif ($pswd eq '')         # first char
        {
          $pswd = $chrgot;
          print "*" ;
        }
        else
        {
          $pswd = $pswd . $chrgot;
          print "*";
        }
      }
    }

    print "\n";

    # check for any invalid characters. (one or more of these chars).
    if ($pswd =~ /[\t\$\%\^\&\*\(\)\<\>\"\']/ )
    {
      # Invalid character in password.
      asmcmdshare_error_msg(8021, undef) ;
      $pswd = '' ;  # to resetart clear.
    }
    else
    {
      $valid = 1 ;  # no invalid characters found.
    }
  }

  return $pswd;
}



########
# NAME
#   asmcmdshare_getchr_noecho()
# DESCRIPTION
#   This routine reads in usr character input without clear-text echo, thus
#   provide a simple way of pretecting usr privacy & security. 
# PARAMETERS
#   None.
# RETURNS
#   The user entered password in a string.
# NOTES
########
sub asmcmdshare_getchr_noecho 
{
  my $inputchr;
  my $term;
  my $fd_stdin;
  my $oldterm;

  my $echo   = ECHO | ECHOK | ICANON;
  my $noecho = $echo & ~$echo;
  $fd_stdin = fileno(STDIN);
  $term     = POSIX::Termios->new();

  $term->getattr($fd_stdin);
  $oldterm    = $term->getlflag();

  # disable stdin clear-text echo so we can protect user passwd 
  $term->setlflag($noecho);
  $term->setcc(VTIME, 1);
  $term->setattr($fd_stdin, TCSANOW);

  # read one character at a time for sync. purpose
  sysread(STDIN, $inputchr, 1);

  # enable stdin right away so other apps won't be affected
  $term->setlflag($oldterm);
  $term->setcc(VTIME, 0);
  $term->setattr($fd_stdin, TCSANOW);

  return $inputchr;
}



########
# NAME
#   asmcmdshare_usergroupnumber2name
# DESCRIPTION
#   This routine gets a usergroup number and returns its name.
# PARAMETERS
#   usergroup number, diskgroup_number
# RETURNS
#   The usergroup name.
# NOTES
########
sub asmcmdshare_usergroupnumber2name
{
  my ($dbh, $usergroup_number, $dg_number) = @_;
  my ($usergroup_name);
  my (@what, @from, @where);
  my ($sth, $row);

  @what = ('name');
  @from = ('v$asm_usergroup');
  @where = ('usergroup_number = ' . $usergroup_number .
            ' and group_number = ' . $dg_number );

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where);
  asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') unless defined ($sth);

  $row = asmcmdshare_fetch($sth);

  #bug 7420875 corrected column to fetch user_group
  $usergroup_name = $row->{'NAME'};

  asmcmdshare_finish($sth);

  $usergroup_name = '' if (!defined($usergroup_name));

  return $usergroup_name;
}



########
# NAME
#   asmcmdshare_usernumber2name
# DESCRIPTION
#   This routine gets a user number, group number and returns its name.
# PARAMETERS
#   user number
#   group number
# RETURNS
#   The user name.
# NOTES
########
sub asmcmdshare_usernumber2name
{
  my ($dbh, $user_number, $group_number) = @_;
  my ($user_name) = '';
  my (@what, @from, @where);
  my ($sth, $row);

  @what = ('os_name');
  @from = ('v$asm_user');
  @where = ('user_number = ' . $user_number .
            ' and group_number = '.$group_number);

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where);
  asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y') unless defined ($sth);

  $row = asmcmdshare_fetch($sth);

  $user_name = $row->{'OS_NAME'};

  asmcmdshare_finish($sth);

  $user_name = '' if (!defined($user_name));

  return $user_name;
}

######## 
# NAME
#   asmcmdshare_print_cmds
# DESCRIPTION
#   This routine prints a list of commands in a formatted way for help.
# PARAMETERS
#   An array with the commands to print.
# RETURNS
#   A string with the formatted commands.
# NOTES
########
sub asmcmdshare_print_cmds
{
  my (@cmds) = @_;

  my ($cmd, $t);
  my (@line);
  my ($str)    = '';
  my ($header) = '        ';

  foreach $cmd(@cmds)
  {
    push (@line, $cmd);
    $t = $header . join(', ', @line);
    if (length ($t) >= 60)
    {
      @line = ();
      $str = $str . $t . "\n";
    }
  }
  $str = $str . $t . "\n" if (@line);

  return ($str);
}

########
# NAME
#   asmcmdshare_check_option_consistency
# DESCRIPTION
#
# PARAMETERS
# cmd(IN) - current command being processed
# $args_ref(IN) - reference to GetOptions result hash
#
# RETURNS
# None
#
# NOTES
########
sub asmcmdshare_check_option_consistency
{ 
  my(%module_cmds) = @_;
  my ($opt);
  my ($cmd);
  my $return_val = 1;

  ######################################################
  # If option not present in global hash
  #   - Add it to global hash
  # Else if duplicate (k,V) pair
  #   - ignore and move to next option
  # Else if the value is differnt for same options (key with diff values)
  #   - Error out and exit(check Failed)
  #######################################################
  foreach $cmd(sort(keys %module_cmds))
  {
    foreach $opt (sort(keys %{$module_cmds{$cmd}{flags}}))
    {
      my $k = $opt;
      #remove the '=' in the options which take values
      $opt =~ s/=.//;

      if($asmcmdglobal_options{$opt})
      {
        #handle duplicate options,error out if inconsistent
        if($asmcmdglobal_options{$opt} ne $module_cmds{ $cmd }{ flags}{$k})
        {
          asmcmdshare_trace(3, "Option '$opt' inconsistent while processing "
                    ."command:$cmd, correct the same to continue\n", 'n', 'y');
          $return_val=0;
          goto done;
        }
        next;
      }
      else
      {
         #if not already present add the current option to global options Hash
         $asmcmdglobal_options{$opt} = $module_cmds{ $cmd }{ flags}{$k};

      }
    }
  }
  done: 
    return $return_val;
}


########
# NAME
#   asmcmdshare_handle_deprecation
# DESCRIPTION
# This function checks whether the options for a command is deprecated
# If yes then print out a warning and set the new option for processing.
#
# PARAMETERS
# cmd(IN) - current command being processed
# $args_ref(IN) - reference to GetOptions result hash
# 
# RETURNS
# None
#
# NOTES
# This function is called only for commands which have deprecated
# options ie. find,ls,lsdsk,lsdg,md_restore,md_backup
########
sub asmcmdshare_handle_deprecation
{
  my ($cmd,$args_ref) = @_;
  my $iter;
  my @string;
  my (%args_depr);
  my $depr_opt = \%{$asmcmdglobal_deprecated_options{$cmd}};
  my @common_keys = grep { exists $depr_opt->{$_} } keys( %{ $args_ref } );

  #If there are deprecated options used.
  if($#common_keys >= 0) 
  {
    foreach $_(@common_keys)
    { 
       #Fetch the new option for current deprecated option
       my $option = $asmcmdglobal_deprecated_options{$cmd}{$_}[1]; 

       # Set the new option if corresponding deprecated option was set
       # Special checks for 'lsdsk' and 'md_restore' since options expand
       if($cmd eq 'lsdsk' && $_ eq 'm')
       { 
         $$args_ref{'member'} = '1' if($$args_ref{$_} eq 'm');
         $$args_ref{'candidate'} ='1' if($$args_ref{$_} eq 'c'); 
       }
       elsif($cmd eq 'md_restore' && $_ eq 't')
       {
         $$args_ref{'full'} ='1' if($$args_ref{$_} eq 'full');
	 $$args_ref{'nodg'} =1 if($$args_ref{$_} eq 'nodg');
         $$args_ref{'newdg'} =1 if($$args_ref{$_} eq 'newdg');
       }
       else
       {
         $$args_ref{$option} = $$args_ref{$_};
       }

       # Note: There are corner cases where both new option and the 
       # corresponding deprecated options is used together in a 
       # command the value that appears later in the order takes 
       # precedence. 
       # for eg : ASMCMD> md_backup -G DG1 -g DG2
       #          ASMCMD> md_backup -G DG1 -G DG2
       #          ASMCMD> md_backup -g DG1 -G DG2 
       # In the above examples md_backup will backup DG2 not DG1

       asmcmdshare_trace(2, "WARNING:option '$_' is deprecated for '$cmd'",
                         'n', 'y');
       asmcmdshare_trace(2, "please use '$option'\n", 'n',
                         'y') if($option ne 'NULL') ;
    }
  }
  return;
}


##############
# NAME
#   asmcmdshare_parse_xml
#
# DESCRIPTION
#   This function parses the asmcommand.xml file
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_parse_xml
{
  $xmlfile = "$ENV{'ORACLE_HOME'}/lib/asmcommand.xml";

  # set the handlers
  #  start_handler -> will be called whenever a new node is started.
  #  end_handler -> will be called whenever a node ends.
  #  char_hander -> will be called for the text within the node.
  #			This function will be called for each line 
  #			on multi-line case.  This callback is used to 
  #			compose the description for option.
  $xmlparser = XML::Parser->new(Handlers=>{Start=>
					\&asmcmdshare_xml_start_handler,
				Char=>\&asmcmdshare_xml_char_handler,
				End=>\&asmcmdshare_xml_end_handler});

  #parse the xml file
  $xmlparser->parsefile($xmlfile);  
}


##############
# NAME
#   asmcmdshare_xmlStartNodeHandler
#
# DESCRIPTION
#   This function is a callback - called whenever a start-node occurs
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_xmlStartNodeHandler
{
  my ($expat, $element, %attrs)=@_;
  
  # only interested in <command> nodes
  if($element eq $cmdNode)
  {
    # need to have cmdName attribute
    if(defined($attrs{'cmdName'}))
    {
      $cmdName = $attrs{'cmdName'} ;
      $text = "" ;
      $inCmdNode = "TRUE" ;
    }
  } 
}


##############
# NAME
#   asmcmdshare_xmlTextNodeHandler
#
# DESCRIPTION
#   This function is a callback - called on text node data .
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_xmlTextNodeHandler
{
  my($e, $string)=@_;

  # in case of multi-line, this callback is called for each line, 
  # append and collect all lines together.
  
  $text .= $string ;
}

##############
# NAME
#   asmcmdshare_xmlEndNodeHandler
#
# DESCRIPTION
#   This function is a callback  - called on endnode.
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_xmlEndNodeHandler
{
  my ($expat, $element, %attrs)=@_;

  # synopsis node
  if ( $element eq "synopsis" )
  {
    $hSyntax{$cmdName} = $text ;

  }

  # description for command node.
  if ($element eq "description")
  {
    $hCmdDesc{$cmdName} = $text ;
  }

  #option-description node
  if ($element eq "optDescription")
  {
    $hOptDesc{$cmdName} = $text ;
  }

  # example node
  if ($element eq "example")
  {
    $hExample{$cmdName} = $text;
  }

  $text ="";
}



##############
# NAME
#   asmcmdshare_parse_xml_help
#
# DESCRIPTION
#   This function parses the asmcommand.xml file
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_parse_xml_help
{
  $text = "" ;    # clear the text.
  $xmlfile = "$ENV{'ORACLE_HOME'}/lib/asmcommand.xml" ;
  
  #set the handlers
  #  xmlStartNodeHandler -> will be called whenever a new node is started
  #  xmlTextNodeHandler  -> will be called whenever text node (multi line)
  #  xmlEndNodeHandler   -> will be called whenever a node ends.
  
  $xmlparser = XML::Parser->new( Handlers => { 
                             Start => \&asmcmdshare_xmlStartNodeHandler,
                             Char  => \&asmcmdshare_xmlTextNodeHandler,
                             End   => \&asmcmdshare_xmlEndNodeHandler } ) ;
  # parse the xml file
  $xmlparser->parsefile ( $xmlfile ) ;

  $xmlparsed = 1 ;       # to avoid parsing xml file repeatedly.    
}

########
# NAME
#   asmcmdshare_get_help_syntax
#
# DESCRIPTION
# This function obtains the synopsis for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
# 
# RETURNS
# syntax - syntax of the given command
########
sub asmcmdshare_get_help_syntax
{
  $helpcmd = shift;      # given command

  # Syntax can be requested to display while invalid parameters are provided.
  # Load the help from xml file if not already done.
  if (!$xmlparsed)
  {
    asmcmdshare_parse_xml_help () ;
  }

  return $hSyntax{$helpcmd} ;
}


########
# NAME
#   asmcmdshare_get_help_cmdDesc
#
# DESCRIPTION
# This function obtains the description of the command
#
# PARAMETERS
# cmd(IN) - current command being processed
# 
# RETURNS
# syntax - syntax of the given command
########
sub asmcmdshare_get_help_cmdDesc
{
  $helpcmd = shift ;     #given command

  return $hCmdDesc{$helpcmd} ;
}

########
# NAME
#   asmcmdshare_get_help_optDesc
#
# DESCRIPTION
# This function obtains the description for the option.
#
# PARAMETERS
# cmd(IN) - current command being processed
# 
# RETURNS
# description of the option for the command
########
sub asmcmdshare_get_help_optDesc
{
  $helpcmd = shift ;    #given command

#  print "Opt Desc for $helpcmd \n $hOptDesc{$helpcmd}\n" ;
  return $hOptDesc{$helpcmd};
}


########
# NAME
#   asmcmdshare_get_help_syntax
#
# DESCRIPTION
# This function obtains the synopsis for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
# 
# RETURNS
# example - example for the command.
########
sub asmcmdshare_get_help_example
{
  $helpcmd = shift ; #given command

#  print "Example for $helpcmd : $hExample{$helpcmd}\n";
  return $hExample{$helpcmd};
}


########
# NAME
#   asmcmdshare_trim_str
#
# DESCRIPTION
# This function trims the given string
#
# PARAMETERS
# string (IN) - given string
# 
# RETURNS
# string  - trimmed string
########
sub asmcmdshare_trim_str
{
  my $string = shift ; # given string to trim.

  if (defined($string))
  {
    $string =~s/^\s+//;    # remove leading spaces
    $string =~s/\s+$//;    # remove trailing spaces.
  }
  else
  {
    $string ="";
  }
  
  return $string ;
}

########
# NAME
#   asmcmdshare_get_help_desc
# DESCRIPTION
# This function is the entry point to the parse help string and return.
#
# PARAMETERS
# cmd(IN) - current command being processed
# 
# RETURNS
# helpstr - help description for the given command
########
sub asmcmdshare_get_help_desc
{
  my $helpcmd = shift;   # given command for help.

  # Load the help from xml file if not already done.
  if (!$xmlparsed)
  {
    asmcmdshare_parse_xml_help () ;
  }

  # description of the command
  my $cmddesc = asmcmdshare_get_help_cmdDesc ($helpcmd);
  $cmddesc = "        ".asmcmdshare_trim_str ($cmddesc) ;


  # synopsis of command
  my $synopsis = asmcmdshare_get_help_syntax ( $helpcmd ) ;
  $synopsis = "        ".asmcmdshare_trim_str ($synopsis);

  # options description
  # options description could be empty (like in pwd).
  my $optDesc = asmcmdshare_trim_str (asmcmdshare_get_help_optDesc ($helpcmd));
  if ( "" ne $optDesc )  
  {
    $optDesc = "        ".asmcmdshare_trim_str ($optDesc);
  }

  # example
  my $example = asmcmdshare_get_help_example ($helpcmd);
  $example = "        ".asmcmdshare_trim_str ($example) ;

  my $helpstr = "" ;

  $helpstr = "        ".$helpcmd."\n\n$cmddesc\n" ;
  if ($synopsis ne "" )
  {
    $helpstr = $helpstr."\n$synopsis\n\n" ;
  }

  if ($optDesc ne "" )
  {
    $helpstr = $helpstr."$optDesc\n\n" ;
  }

  if ($example ne "" )
  {
    $helpstr = $helpstr."$example\n" ;
  }
 
  return $helpstr ;
}

