MINI MINI MANI MO

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

# Copyright (c) 2004, 2018, 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     06/04/18 - Backport prabbala_acmdpip_27948050 from main
#    apfwkr     03/28/18 - Backport moreddy_bug-27688692 from main
#    prabbala   05/31/18 - 27948050: add new timeout for checking pipes presence
#    moreddy    03/19/18 - 27688692: option to skip tracing in execute_tool
#    anovelo    05/19/17 - 26036398: return code 0 for ASMCMD 9526
#    anovelo    05/17/17 - 26083595: Remove hostname from example
#    diguzman   10/20/16 - 24923984: improve code coverage
#    anovelo    10/18/16 - 24706236: Enable different date formats in get_file
#    diguzman   10/13/16 - 12825292: add asmcmdshare_humanBytes
#    diguzman   09/22/16 - 11736372: add asmcmdshare_stack_trace
#    diguzman   09/01/16 - 24570890: get_redund returns FLEX & EXTEND values
#    emsu       06/08/16 - Add site_guid to disk queries
#    samjo      06/01/16 - 23511600: Fix asmcmdshare_is_dev_env()
#    diguzman   05/30/16 - 19654070: improve pasing of XML 'nointance' option
#    diguzman   05/20/16 - 23061091: get instance name from running processes
#    diguzman   04/12/16 - 22600475: add _filegroup_exists and _xml_exists
#    diguzman   03/08/16 - 21456380: add asmcmdshare_set_instance
#    jesugonz   02/16/15 - 22660914: add site_status info to disk queries
#    dacavazo   11/27/15 - 20766180: Make bg pipe filename contain its pid
#    emsu       10/18/15 - 22088795: +optional arg in asmcmdshare_execute_tool
#                          to redirect stderr to stdout,+asmcmdshare_is_dev_env
#    emsu       08/31/15 - 21827297: Add site info to disk queries
#    ykatada    08/06/15 - 21936597: add trace when opening pipe file
#    dacavazo   08/06/15 - 21453939: flag removed files on get_file
#    prabbala   07/30/15 - 21140011:use both LANG/NLS_LANG in asmcmdshare_runcmd
#    emsu       06/24/15 - 20732199: print informational error 9489 to stdout,
#                          make asmcmdshare_execute_tool fail when tool signals
#    byyang     06/04/15 - bug#21204705: do not trace msg to console when write
#                          to pipe fails
#    siyarlag   05/20/15 - fix syntax error
#    sanselva   05/12/15 - remove password validation in asmcmd
#    shlian     03/31/15 - 20672249: trim password from trace in share_do_stmt
#    dacavazo   03/10/15 - 20657441: added optional argument to
#                          asmcmdshare_do_stmt to trim DBD ERROR from trace
#    prabbala   01/30/15 - 20430157: log error in asmcmdshare_execute_tool
#                          only in cmd failure
#    prabbala   01/30/15 - 20434627: set SIG{CHLD} to DEFAULT before executing
#                          commands via backticks
#    dacavazo   01/21/15 - Showing user as <user>@<cluster> if cluster user
#    siyarlag   01/08/15 - do not remove new lines from runcmd output
#    ykatada    12/04/14 - #15949549: Add asmcmdshare_init_disk_attr and
#                                     asmcmdshare_get_dsk
#    siyarlag   12/10/14 - runcmd to store the command return value
#    prabbala   11/24/14 - bug19512500: add asmcmdshare_runcmd
#    ykatada    10/03/14 - #19617921: Enhance asmcmdshare_do_construct_select
#    shlian     09/09/14 - 19561559: filter out domain part in host name
#    cgraybl    09/05/14 - bug19065962: add logical sector size to lsdg
#    dacavazo   08/18/14 - 19445832: Daemon now reads pipe to a buffer
#    dacavazo   05/15/14 - 18274328: Added support for multiple concurrent FGs
#    hppancha   03/28/14 - Change behaviour of global_hash{'spawnutil'}
#    siyarlag   05/12/14 - increase the upper limit for asmcmd errors
#    hppancha   04/14/14 - bug18462686: relax error message range to accomodate
#                          afd related errors
#    shlian     03/13/14 - bug17377794: Use a generic name for SIGUSR1
#    pvenkatr   03/05/14 - Bug# 18034732 - replace all Windows style '\' to
#                          Unix style '/'.
#    pvenkatr   01/23/14 - Bug #18113228 Added check for SYSASM requirement.
#    diguzman   11/25/13 - #17786672: Fix pipe mask on asmcmdshare_mkfifo.
#    pvenkatr   11/04/13 - # 17395893 - Using PERL2C interface to pickup
#                          connection string. Added asmcmdshare_trace_msg,
#    pvenkatr   10/08/13 - LRG #9924405,9924821,9925240 - untaint the hostname
#                          value.
#    pvenkatr   09/25/13 - 17398626 - using uname-n & %COMPUTERNAME% instead of
#                          $HOSTNAME env var.
#    diguzman   01/10/13 - Bug #17346246 Check if the process exists before
#                          trying to kill it.
#    pvenkatr   07/31/13 - Bug #17180882 skipping TAB char.
#    manuegar   05/02/13 - Bug13951456 - added asmcmdshare_do_prepare,
#                          asmcmdshare_do_validate_sth and
#                          asmcmdshare_do_execute.
#    pvenkatr   04/29/13 - LRG 8953219 - reverted previous change, added
#                          more traces.
#    diguzman   04/18/13 - add functions for slave processes management
#    pvenkatr   04/16/13 - Bug #16670094 - added check in validate_path_recurse
#                          for non-existing dir in cd.
#    pvenkatr   04/01/13 - Bug 16578074, LRG 8953219 - fixed hang/signal 9
#                          issue.
#    pvenkatr   02/01/13 - consolidated all _execute_xxx to _execute_tool.
#    shmubeen   01/09/13 - Bug# 16384206/Lrg #8710957 - make sure background
#                          process in wait mode to receive signal
#    sanselva   12/05/12 - add support for checking visible flag
#    moreddy    11/22/12 - 14142633: asmcmd needs to reconnect for flex asm
#    pvenkatr   11/10/12 - #15848691 - consolidated executing kfod
#    shmubeen   10/16/12 - Fix for bug# 14184571.
#    skiyer     05/28/12 - 13952494:set exit return status for asmcmd commands
#    pvenkatr   05/10/12 - #14014928 - Using ReadKey instead of POSIX::Term
#    adileepk   11/25/11 - Fix for bug-12661648. ASMCMD should have exit status
#                          as -1 when a command fails in non-interactive mode.
#    adileepk   10/31/11 - Fix for lrg-5705736.
#    adileepk   10/10/11 - Extending the upper bound for error message numbers
#                          to accommodate new error messages for Shared
#                          Password commands.Transaction:
#                          adileepk_sharedpassword
#    pvenkatr   09/08/11 - Added noinstance, wildcard to XML & processing.
#    adileepk   08/23/11 - Fix for lrg 5840040. Solution for race condition in
#                          Connection Pooling.
#    adileepk   06/20/11 - Connection Pooling.
#    moreddy    03/09/11 - lrg 4808166 fix clsecho issues on NT
#    pvenkatr   03/02/11 - Bug 11829123. Character validation for password
#                          input.
#    adileepk   12/28/10 - Adding code for parsing the new xml help file,
#                          asmcommanddiag.xml
#    canuprem   01/09/10 - lrg 4808166. Fix for clsecho quotes on NT
#    moreddy    05/06/10 - bug 8667038 NLS for error messages
#    mchimang   05/26/10 - bug 8846724 added group_number check to
#                          usergroupnumber2name function.
#    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
#   asmcmdshare_get_host_name
#   asmcmdshare_runcmd
#   asmcmdshare_is_dev_env
#   asmcmdshare_set_instance
#
# 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
#   asmcmdshare_get_instance_name
#   asmcmdshare_init_disk_attr
#   asmcmdshare_get_dsk
#
# 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_host_name
                 asmcmdshare_runcmd
                 asmcmdshare_is_dev_env
                 asmcmdshare_set_instance
                 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_ziplists
                 asmcmdshare_humanBytes
                 asmcmdshare_getpswd
                 asmcmdshare_usergroupnumber2name
                 asmcmdshare_usernumber2name
                 asmcmdshare_filegroup_exists
                 asmcmdshare_xml_exists
                 asmcmdshare_get_ugnum_from_ugname
                 asmcmdshare_print_cmds
                 asmcmdshare_check_option_consistency
                 asmcmdshare_handle_deprecation
                 asmcmdshare_get_help_desc
                 asmcmdshare_get_help_syntax
                 asmcmdshare_get_cmd_wildcard
                 asmcmdshare_get_cmd_noinst
                 asmcmdshare_is_cmd_visible
                 asmcmdshare_trace
                 asmcmdshare_stack_trace
                 asmcmdshare_trace_msg
                 asmcmdshare_trim_str
                 asmcmdshare_print
                 asmcmdshare_printstderr
                 asmcmdshare_printprompt
                 asmcmdshare_printpipe
                 asmcmdshare_output
                 asmcmdshare_readcommands
                 asmcmdshare_readstdin
                 asmcmdshare_backup_stdio
                 asmcmdshare_alarm_handler
                 asmcmdshare_check_dbh_alive
                 asmcmdshare_openreadpipe
                 asmcmdshare_openwritepipe
                 asmcmdshare_closereadpipe
                 asmcmdshare_closewritepipe
                 asmcmdshare_removereadpipe
                 asmcmdshare_removefgpipe
                 asmcmdshare_removebgpipe
                 asmcmdshare_execute_tool
                 asmcmdshare_get_instance_name
                 asmcmdshare_filter_invisible_cmds
                 asmcmdshare_setstate
                 asmcmdshare_signalhandler
                 asmcmdshare_wait
                 asmcmdshare_waitslaves
                 asmcmdshare_packmsg
                 asmcmdshare_unpackmsg
                 asmcmdshare_logdata
                 asmcmdshare_signal
                 asmcmdshare_killslave
                 asmcmdshare_mkfifo
                 asmcmdshare_connect
                 asmcmdshare_sendmsg
                 asmcmdshare_recvmsg
                 asmcmdshare_fork_slaves
                 asmcmdshare_check_reqd_priv
                 asmcmdshare_init_disk_attr
                 asmcmdshare_get_dsk
                 %asmcmdshare_unix_os
                 %asmcmdshare_trace_levels
                 @asmcmdshare_commandlist
                 $asmcmdshare_term_string
                 $asmcmdshare_reconnect_string
                 $asmcmdshare_disablecp_string
                 $asmcmdshare_foreground_retries
                 $asmcmdshare_started_daemon
                 $asmcmdshare_writehandle
                 $asmcmdshare_readhandle
                 $asmcmdshare_pipetowrite
                 $asmcmdshare_pipetoread
                 $asmcmdshare_logheader
                 $asmcmdshare_daemonidlewait
                 $asmcmdshare_openpipewait
                 $asmcmdshare_readpipewait
                 $asmcmdshare_process_status
                 $asmcmdshare_siguser1
                 $asmcmdshare_pipehome
                 $asmcmdshare_wait4pipeCrt
               );

use strict;
use English;
use DBI qw(:sql_types);
use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat);
use POSIX;
use Term::ReadKey;
use Config;
##Commenting IPC::SysV module as it doesnt exist on Windows.
#use IPC::SysV;
use Data::Dumper;
use asmcmdexceptions;

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_DEFAULT
                    $ASMCMDGLOBAL_EXTERNAL
                    $ASMCMDGLOBAL_NORMAL
                    $ASMCMDGLOBAL_HIGH
                    $ASMCMDGLOBAL_FLEX
                    $ASMCMDGLOBAL_EXTENDED
                    $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   #

# bug19065962: ASMCMD internally runs few commands (e.g srvctl, crsctl) and #
# parses its output. It expects the output should be in English. If ASMCMD  #
# env has different language setting, it affects these commands too and     #
# ASMCMD fails to parse the output. So, run such commands with English as   #
# NLS_LANG.                                                                 #
my ($ASMCMDSHARE_SHENV_NLSLANG) = 'AMERICAN_AMERICA.us7ascii';
my ($ASMCMDSHARE_SHENV_LANG)    = 'en_US.UTF-8'; 
 
# 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',
                                  );

######
# Global variables used in connection pooling
######
our (@asmcmdshare_readbuffer)       = ();
our (@asmcmdshare_commandlist)      = ();
our ($asmcmdshare_term_string)      = "&&&&&\n";
our ($asmcmdshare_reconnect_string) = "Reconnect to ASM. dbh is stale.";
our ($asmcmdshare_disablecp_string) = "Disable Connection Pooling. Unable " .
                                      "to connect to ASM.";
our ($asmcmdshare_writehandle);
our ($asmcmdshare_readhandle);
our ($asmcmdshare_pipetowrite);           # Reference to the name of the pipe
                                          # to be used for writing. Would be
                                          # different for Foreground and
                                          # Background
our ($asmcmdshare_pipetoread);            # Reference to the name of the pipe
                                          # to be used for reading. Would be
                                          # different for Foreground and
                                          # Background
our ($asmcmdshare_logheader);             # A header string to be prepended to
                                          # logs. Different for Foreground and
                                          # Background
our ($asmcmdshare_wait4pipeCrt)     = 10; # Time to wait(in secs) for the pipes 
                                          # to appear
our ($asmcmdshare_daemonidlewait)   = 300;# Idle wait time for the ASMCMD
                                          # Background process, in secs
our ($asmcmdshare_openpipewait)     = 30; # Number of secs to wait for the open
                                          # pipe operation to complete.
our ($asmcmdshare_readpipewait)     = 300;# Time in secs to wait on a pipe for
                                          # data
our ($asmcmdshare_process_status);        # Status of this process, relevant
                                          # only in connection pooling context.
our ($asmcmdshare_siguser1)         = 0;  # Signal to invoke BG process waiting
                                          # IO
######
# 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 ($isWildcard) = "false" ;
my ($isNoinstance) = "false" ;
my ($isVisible) = "true" ;
my ($privReqd) = "sysasm";

# hash table of data for each commands
my (%hCmdDesc) = ();
my (%hSyntax) = () ;
my (%hExample) = () ;
my (%hOptDesc) = ();
my (%hSeeAlso) = () ;
my (%hWildcard) = () ;
my (%hNoInst) = () ;
my (%hisvisible) = () ;
my (%hPrivSysAsm) = ();
my (%hPrivSysDba) = ();

# Help msg topics
my ($synopStr) = "Synopsis";
my ($descStr)  = "Description";
my ($exampStr) = "Examples";
my ($excepStr) = "Exceptions" ;
my ($enhanStr) = "Enhancements" ;
my ($verStr) = "Version" ;
my ($seeAlsoStr) = "See Also";

# Globals used for master-slave communication
my ($asmcmdshare_pipehome);
my ($asmcmdshare_state);
my ($asmcmdshare_wait_flag) = 1;
my ($asmcmdshare_missing);
my ($exit) = 0;

######################### 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 it does not
#   belong to this module
#   0 if $arg is a command that needs to connect to an ASM instance
#   -1 if $arg is a command that may use an ASM instance.
#
# NOTES
#   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)
  {
    my $rc = $module->($arg);

    if ($rc != 1)
    {
      $no_instance_necessary = $rc;
      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, $buf);
  my ($clsecho_mesg) = '';
  my ($exit) = 0;      # Whether to exit ASMCMD at the end of this function. #

  # if clsecho utility does not exist or no execute permission, bail out.
  if (!-x $asmcmdglobal_clsecho)
  {
    return;
  }

  # Assert that $err_num is within 8001-9550, inclusive.
  @eargs = ("asmcmdshare_error_msg_05", $err_num);
  asmcmdshare_assert( (($err_num >= 8001) && ($err_num <= 9550)) , \@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 '')
  {
    eval
    {
      $buf = `$clsecho_mesg`;
    };
    if ($@ ne '')
    {
      chomp $@;
      asmcmdshare_print("$@\n");
      return;
    }
    elsif (defined($buf) && ($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)
      {
        chomp $buf;
        asmcmdshare_printstderr ("$buf\n");
      }
      # 20732199: error 9489 "no Client Clusters configured" is informational,
      # so print it to stdout, rather than stderr
      elsif ($err_num == 9489)
      {
        asmcmdshare_print("$buf\n");
      }
      else
      {
        asmcmdshare_trace(1,"$buf", 'y', 'y');
        asmcmdshare_stack_trace();
      }
    }
  }

  # 20732199: command should succeed when displaying error 9489
  # 26036398: ASMCMD-9526 is informative, should not return error code
  if (($asmcmdglobal_hash{'mode'} eq 'n') && 
       ($err_num != 9489 && $err_num != 9526))
  {
    $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 8000-9530, inclusive.
  if (($exception_num < 8000) || ($exception_num > 9530))
  {
    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_alarm_handler
#
# DESCRIPTION
#   This routine handles OS ALRM signal and other scenarios.
#   Logs the particular situation and either returns or raises and exception
#
# RETURNS
#   Null.
#
# NOTES
#   NONE.
########
sub asmcmdshare_alarm_handler
{
   my $exception = $@;
   if($asmcmdglobal_hash{'isdaemon'})
   {
      if($asmcmdshare_process_status =~ "READCOMMANDS")
      {
         if($exception =~ "ALARM")
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Exiting after " .
                                 "$asmcmdshare_daemonidlewait secs idle wait.",
                                 'y', 'n');
         }
         else
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
                                 "$$asmcmdshare_pipetoread. Exiting.",
                                 'y', 'n');
         }
         asmcmdshare_removebgpipe();
         exit(0);
      }
      elsif($asmcmdshare_process_status =~ "USERINPUT")
      {
         if($exception =~ "ALARM")
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader User input timed " .
                                 "out. Skipping.", 'y', 'n');
         }
         else
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
                                 "$$asmcmdshare_pipetoread. Skipping user " .
                                 "input.",
                                 'y', 'n');
         }
         return;
      }
      elsif($asmcmdshare_process_status =~ "OPENREADPIPE" ||
            $asmcmdshare_process_status =~ "OPENWRITEPIPE")
      {
        asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
                             "$$asmcmdshare_pipetoread. Exiting.", 'y', 'n');
        asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
        exit(0);
      }
   }
   else
   {
      if($exception =~ "ALARM")
      {
         if($asmcmdshare_process_status =~ "RECEIVINGOUTPUT")
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Output from ASMCMD " .
                                 "Background process timed out. Disabling " .
                                 "connection pooling and retrying command.",
                                 'y', 'n');
            $asmcmdshare_process_status = "RUNNING";
            return;
         }
      }
      if($asmcmdshare_process_status =~ "OPENREADPIPE" ||
            $asmcmdshare_process_status =~ "OPENWRITEPIPE")
      {
         if($asmcmdshare_process_status =~ "OPENREADPIPE")
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
                              "$$asmcmdshare_pipetoread. Connection Pooling " .
                              "failed. Retrying.", 'y', 'y');
         }
         elsif($asmcmdshare_process_status =~ "OPENWRITEPIPE")
         {
            # bug#21204705: do not trace msg to console with OPENWRITEPIPE
            # If the last arg is 'y', the process dies in trace().
            # Thus unlink is not executed.
            asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
                              "$$asmcmdshare_pipetowrite. Connection Pooling " .
                              "failed. Retrying.", 'y', 'n');
            asmcmdshare_closewritepipe();
            if(-p $$asmcmdshare_pipetowrite)
            {
               asmcmdshare_trace(3, "$asmcmdshare_logheader ".
                                "Removed pipe $$asmcmdshare_pipetowrite: ".
                                "unresponsive pipe, the BG could have exited ".
                                "without removing the pipe.", 'y', 'n');
               unlink $$asmcmdshare_pipetowrite; # Remove the unresponsive pipe.
                                                 # The ASMCMD background process
                                                 # could have exited without
                                                 # removing the pipe.

               asmcmdshare_removebgpipe();
            }
         }
         elsif($asmcmdshare_process_status =~ "OPENREADPIPE")
         {
            asmcmdshare_closereadpipe();
            if(-p $$asmcmdshare_pipetoread)
            {
               asmcmdshare_trace(3, "$asmcmdshare_logheader ".
                                 "Removed pipe $$asmcmdshare_pipetoread: ".
                                 "unresponsive pipe.", 'y', 'n');
               unlink $$asmcmdshare_pipetoread;  # Remove the unresponsive pipe.
            }
         }
         $asmcmdshare_process_status = "RUNNING";
         die;
      }
   }
}


########
# 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 SIGINT and SIGUSR1.
########
sub asmcmdshare_signal_handler
{
   my ($sigtype) = shift;
   asmcmdshare_trace(1, "Proc: [$$] SIG: [$sigtype]", 'y', 'n');

   if($sigtype eq 'INT')
   {
      if($asmcmdglobal_hash{'ispooled'})
      {
         if($asmcmdglobal_hash{'isdaemon'})
         {
            if(defined ($asmcmdglobal_hash{'sth'}))
            {
               $asmcmdglobal_hash{'sth'}->cancel;
               asmcmdshare_finish($asmcmdglobal_hash{'sth'});
               $asmcmdglobal_hash{'sth'} = undef;
            }
            if(-p $asmcmdglobal_hash{'fgpipe'})
            {
               asmcmdshare_print($asmcmdshare_disablecp_string);
            }
            asmcmdshare_trace(3, "$asmcmdshare_logheader Received an INT " .
                                 "signal. Exiting.",
                                 'y', 'n');
            asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
            exit(0);
         }
         else
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader Received an INT " .
                                 "signal.", 'y', 'n');
            asmcmdshare_closereadpipe();
            asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
            exit(0);
         }
      }
      else # If it is not a connection pooled scenario
      {
         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;
      }
   }
   elsif ($sigtype eq 'USR1')
   {
     if (defined($asmcmdglobal_hash{'ismaster'}))
     { # Is the master of a master-slaves scenario
       if ($asmcmdshare_state eq 'CONNECTED') {
         $asmcmdshare_missing--;
         asmcmdshare_trace(1, "Waiting for slaves: [$asmcmdshare_missing]",
           'y', 'n');
       }
       elsif($asmcmdshare_state eq 'WAITING')
       {
         $asmcmdshare_wait_flag = 0;
       }
     }
     elsif (defined($asmcmdglobal_hash{'isslave'}))
     { # Is a slave of a master-slaves scenario
       if ($asmcmdshare_state eq 'RUNNING') {
         $exit = 1;
       }
       elsif( $asmcmdshare_state eq 'WAITING')
       {
         $asmcmdshare_wait_flag = 0;
       }
       # TODO: implement signal handle for slave
     }
   }
   elsif ($sigtype eq 'QUIT')
   {
     asmcmdshare_trace(1, "Received signal 'quit'", 'y', 'n');
     if (defined($asmcmdglobal_hash{'isslave'}))
     {
       asmcmdshare_trace(1, "Slave $$ killed by master process", 'y', 'n');
       exit 1;
     }
   }
}
##############################################################################




########################### 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. #

  # Replace DOS/Windows style '\' to Unix style '/'
  if ($path =~ m/\\/)
  {
    $path =~ s/\\/\//g ;      # replace all occurecte(s) DOS '\' to Unix '/' #
  }

  $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. #
  my ($subdirExists);                           # subdirectory exists or not #

  $subdirExists = 'true' ;             # assume each subdir component exists #

  @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)
      {
        if ($subdirExists eq 'true')
        {
          # pop out the last subdir - only if exists.
          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.

        # Bug # 16670094 In case of directory deleted by "rm -rf *", "cd .."
        # will not find last parent directory - in case of sys alias -.
        # Ignore this case and continue.  $vld_XXX will valid components
        if ( ($asmcmdglobal_hash{'cmd'} eq 'cd' ) && ($work_path eq ".."))
        {
          # subdir does not exist.
          $subdirExists = 'false';
          next;
        }

        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, undef, $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_get_host_name
#
# DESCRIPTION
#   This function obtains the current computer name
#
# PARAMETERS
#   -none-
#
# RETURNS
#   $ret  - current computer name, undef if any errors.
#
# NOTES:
#   $ENV{'HOSTNAME'} is not available in all Unix platforms, using "uname -n"
#   On Windows, %COMPUTERNAME% environment variable will be defined always.
#   If the domain part is contained in the hostname, it will be filtered out.
########
sub asmcmdshare_get_host_name
{
  my ($hostname) = undef;
  my ($ret)      = undef;         # return value with domain filtered out #

  if ($^O =~ /win/i)
  {
    # windows  - COMPUTERNAME environment will have the host name.
    if (defined ($ENV{'COMPUTERNAME'}))
    {
      $hostname = $ENV{'COMPUTERNAME'};

      asmcmdshare_trace (3, "Computer name $hostname", 'y', 'n');
    }
    else
    {
      asmcmdshare_trace (3, "Unable to obtain computer name", 'y', 'n');
    }
  }
  else
  {
    # unix flavors
    eval
    {
      $hostname = `uname -n`;
    };
    if ($@ ne '')
    {
      asmcmdshare_trace (3, "Computer name $hostname", 'y', 'n');
    }
    else
    {
      asmcmdshare_trace (3, "Unable to obtain computer name", 'y', 'n');
    }
  }

  # untaint hostname value if exists.
  if (defined($hostname))
  {
    $hostname =~ /([^\n^\r^\t]+)/;
    $hostname = $1;

    # Bug19561559: sometimes the host name we obtain may contain the
    # domain (i.e. <hostname.domain>). In such a case, the 
    # domain name should be filtered out and only the node name should 
    # be returned.
    ($ret) = split(/\./, $hostname); 
  }
 
  return $ret;
}

########
# NAME
#   asmcmdshare_runcmd
#
# DESCRIPTION
#   This function runs a command in OS shell and stores the output in the 
#   passed-in buffer.
#
# PARAMETERS
#   cmd      (IN)    The command to be run
#   buf_ref  (OUT)   Reference of the buffer to store command output
#   lang     (IN)    Flag to specify if the command should be run with 
#                    LANG=en_US.UTF-8
#   redirect (IN)    redirect stderr to stdin i.e 2>&1
#
# RETURNS
#   0           success
#   non-zero    failure
#
# NOTES
#   If LANG env is set to non-english, ASMCMD doesn't know to parse it.  This 
#   function sets LANG environment variable so that output can be parsed 
#   by other ASMCMD functions.
#   CRS commands honours LANG only. To be transparent  and usable to all 
#   commands, let us set both env variables LANG and NLS_LANG to English before
#   running the command.
#   
########
sub asmcmdshare_runcmd
{
  my ($cmd, $buf_ref, $lang, $redirect) = @_;
  my  $ret = 0;
  my  $erc = 0;                              # program return code
  my ($nlslang_sav) = "";             # current setting of env var NLS_LANG #
  my ($lang_sav) = "";                   # current setting of env var _LANG #
  my  @eargs;

  @eargs = ('asmcmdshare_runcmd01');
  asmcmdshare_assert((ref($buf_ref) eq "ARRAY"), \@eargs);

  # Store current NLS_LANG/LANG value and set it to readable through code.  #
  if ($lang) 
  {
    if (defined($ENV{'NLS_LANG'}) && $ENV{'NLS_LANG'})
    {
      $nlslang_sav = $ENV{'NLS_LANG'} 
    }
    if (defined($ENV{'LANG'}) && $ENV{'LANG'})
    {
      $lang_sav = $ENV{'LANG'} 
    }
    $ENV{'NLS_LANG'} = $ASMCMDSHARE_SHENV_NLSLANG;
    $ENV{'LANG'} = $ASMCMDSHARE_SHENV_LANG;
  }

  # run the command
  eval
  {
    # 20434627: if SIG{CHLD} is IGNORED, perl always set $? to -1 and $! to 10 
    # (no child process). It might be because when signal IGNORED, it seems 
    # somehow the child status is lost.
    local $SIG{CHLD} = 'DEFAULT';
    if ($^O =~ /win/i)
    {
      #no need to redirect output in Windows
      my $line = `$cmd`;
      @{$buf_ref} = split(/\n/, $line);
    }
    else
    {
      $cmd = $cmd . " 2>&1" if $redirect;
      @{$buf_ref} = `$cmd`;
    }
    $erc = $CHILD_ERROR >> 8;   # get program return code
  };
  if ($@ ne '')
  {
    # if there was a signal or coredump during command execution
    $ret = 1;
    asmcmdshare_trace(3, "NOTE: command ($cmd) execution signalled : $@",
                      'y', 'n');

    # in failure, store the error in buffer.
    @{$buf_ref} = $@;
  }
  else
  {
    # after command exeuction return the program return code
    $ret = $erc;
    if ($ret != 0)
    {
      asmcmdshare_trace(3, "NOTE: command execution ($cmd) returned : $ret",
                        'y', 'n');
    }
  }

  # restore the NLS_LANG value. set NLS_LANG only if it was set  previously
  if ($lang)
  {
    $ENV{'NLS_LANG'} = ($nlslang_sav) ? $nlslang_sav : undef;
    $ENV{'LANG'}     = ($lang_sav)    ? $lang_sav    : undef;
    asmcmdshare_trace(3, "reverted LANG/NLS_LANG to $lang_sav/$nlslang_sav ",
                      'y', 'n');
  }
  return $ret;
}


########
# NAME
#   asmcmdshare_is_dev_env
#
# DESCRIPTION
#   This function checks whether it is being executed in a development
#   environment.
#
# PARAMETERS
#   None
#
# RETURNS
#   1 if in development environment
#   0 otherwise
#
# NOTES
#   Whether we are in a development environment is determined by whether
#   certain environment variables are set.
#
########
sub asmcmdshare_is_dev_env
{
  my $ret = 0;

  if ((defined($ENV{'_SCLS_DEVELOPMENT'}) &&
       uc($ENV{'_SCLS_DEVELOPMENT'}) eq "TRUE") ||
      (defined($ENV{'HAS_DEVELOPMENT_ENVIRONMENT'}) &&
       uc($ENV{'HAS_DEVELOPMENT_ENVIRONMENT'}) eq "TRUE"))
  {
    $ret = 1;
  }
  return $ret;
}

########
# NAME
#   asmcmdshare_set_instance
#
# DESCRIPTION
#   This function looks for the SID value for the specified type of instance
#
# PARAMETERS
#   tgt       (IN)  - type of the target instance, it could be ASM, IOS or APX
#
# RETURNS
#   The target's SID on success
#   empty string when the target's SID could not be fetched.
#   undef on error
#
# NOTES
#
########
sub asmcmdshare_set_instance
{
  my $tgt = shift;
  my $sid = undef;
  my @procs;
  my @pmons;
  my $pmon;
  my $sth;
  my $inst;
  my @what;
  my @from;

  return undef unless ($tgt =~ m/^(ASM|IOS|APX)$/);

  if ($^O =~ m/win/i)                                       # Windows         #
  {
    @procs = `tasklist`;
  }
  else                                                      # UNIX default    #
  {
    @procs = `ps -ef`;
  }

  @pmons = grep(/\w\w\w_pmon_\S+/, @procs);

  foreach $pmon (@pmons)
  {
    if ($pmon =~ /.*(\w{3})_pmon_(\S+)/)
    {
      my $inst = uc $1; # Instance type
      my $insn = $2;    # Instance name

      if ($inst =~ /$tgt/)
      {
        $sid = $insn;
        last;
      }
    }
  }

  if (!defined($sid))
  {
    asmcmdshare_trace(1, "Could not identify instance name for target ".
                      "$tgt", 'y', 'y');
    $sid = '';
  }

  return $sid;
}

########
# 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);

  # if clsecho utility does not exist or no execute permission bail out.
  if (!-x $asmcmdglobal_clsecho)
  {
    return;
  }

  $set_level = $asmcmdshare_trace_levels{$asmcmdglobal_hash{'verbose'}};

  # Nothing to do if the given msg_level is greater than set_level
  if ($set_level < $msg_level)
  {
    return;
  }

  # Check if timestamp is printed along with the trace messages
  if ($timestamp eq 'y')
  {
    $timestamp = '-t';
  }
  else
  {
    $timestamp = ' ';
  }

  # Print the trace messages
  if ($console eq 'y')
  {
    asmcmdshare_printstderr("$msg\n");
  }
  $msg=~ s/\"/\"'\"'\"/g;
  if($set_level <= 3)
  {
    if (defined $asmcmdglobal_hash{'isslave'})
    { # Each slave process writes on its own trace file
      my $slave_id = $asmcmdglobal_hash{'slaveid'};
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/trace/asmcmd_slave_$$.trc \"$msg\"";
    }
    else
    {
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/alert/alert.log \"$msg\"";
    }
  }
  else
  {
    if (defined $asmcmdglobal_hash{'isslave'})
    { # Each slave process writes on its own trace file
      my $slave_id = $asmcmdglobal_hash{'slaveid'};
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/trace/asmcmd_slave_$$.trc \"$msg\"";
    }
    else
    {
      $clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
        ."$asmcmdglobal_trace_path/trace/trace.trc \"$msg\"";
    }
  }

  eval
  {
    if($asmcmdglobal_hash{'isdaemon'})
    {
       my $clsecho_stderr_file = $asmcmdglobal_hash{'tempdir'} .
                                 "/clsecho_stderr_file.txt";
       $buf = `$clsecho_write 2> $clsecho_stderr_file`;
       # Open the file handle, if failed then raise an exception.
       open (MYSTDERR, "<$clsecho_stderr_file") or
       die "Could not open intermediate file $clsecho_stderr_file when " .
           "in Connection pooled mode.";
       my $string;
       while($string = <MYSTDERR>)
       {
          asmcmdshare_printstderr($string);
       }
       close MYSTDERR;
       unlink $clsecho_stderr_file;
    }
    else
    {
       $buf=`$clsecho_write`;
    }
  };
  if ($@ ne '')
  {
    if($@ =~ "Could not open intermediate file")
    {
       $buf=`$clsecho_write`;
    }
    asmcmdshare_error_msg(8020, undef);
  }
}

########
# NAME
#   asmcmdshare_stack_trace
#
# DESCRIPTION
#   This function prints the call stack with each funcion's arguments if verbose
#   mode is lower than 'debug' this function does nothing.
#
# USAGE
#
# PARAMETERS
#
# RETURNS
#   Null.
#
########
sub asmcmdshare_stack_trace
{
  if ($asmcmdglobal_hash{'verbose'} eq 'debug')
  {
    my $stack;

    $stack = Carp::longmess("The stack trace");
    $stack =~ s/\t//g;
    asmcmdshare_trace(5, "$stack", 'y', 'n');
  }
}

########
# NAME
#   asmcmdshare_trace_msg
#
# DESCRIPTION
#   This function fetches message string from msg file (localized) and adds
#   it to trace file using asmcmdshare_trace().
#
# PARAMETERS
#   level    (IN)  - trace level
#   err_num  (IN)  - error number.
#
# RETURNS
#   Nothing
#
# NOTES:  Only Trace level is passed as parameter, no replacement string(s).
#         This only goes to trace file and NOT displayed to user, Timestamp
#         added as default.
#
##########
sub asmcmdshare_trace_msg
{
  my ($level, $err_num) = @_;
  my ($clsecho_mesg);
  my ($buf);

  # if clsecho utility does not exist or no execute permission bail out.
  if (!-x $asmcmdglobal_clsecho)
  {
    return;
  }

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

  if ($clsecho_mesg ne '')
  {
    eval
    {
      $buf = `$clsecho_mesg` ;
    };

    if ($@ ne '')
    {
      chomp $@;
      asmcmdshare_print ("$@\n");
      return ;
    }
    elsif (defined($buf) && ($buf ne ''))
    {
      asmcmdshare_trace ($level, $buf, 'y', 'n');
    }
  }
}


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





############################## 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 = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,'instance_type',SQL_VARCHAR);
  asmcmdshare_do_execute($sth);
  $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.
#   time_format    (IN)     - format of the timedate for file in question.
#   file_info_ref  (IN/OUT) - reference to hash of file information to be
#                             returned.
#
# RETURNS
#   1 if the file is valid
#   0 if the file was removed
#
# 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 different time
#   formats of the attribute 'modification_date'.
#
#   When a file has been removed and we query using the compound_index, the
#   modification_date will be NULL. We can use that column to see if the file
#   is still valid.
########
sub asmcmdshare_get_file
{
  my ($dbh, $gnum, $fnum, $time_format, $file_info_ref) = @_;
  my $sth;
  my $qry;
  my $row;
  my $cmpd_ind;
  my $printError;

  # If no time format has been specified, use the default (MON DD HH24:MI:SS)
  if (!defined $time_format)
  {
    $time_format = 'MON DD HH24:MI:SS';
    $printError = 0;
  }
  else
  {
    # In case the time format written by the user contains errors, enable
    # error tracing.
    $printError = 1;
  }

  $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, ?) "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 = ? ORDER BY ' .
         'GROUP_NUMBER, FILE_NUMBER, USER_NUMBER, USERGROUP_NUMBER '
         ;

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1, $time_format, SQL_VARCHAR);
  $sth->bind_param(2, $cmpd_ind, SQL_INTEGER);
  asmcmdshare_do_execute($sth, $printError);

  $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);

  # If the file was removed before calling this function, the
  # modification_date will be NULL. We can use the julian_date column since it
  # is derived from modification_date. Return 1 if it is valid, 0 if invalid.
  return defined($file_info_ref->{'julian_date'});
}

########
# 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.
#   time_format   (IN)     - format in which the datetime will be displayed.
#   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, $time_format, $gnum, $ref_id, $par_id, $name, $type,
      $sys_non_dir_only, $get_file_info) = @_;

  my $sth;
  my $qry_alias;
  my $row;
  my $hash_key;
  my %tmphash;

  my @what;
  my @from;
  my @where;
  my @order;
  my @binds;

  # 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.
  push (@what, 'name');
  push (@what, 'group_number');
  push (@what, 'file_number');
  push (@what, 'reference_index');
  push (@what, 'parent_index');
  push (@what, 'alias_directory');
  push (@what, 'system_created');

  push (@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))
  {
    push (@where, 'group_number = ?');
    push (@binds, [$gnum, SQL_INTEGER]);
  }

  if (defined ($name))
  {
    push (@where, 'upper(name) like ?');
    push (@binds, [uc($name), SQL_VARCHAR]);
  }

  if (defined ($ref_id))
  {
    push (@where, 'reference_index = ?');
    push (@binds, [$ref_id, SQL_INTEGER]);
  }

  if (defined ($par_id))
  {
    push (@where, 'parent_index = ?');
    push (@binds, [$par_id, SQL_INTEGER]);
  }

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

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

  # Fetch results row by row and store each row in %entry_info which will be
  # stored in a temporary hash.
  while (defined ($row = asmcmdshare_fetch($sth)))
  {
    my (%entry_info);                    # Allocate fresh hash for next row. #

    # v$asm_alias entries
    # If any field is undefined, the file might have been removed so skip it.
    $entry_info{'group_number'} = $row->{'GROUP_NUMBER'};
    next if !defined($entry_info{'group_number'});
    $entry_info{'file_number'} = $row->{'FILE_NUMBER'};
    next if !defined($entry_info{'file_number'});
    $entry_info{'reference_index'} = $row->{'REFERENCE_INDEX'};
    next if !defined($entry_info{'reference_index'});
    $entry_info{'parent_index'} = $row->{'PARENT_INDEX'};
    next if !defined($entry_info{'parent_index'});
    $entry_info{'alias_directory'} = $row->{'ALIAS_DIRECTORY'};
    next if !defined($entry_info{'alias_directory'});
    $entry_info{'system_created'} = $row->{'SYSTEM_CREATED'};
    next if !defined($entry_info{'system_created'});
    $entry_info{'name'} = $row->{'NAME'};
    next if !defined($entry_info{'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'};
      # Save the index for the current element in @$list_ref
      $tmphash{$hash_key} = scalar @$list_ref;
    }
    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.
  #
  # The file could have been removed by another process. If so, undefine the
  # whole row to filter out the invalid files.
  foreach (keys (%tmphash))
  {
    my $i = $tmphash{$_};
    my $valid = asmcmdshare_get_file($dbh, $$list_ref[$i]->{'group_number'},
                                     $$list_ref[$i]->{'file_number'},
                                     $time_format, $$list_ref[$i]);
    undef $$list_ref[$i] if !$valid;
  }
  # Filter out all undefined rows
  @$list_ref = grep{ defined }@$list_ref;

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

  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 = ?' .
         ' and parent_index = ?' .
         ' and upper(name) = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$asmcmdglobal_hash{'gnum'});
  $sth->bind_param(2,$par_id);
  $sth->bind_param(3,uc($name),SQL_VARCHAR);
  asmcmdshare_do_execute($sth);
  $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 ?';

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

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

  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_prepare($dbh, $qry);
  $sth->bind_param(1,uc($name),SQL_VARCHAR);
  if (defined ($par_id))
  {
    $sth->bind_param(2,$par_id,SQL_INTEGER);
  }
  asmcmdshare_do_execute($sth);

  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) = ?';

      $sth = asmcmdshare_do_prepare($dbh, $qry2);
      $sth->bind_param(1,$cmpd_ind);
      $sth->bind_param(2,uc($type),SQL_VARCHAR);
      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 ?';
  }

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

  if (defined ($gname))
  {
    $sth->bind_param(1,uc($gname),SQL_VARCHAR);
  }

  asmcmdshare_do_execute($sth);

  # Fetch results row by row and store each 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 doesn't error
                                         # out saying that path is
                                         # uninitialized.
    $dg_info{'sector_size'} = $row->{'SECTOR_SIZE'};
    $dg_info{'logical_sector_size'} = $row->{'LOGICAL_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
#   TYPE       RETURNS
#   ---------  --------------------
#   DEFAULT    ASMCMDGLOBAL_DEFAULT
#   EXTERNAL   ASMCMDGLOBAL_EXTERNAL
#   NORMAL     ASMCMDGLOBAL_NORMAL
#   HIGH       ASMCMDGLOBAL_HIGH
#   FLEX       ASMCMDGLOBAL_FLEX
#   EXTENDED   ASMCMDGLOBAL_EXTENDED
#
########
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 $ASMCMDGLOBAL_EXTERNAL if ($redund eq 'EXTERN');
  return $ASMCMDGLOBAL_NORMAL   if ($redund eq 'NORMAL');
  return $ASMCMDGLOBAL_HIGH     if ($redund eq 'HIGH');
  return $ASMCMDGLOBAL_FLEX     if ($redund eq 'FLEX');
  return $ASMCMDGLOBAL_EXTENDED if ($redund eq 'EXTEND');
  return $ASMCMDGLOBAL_DEFAULT;
}

########
# 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 = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,uc($gname),SQL_VARCHAR);
  asmcmdshare_do_execute($sth);
  $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_filegroup_exists
#
# DESCRIPTION
#   Checks if the given file group name exists.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   fgname (IN) - the name for the file group for which we need to confirm
#                 its existence
#
# RETURNS
#   1 if the file group exists; undefined otherwise.
########
sub asmcmdshare_filegroup_exists
{
  my ($dbh)    = shift;
  my ($fgname) = shift;
  my ($rc)     = undef;

  my ($sth, $qry, $row);

  # Get file group number from file group name.
  $fgname = uc $fgname;
  $qry    = 'select filegroup_number from v$asm_filegroup ' .
            "where name = '$fgname'";

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

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

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

  $rc = 1 if (defined($row));
  return $rc;
}

########
# NAME
# asmcmdshare_xml_exists
#
# DESCRIPTION
#   This routine checks if the given XML file name exists and is readable
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   dgnum (IN) - disk group number.
#   ugname(IN) - user group name.
#
# RETURNS
#   1 if the files exists and is readable. undef otherwise with the proper
#   error number signaled.
########
sub asmcmdshare_xml_exists
{
  my ($file)  = shift;
  my (@eargs) = ();
  my ($err);

  if (-e $file)
  {
    return 1 if (-r $file);

    # ASMCMD-8611 "cannot read '%s', permission denied."
    $err = 8611;
  }
  else
  {
    # ASMCMD-8610: "cannot access '%s', no such XML file."
    $err = 8610;
  }

  push @eargs, $file;
  asmcmdshare_error_msg($err, \@eargs);
  return undef;
}

########
# 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 = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$dgnum);
  $sth->bind_param(2,$ugname,SQL_VARCHAR);
  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
#   bind  (IN) - array of bind variables
#
# RETURNS
#   The initialized SQL statement handle.
#
# NOTES
#   This routine constructs the statement and calls asmcmdshare_do_execute
########
sub asmcmdshare_do_construct_select
{
  my ($dbh, $what, $from, $where, $order, $bind) = @_;
  my ($stmt);
  my ($sth);
  my ($numarry);
  my ($id);
  my (@eargs);

  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);
  }

  # For diagnostic
  asmcmdshare_trace(5, "asmcmdshare_do_construct_select: $stmt",'y', 'n');

  $sth = asmcmdshare_do_prepare($dbh, $stmt);

  # Calls bind_param based on the array bind
  if (defined($bind) && @{$bind})
  {
    # get number of indexes
    $numarry = scalar(@{$bind});

    for ($id = 0; $id < $numarry; $id++)
    {
      # For diagnostic
      asmcmdshare_trace(5, "asmcmdshare_do_construct_select: idx=$id" .
                        " val=$bind->[$id][0] type=$bind->[$id][1]", 'y', 'n');

      # Bind the variables based on the data type  
      $sth->bind_param($id+1, $bind->[$id][0], $bind->[$id][1]);
    }
  }

  asmcmdshare_do_execute($sth);

  return ($sth);
}


########
# NAME
#   asmcmdshare_check_db_alive
#
# DESCRIPTION
#   This routine tries to prepare a sql statement handle using the dbh.
#   If it fails, the DB Handle is stale and invalid. The ASM
#   instance has gone down in the meantime
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, MUST BE non-null.
#
# RETURNS
#   0 - If the preparation of statement failed, indicating stale DB Handle
#   1 - If the attempt was successful. DB Handle is valid.
#
########
sub asmcmdshare_check_dbh_alive
{
   my ($dbh) = @_;
   my ($sth, $qry);

   $qry = 'select version from v$instance';
   $qry = '/* ASMCMD */ ' . $qry;                       # Add ASMCMD comment. #
   $sth = $dbh->prepare($qry);

   if (!defined ($sth))
   {
      return 0;
   }
   return 1;
}


########
# 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);

  asmcmdshare_do_execute($sth);

  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 and statement info. contains password
#                          from trace (default: 0).
#
# RETURNS
#   The return value of $sth->do($stmt).
#
# NOTES
#   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_stmt
{
  my ($dbh, $stmt) = @_;
  my ($trim) = defined $_[2] ? $_[2] : 0;
  my ($comment) = '/* ASMCMD */';
  my ($sth);

  if (!defined ($dbh))
  {
    asmcmdshare_signal_exception(8200, undef);
  }

  # 20672249: avoid tracing statement info. that contains password.
  # $trim would be set to 1 for three commands: "passwd", "orapwusr -add" and
  # "orapwusr -modify".
  if ($trim == 0)
  {
    asmcmdshare_trace(3, "$comment . $stmt", 'y', 'n');
  }

  $sth = $dbh->do($comment . $stmt);

  if (!defined ($sth))
  {
    # trace the error
    my $err = "$DBI::errstr";
    if ($trim)
    {
       $err =~ s/\(DBD ERROR: .*//g;
    }
    asmcmdshare_trace(1, "$err", 'y', 'y');
    # throw this exception up to catch and handle
    asmcmdexceptions::throw("asmcmdexceptions");
  }
  # Return the statement handle if it is not null,
  # ie. if the statement was executed successfully.
  return $sth;

}


########
# 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');
    }
    # Throw this exception up to catch and handle, (it is a syntax error).
    asmcmdexceptions::throw("asmcmdexceptions");
  }

  return $sth;
}

########
# NAME
#   asmcmdshare_do_execute
#
# DESCRIPTION
#   This routine executes select SQL queries previously
#   prepared.
#
# PARAMETERS
#   sth   (IN) - The prepared SQL statement handle.
#   trim  (IN) - OPTIONAL: trim DBD ERROR and statement info. Used only for 'ls'
#
# RETURNS
#   The initialized SQL statement handle.
#   Raises an asmcmd exception if the statement could not be executed
#   successfully.
#
########
sub asmcmdshare_do_execute
{
  my ($sth) = @_;
  my $trim = defined $_[1] ? $_[1] : 0;
  my $rv;

  asmcmdshare_do_validate_sth($sth);

  $asmcmdglobal_hash{'sth'} = $sth;
  $rv = $sth->execute;
  $asmcmdglobal_hash{'sth'} = undef;

  if (!defined ($rv))
  {
    my $err = "$DBI::errstr";
    if ($trim)
    {
      $err =~ s/\(DBD ERROR: .*//g;
      asmcmdshare_trace(1, $err, 'y', 'y');
    }
    elsif (!defined ($sth))
    {
      asmcmdshare_trace(1, $err, 'y', 'y');
    }
    # throw this exception up to catch and handle
    asmcmdexceptions::throw("asmcmdexceptions");
  }

  return $sth;
}

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

########
# NAME
#   asmcmdshare_ziplists()
#
# DESCRIPTION
#   Function to combine two lists of the same length into one in the same way
#   the Python built-in zip() function does.
#
#   Let A = (a0, a1, ..., an) and B = (b0, b1, ..., bn) two arrays of the same
#   lenght this routine returns a new list Z = (a0, b0, a1, b1, ..., an, bn).
#
# PARAMETERS
#   A (IN) - array
#   B (IN) - array
#
# RETURNS
#   The generated zipped array.
########
sub asmcmdshare_ziplists
{
  my @args   = @_; # @args list contains all the elements of A followed by
                   # all the elements of B
  my @eargs  = ("asmcmdshare_ziplists01");
  my $middle;
  my @zipped;

  asmcmdshare_assert(@args % 2 == 0, \@eargs);

  $middle = @args / 2;
  @zipped = @args[ map {$_, $_ + $middle} (0 .. ($middle - 1)) ];

  return @zipped;
}

########
# NAME
#   asmcmdshare_humanBytes()
#
# DESCRIPTION
#   This rutine provides a human readable string that represents the given
#   number interpreted as a size in bytes.
#
# PARAMETERS
#   bytes (IN) - The number of bytes to be represented as a string
#
########
sub asmcmdshare_humanBytes
{
  my $bytes = shift;
  my $hBytes;
  my @keys;
  my %table;
  my $k;
  my $v;

  @keys  = ('1099511627776', '1073741824', '1048576', '1024', '1');
  %table = asmcmdshare_ziplists(@keys, ('T', 'G', 'M', 'K', ''));

  foreach (@keys)
  {
    $k = $_;
    $v = $table{$k};
    last if ($bytes >= $k);
  }

  if ($k > 1)
  {
    $bytes /= $k;
    $hBytes = sprintf("%.1f%s", $bytes, $v);
    $hBytes =~ s/\.0+//;
  }
  else
  {
    $hBytes = "$bytes";
  }

  return $hBytes;
}

########
# 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;

  $| = 1;

  $msg = 'Enter password: ' if (!defined($msg));

  print $msg;
  $maxstringput = $ASMCMDSHARE_MAXPASSWD ;

  while ($maxstringput--)
  {
    ReadMode("raw");
    $chrgot = ReadKey(0);
    ReadMode("restore");

    # Accept All alpha-numeric, printable characters.
    # Stop - LineFeed/CR - stop accepting character
    if (ord($chrgot) == 13 || ord($chrgot) == 10)
    {
      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.
      ## Using ORD to keep it consistent.
      ## ORD-127 is for DEL, and that cannot be supported as we do not support
      ## <- & -> keys.
      ##
      if (ord($chrgot) == 8 ) # BACKSPACE pressed
      {
        if (length($pswd) > 0)
        {
          $pswd = substr $pswd, 0, (length ($pswd) - 1);
          print "\b \b";

          ## 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";
  # 20721939: no need to validate password in asmcmd, passwords are quoted and
  # sqlplus is supposed to handle the validation and asmcmd is just a wrapper

  return $pswd;
}

########
# 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 ($sth, $qry, $row);

  $qry = 'select name from v$asm_usergroup' .
         ' where usergroup_number = ?' .
         ' and group_number = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$usergroup_number,SQL_INTEGER);
  $sth->bind_param(2,$dg_number,SQL_INTEGER);

  asmcmdshare_do_execute($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 ($sth, $qry, $row);

  $qry = 'select os_name, cluster_id from v$asm_user' .
         ' where user_number = ? and group_number = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$user_number,SQL_INTEGER);
  $sth->bind_param(2,$group_number,SQL_INTEGER);

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

  $user_name = $row->{'OS_NAME'};
  if (defined($user_name))
  {
    $user_name .= '@'.$row->{'CLUSTER_ID'} if (defined($row->{'CLUSTER_ID'}));
  }
  else
  {
    $user_name = '';
  }

  asmcmdshare_finish($sth);

  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
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_parse_xml
{
  # The file asmcommanddiag.xml is parsed only if it exists.
  my @xmlfiles = ("$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 files
  foreach (@xmlfiles)
  {
      $xmlparser->parsefile ($_) if (-r $_);
  }
}


##############
# 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'};
      $inCmdNode = "TRUE";
      $text      = "";

      if (defined($attrs{'wildcard'}) && ($attrs{'wildcard'} eq "true"))
      {
        $isWildcard = "true";
      }
      else
      {
        $isWildcard = "false";
      }

      if (defined($attrs{'noinstance'}))
      {
        if ($attrs{'noinstance'} eq "true")
        {
          $isNoinstance = "true";
        }
        elsif ($attrs{'noinstance'} eq "undef")
        {
          # 19654070: this is a special case in which the command needs to
          # connect to the instance, but not for all its options. ASMCMD will
          # try to connect to ASM but will not signal errors if connection
          # fails.
          $isNoinstance = "undef";
        }
        else
        {
          $isNoinstance = "false";
        }
      }
      else
      {
        $isNoinstance = "false";
      }

      if (defined($attrs{'visible'}))
      {
        $isVisible = $attrs{'visible'};
      }

      # privileged required for this command.  SYSASM is assumed for all cmds.
      # if SYSDBA is specifed, SYSDBA connections can also run this command.
      if (defined($attrs{'priv'}))
      {
        $privReqd = $attrs{'priv'};
      }
    }
  }
}


##############
# 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;
  }

  # seeAlso node
  if ($element eq "seeAlso" )
  {
    $hSeeAlso{$cmdName} = $text ;
  }
  $text ="";

  #
  # Attributes for the command
  #
  if ($element eq $cmdNode)
  {
    # wildcard
    if ($isWildcard eq "true")
    {
      $hWildcard{$cmdName} = "true";
    }
    else
    {
      $hWildcard{$cmdName} = "false";
    }

    # noinstance
    if ($isNoinstance eq "true")
    {
      $hNoInst{$cmdName} = "true";
    }
    elsif ($isNoinstance eq "undef")
    {
      # 19654070: this is a special case in which the command needs to connect
      # to the instance, but not for all its options. ASMCMD will try to connect
      # to ASM but will not signal errors if connection fails.
      $hNoInst{$cmdName} = "undef";
    }
    else
    {
      $hNoInst{$cmdName} = "false";
    }

    # visible
    if ($isVisible eq "false")
    {
      $hisvisible{$cmdName} = "false";
    }

    # Privilege required.  All Commands can be executed in SYSASM connections
    # Commands which can be executed are listed here.
    $hPrivSysAsm{$cmdName} = "True";
    if ($privReqd eq "sysdba")
    {
      $hPrivSysDba{$cmdName} = "True";
    }
  }
}



##############
# NAME
#   asmcmdshare_parse_xml_help
#
# DESCRIPTION
#   This function parses the asmcommand.xml and asmcommnaddiag.xml files
#
# PARAMETERS
#   -None-
#
# RETURNS
#  None
##############
sub asmcmdshare_parse_xml_help
{
  $text = "" ;    # clear the text.
  # The file asmcommanddiag.xml is parsed only if it exists.
  my @xmlfiles = ("$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 files
  foreach (@xmlfiles)
  {
    $xmlparser->parsefile ($_) if (-r $_);
  }

  $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_cmd_wildcard
#
# DESCRIPTOIN
#   This function obtains flag - whether the give command supports wild card
#
# PARAMETERS
#   cmd(IN) - current command.
#
# RETURNS
#   True    - if wild card is supported
#   False   - otherwise
#
########
sub asmcmdshare_get_cmd_wildcard
{
  my $cmd = shift ;   # given command.
  my $ret = "false" ;

  if (!$xmlparsed)
  {
    asmcmdshare_parse_xml_help () ;
  }

  if(defined $hWildcard{$cmd} && ($hWildcard{$cmd} eq "true" ) )
  {
    $ret = "true" ;
  }

  return $ret ;
}

########
# NAME
#   asmcmdshare_get_cmd_noinst
#
# DESCRIPTOIN
#   This function obtains flag - whether the give command needs ASM Instance or
#   not.
#
# PARAMETERS
#   cmd(IN) - current command.
#
# RETURNS
#   true    - if instance is not required
#   false   - otherwise
#
########
sub asmcmdshare_get_cmd_noinst
{
  my ($cmd) = shift;   # given command.
  my ($ret) = "false";

  asmcmdshare_parse_xml_help() if (!$xmlparsed);

  $ret = $hNoInst{$cmd} if (defined($hNoInst{$cmd}));

  return $ret;
}

########
# NAME
#   asmcmdshare_is_cmd_visible
#
# DESCRIPTOIN
#   This function checks if the given command is visible or hidden
#
# PARAMETERS
#   cmd(IN) - current command.
#
# RETURNS
#   1    - if marked as visible
#   0    - otherwise
#
########
sub asmcmdshare_is_cmd_visible
{
  my $cmd = shift ;   # given command.
  my  $ret = 1 ;

  if (!$xmlparsed)
  {
    asmcmdshare_parse_xml_help () ;
  }

  if(defined $hisvisible{$cmd} && $hisvisible{$cmd} ne "true" )
  {
    $ret = 0 ;
  }

  return $ret ;
}

# 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_get_help_seeAlso
#
# DESCRIPTION
# This function obtains the seeAlso for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# seeAlso - seeAlso for the command.
########
sub asmcmdshare_get_help_seeAlso
{
  $helpcmd = shift ; #given command
  return $hSeeAlso{$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.
  my $helpstr = "" ;

  # 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) ;
  $helpstr .= $helpcmd."\n$cmddesc\n" ;

  # synopsis of command
  my $synopsis = asmcmdshare_get_help_syntax ( $helpcmd ) ;
  $synopsis = "$synopStr\n        ".asmcmdshare_trim_str ($synopsis);
  $helpstr .= "\n$synopsis\n\n" ;

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

  # example
  my $example = asmcmdshare_trim_str (asmcmdshare_get_help_example ($helpcmd));
  if ($example ne "" )
  {
    $example = "$exampStr\n        ". $example ;
    $helpstr .= "$example\n\n" ;
  }

  #see Also
  my $seeAlso = asmcmdshare_trim_str (asmcmdshare_get_help_seeAlso ($helpcmd));
  if ($seeAlso ne "" )
  {
    $seeAlso = "$seeAlsoStr\n       ". $seeAlso;
    $helpstr .= "$seeAlso\n" ;
  }

  return $helpstr ;
}

########
# NAME
#   asmcmdshare_print
#
# DESCRIPTION
#   This function is a wrapper which calls asmcmdshare_print_internal with
#   correct parameters.
#
# PARAMETERS
#   string (IN) - The string to be printed.
#
# RETURNS
#   Nothing.
#
# NOTES
#   Ideally, each line being printed to the pipe should be terminated with a
#   new line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_print
{
   my $string = shift;
   asmcmdshare_print_internal(1, $string);
}

########
# NAME
#   asmcmdshare_printstderr
#
# DESCRIPTION
#   This function is a wrapper which calls asmcmdshare_print_internal with
#   correct parameters to print to STDERR.
#
# PARAMETERS
#   string (IN) - The string to be printed.
#
# RETURNS
#   Nothing.
#
# NOTES
#   Ideally, each line being printed to the pipe should be terminated with a new
#   line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_printstderr
{
   my $string = shift;
   asmcmdshare_print_internal(2, $string);
}

########
# NAME
#   asmcmdshare_printprompt
#
# DESCRIPTION
#   This function is a wrapper which calls asmcmdshare_print_internal with
#   correct parameters. It is used to print a prompt (for confirmation from
#   the user or requesting more information from the user.)
#   The string which will be printed to the pipe will be prepended with
#   "STDIN:" so that the foreground process prompts the user and accepts
#   further user input. The foreground process then provides this data back to
#   the daemon.
#
# PARAMETERS
#   string (IN) - The string to be printed.
#
# RETURNS
#   Nothing.
#
# NOTES
#   Ideally, each line being printed to the pipe should be terminated with a new
#   line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_printprompt
{
   my $string = shift;
   asmcmdshare_print_internal(0, $string);
}

########
# NAME
#   asmcmdshare_print_internal
#
# DESCRIPTION
#   This function prints the string passed as argument to the pipe or to the
#   appropriate STD outputs depending on the filehandle type and the nature of
#   the connection (pooled or otherwise).
#   If it is to be printed to the pipe, (as in the case of a pooled connection)
#   it prepends appropriate markers eg. "STDERR:" or "STDIN:" in case the string
#   is to be printed to STDERR, or is to be printed as a prompt respectively.
#
# PARAMETERS
#   $filehandletype (IN) - 0, for PROMPT
#                        - 1, for STDOUT
#                        - 2, for STDERR
#   string          (IN) - The string to be printed.
#
# RETURNS
#   Nothing.
#
# NOTES
#   Ideally, each line being printed to the pipe should be terminated with a new
#   line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_print_internal
{
   my $filehandle = shift;
   my $string = shift;

   # If the connection is not pooled, then print to either STDOUT or STDERR
   # depending on the filehandle type
   if(!$asmcmdglobal_hash{'ispooled'})
   {
      print $string if($filehandle <= 1);
      print STDERR $string if($filehandle == 2);
      return;
   }

   if($asmcmdglobal_hash{'isdaemon'})
   {
      if(!$asmcmdglobal_hash{'fgpipe'})
      {
         return;
      }
      # If the filehandle
      # is 0, then this string is to be printed to the STDOUT by the foreground
      # process as a prompt for further input/ a confirmation from the user.
      if($filehandle == 0)
      {
         asmcmdshare_printpipe("$$:PROMPT:$string\n");
         asmcmdshare_trace(3, "$asmcmdshare_logheader Requesting further " .
                              "user input for " . $asmcmdglobal_hash{'cmd'} .
                              " command.\n", 'y', 'n');
         return;
      }
      elsif($filehandle >= 1)
      {
         my @buffer = split(/\n/, $string);
         push(@buffer, "") if($string =~ m/\n\n$/ || $string eq "\n");
         foreach(@buffer)
         {
            # If the filehandle
            # is 1, then this string is to be printed to the STDOUT by the
            # foreground process.
            asmcmdshare_printpipe("$$:STDOUT:$_\n") if($filehandle == 1);
            # If the filedescriptor is 2 then string should be printed to the
            # STDERR.
            asmcmdshare_printpipe("$$:STDERR:$_\n") if($filehandle == 2);
         }
         return;
      }
   }
   # This block will be executed only if this function was called by the
   # foreground process to pass further user input to the asmcmd daemon.
   else
   {
      asmcmdshare_printpipe("$$:STDIN:$string");
      return;
   }
}

########
# NAME
#   asmcmdshare_print_pipe
# DESCRIPTION
#   This function prints the string to the pipe specified as argument
#
# PARAMETERS
#   pipename (IN) - The pathname of the pipe to be written to.
#   string   (IN) - The string to be printed.
#   flush    (IN) - Whether to immediately print or buffer it.
#
# RETURNS
#   Nothing.
########
sub asmcmdshare_printpipe
{
   my $string;
   my $fileno = fileno($asmcmdshare_writehandle);
   $string    = shift;

   if (!defined($fileno))
   {
     asmcmdshare_trace(1, "$asmcmdshare_logheader Invalid file handle for " .
                       "pipe $$asmcmdshare_pipetowrite", 'y', 'n');
     die;
   }

   if($asmcmdglobal_hash{'isdaemon'})
   {
      # Check whether the foreground process is alive, if it is not, then die
      # Also check whether the pipe is stil present.
      if((defined $asmcmdglobal_hash{'fgpid'} &&
          !kill(SIGCHLD, $asmcmdglobal_hash{'fgpid'})) ||
         !-p $asmcmdglobal_hash{'fgpipe'})
      {
         # Highly unlikely events.
         # 1. Foreground process dies out after sending the command to daemon
         # 2. Pipe to the foreground (created by the daemon) suddenly vanishes
         if(-p $asmcmdglobal_hash{'fgpipe'})
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader ".
                              "Removed pipe $asmcmdglobal_hash{'fgpipe'}: ".
                              "FG died after sending the command, ".
                              "or pipe to FG suddenly vanished.", 'y', 'n');
            unlink $asmcmdglobal_hash{'fgpipe'};
         }
         asmcmdshare_removebgpipe();
         exit(0);
      }
   }
   else
   {
      # Check whether the background process is alive, if it is not, then die
      # Also check whether the pipe is still present.
      if((defined $asmcmdglobal_hash{'bgpid'} &&
          !kill(SIGCHLD, $asmcmdglobal_hash{'bgpid'})) ||
         !-p $asmcmdglobal_hash{'bgpipe'})
      {
         # Highly unlikely events.
         # 1. Background process dies out after receiving the command
         # 2. Pipe to the background (created by this foreground or a previous
         #    foreground) suddenly vanishes
         if(-p $$asmcmdshare_pipetowrite)
         {
           asmcmdshare_closewritepipe();
           asmcmdshare_trace(3, "$asmcmdshare_logheader ".
                             "Removed pipe $$asmcmdshare_pipetowrite: ".
                             "BG died after receiving command, ".
                             "or pipe to BG suddenly vanished.", 'y', 'n');
           unlink $$asmcmdshare_pipetowrite;
         }
         die;
      }
   }
   print $asmcmdshare_writehandle $string;
   if ($asmcmdglobal_hash{'running'} == 1)
   {
     #NOTE: need to close/reopen pipe handle in case of iostat command.
     close $asmcmdshare_writehandle;
     open($asmcmdshare_writehandle, "> $$asmcmdshare_pipetowrite");
   }
   return 1;
}

########
# NAME
#   asmcmdshare_readcommands
# DESCRIPTION
#   This function reads a list of commands from the pipe used by foreground
#   processes to send commands to the asmcmd daemon, if it's a connection
#   pooled session. It also reads any user input that is available in the pipe
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
########
sub asmcmdshare_readcommands
{
  my $string;
  my $stdin_string;
  my $waittime;
  my $success = 0;
  my $bgwtfilename;

  if ($asmcmdshare_process_status eq "USERINPUT")
  {
    $waittime = $asmcmdshare_readpipewait;
  }
  else
  {
    $asmcmdshare_process_status = "READCOMMANDS";
    $waittime = $asmcmdshare_daemonidlewait;
  }

  if ($asmcmdglobal_hash{'isdaemon'})
  {
    eval
    {
      alarm $waittime;
      # asmcmd_bg_wait_<BGPID> is created to ensure that background process
      # is in waiting mode to receive signal from foreground. This file will
      # as soon as background process receives signal. Foreground
      # process sends SIGUSR1 only if the file exists.
      # 16875041: SIGUSR1 is no longer sent, it now waits until something is
      # written in the pipe. asmcmd_bg_wait_<BGPID> is not used either.
      asmcmdshare_trace(3, "$asmcmdshare_logheader Waiting for new " .
                        "commands/user input. Waiting for $waittime " .
                        "secs. Status = $asmcmdshare_process_status",
                        'y', 'n');

      $string = asmcmdshare_readpipe();
      # If this line is a user input rather than a command,
      # then do not push it to the array, but return the data as a string
      if ($string =~ /^(\d*):STDIN:(.*)$/)
      {
        if ($1 == $asmcmdglobal_hash{'fgpid'})
        {
          $stdin_string = $2;
          chomp $stdin_string;
          asmcmdshare_trace(3,"$asmcmdshare_logheader received user " .
                            "input for " . $asmcmdglobal_hash{'cmd'} .
                            " command.\n", 'y', 'n');
          $success = 1 if($asmcmdshare_process_status eq "USERINPUT");
          next;
        }
      }
      if ($string =~ /^!!!!!(\d+#[^!]*)!!!!!!!!!!(\d+#[^!]*)!!!!!$/)
      {
        $string = $2;
        $success = 1 if($asmcmdshare_process_status eq "READCOMMANDS");
      }
      elsif ($string =~ /^!!!!!(\d+#[^!]*)!!!!!$/)
      {
        $string = $1;
        $success = 1 if($asmcmdshare_process_status eq "READCOMMANDS");
      }
      push (@asmcmdshare_commandlist, $string) if ($string =~ /^\d+#.*/);
      alarm 0;
    };
    if ($@)
    {
      asmcmdshare_alarm_handler();
      $stdin_string = "";
    }
  }
  $asmcmdshare_process_status = "RUNNING";

  if ($success == 0)
  {
    return '';
  }
  return $stdin_string;
}

########
# NAME
#   asmcmdshare_readpipe
#
# DESCRIPTION
#   This function reads a line of data from the pipe which has been
#   designated as the read pipe.
#
# PARAMETERS
#   None.
#
# RETURNS
#   String that is read from the pipe
#
# NOTES
#   Waits for the pipe to appear indefinitely, hence should be surrounded by
#   an alarm. Also waits indefinitely on the opened pipe till a non-null string
#   is read.
#   Opens the pipe if the read filehandle is not open.
#   Throws an exception if pipe could not be opened.
#
########
sub asmcmdshare_readpipe
{
   my $string;
   my $pipename;

   asmcmdshare_chkpipeexst($$asmcmdshare_pipetoread);

   # 19445832: Background can hog the CPU in a read spin. To avoid this, open
   # the pipe and read it entirely to a buffer and then close it. If there are
   # still lines in the buffer, there is no need to read the pipe. The pipe
   # read will wait as expected only after the first open.
   if ($asmcmdglobal_hash{'isdaemon'})
   {
     if (scalar(@asmcmdshare_readbuffer) == 0)
     {
       # Buffer is empty, read from pipe
       my $pipe_buffer = '';

       asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
                         "$$asmcmdshare_pipetoread to read data", 'y', 'n');

       open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
                      or die "Could not open the pipe $$asmcmdshare_pipetoread";
       asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
                        "pipe $$asmcmdshare_pipetoread in read mode", 'y', 'n');
       @asmcmdshare_readbuffer = <$asmcmdshare_readhandle>;
       map chomp, @asmcmdshare_readbuffer;
       close $asmcmdshare_readhandle;
       asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
                         "$$asmcmdshare_pipetoread.", 'y', 'n');
     }
     # Extract data from the buffer
     $string = shift @asmcmdshare_readbuffer;
   }
   else # Foreground
   {
     if (!defined(fileno($asmcmdshare_readhandle)))
     {
       open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
               or die "PIPE: Could not open the pipe $$asmcmdshare_pipetoread";
     }
     my $success = 0;
     while (!$success)
     {
       $string = <$asmcmdshare_readhandle>;
       $success = 1 if (defined $string);
     }
   }
   return $string;
}

########
# NAME
#   asmcmdshare_output
# DESCRIPTION
#   This function reads whatever output is present in the pipe, and prints it.
#
# PARAMETERS
#   None.
#
# RETURNS
#   status (OUT) - 0 if end of output is reached
#                - 1 if more output is expected, but pipe is empty for now.
#
# NOTES
#   This funtion should be called only by the asmcmd foreground process, since
#   it assumes the presence of the pipe used by the asmcmd daemon to communicate
#   with the foreground process in a connection pooled session.
########
sub asmcmdshare_output
{
   my $string;
   my $ret = 1;
   my ($close_readhandle, $close_writehandle);
   while(1)
   {
      $string = asmcmdshare_readpipe();
      # If marked with STDOUT print to STDOUT
      if($string =~ /^(\d+):STDOUT:/)
      {
         $asmcmdglobal_hash{'bgpid'} = $1;
         # if the string contains the terminating string.
         if($string =~ $asmcmdshare_term_string)
         {
            # read the exit status from the terminating string
            # if no esit status found, then assume '0'
            if($string =~ m/^(\d+):STDOUT:(\d)/ ||
               $string =~ m/^(\d+):STDOUT:(-\d)/)
            {
               $asmcmdglobal_hash{'bgpid'} = $1;
               $asmcmdglobal_hash{'e'} = int($2);
            }
            $ret  = 0; # Marks the end of output.

            # Last line to be read from the pipe, hence close and remove it.
            asmcmdshare_closereadpipe();
            last;
         }
         # if string contains a request to reconnect to ASM.
         elsif($string =~ $asmcmdshare_reconnect_string)
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader ASMCMD Daemon lost " .
                                 "connection to ASM. Foreground process " .
                                 "(PID = $$) will restart the ASMCMD Daemon " .
                                 "to reconnect to ASM", 'y', 'n');

            # Clear the command list as we are going to execute the command
            # from the scratch after spawning the asmcmd dameon.
            undef(@asmcmdshare_commandlist);
            asmcmdshare_closereadpipe();
            asmcmdshare_closewritepipe();
            asmcmdshare_removereadpipe();
            die $asmcmdshare_reconnect_string;
         }
         elsif($string =~ $asmcmdshare_disablecp_string)
         {
            asmcmdshare_trace(3, "$asmcmdshare_logheader ASMCMD Background " .
                                 "process is unable to connect to ASM. " .
                                 "Retrying the command without connection " .
                                 "pooling.", 'y', 'n');

            $asmcmdglobal_hash{'nocp'} = 1;

            asmcmdshare_closereadpipe();
            asmcmdshare_closewritepipe();
            asmcmdshare_removereadpipe();

            $asmcmdglobal_hash{'ispooled'} = 0;
            $asmcmdglobal_hash{'bgpipe'} = '';
            $asmcmdglobal_hash{'fgpipe'} = '';

            die $asmcmdshare_disablecp_string;
         }
         else
         {
            $string =~ s/^(\d+):STDOUT://;
            print $string; # Print the line of output to the console
         }
      }
      # If marked with STDERR print to STDERR
      elsif($string =~ /^(\d+):STDERR:/)
      {
         $asmcmdglobal_hash{'bgpid'} = $1;
         $string =~ s/^(\d+):STDERR://;
         print STDERR $string;
         next;
      }
      # If marked with PROMPT, it means, the daemon is requesting more
      # user input, possibly a confirmation from the user for an action.
      elsif($string =~ /^(\d+):PROMPT:/)
      {
         $asmcmdglobal_hash{'bgpid'} = $1;
         chomp $string;
         $string =~ s/^(\d+):PROMPT://;
         print $string;
         my $userinput = <STDIN>;
         asmcmdshare_openwritepipe();
         asmcmdshare_printpipe("$$:STDIN:$userinput\n");
         asmcmdshare_closewritepipe();
         next;
      }
      elsif($string =~ /^!!!!![^!]*!!!!!/)
      {
         $string =~ s/^!!!!![^!]*!!!!!//;
      }
   };
   return $ret; # More output is expected, but pipe is empty for the time being.
}

########
# NAME
#   asmcmdshare_removefgpipe
#
# DESCRIPTION
#   Removes the foreground pipe file. It will only remove the pipe
#   corresponding to the current fg process ID.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#
########
sub asmcmdshare_removefgpipe
{
  my ($fgpipe) = $asmcmdglobal_hash{'fgpipe'};

  if (defined($fgpipe))
  {
    if (-e $fgpipe)
    {
      if (unlink($fgpipe))
      {
        asmcmdshare_trace(3, "$asmcmdshare_logheader Removed $fgpipe.",
                          'y', 'n');
      }
      else
      {
        asmcmdshare_trace(3, "$asmcmdshare_logheader Could not remove " .
                          "$fgpipe: $!", 'y', 'n');
      }
    }
    else
    {
      asmcmdshare_trace(3, "$asmcmdshare_logheader File to remove not found: " .
                           "$fgpipe.", 'y', 'n');
    }
  }
  $asmcmdglobal_hash{'fgpipe'} = undef;
  $asmcmdglobal_hash{'fgpid'} = undef;
}

########
# NAME
#   asmcmdshare_removebgpipe
#
# DESCRIPTION
#   Removes the background pipe file. It will only remove the pipe
#   corresponding to the current bg process ID.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#
########
sub asmcmdshare_removebgpipe
{
  my ($bgpipe) = $asmcmdglobal_hash{'bgpipe'};

  if (defined($bgpipe))
  {
    if (-e $bgpipe)
    {
      if (unlink($bgpipe))
      {
        asmcmdshare_trace(3, "$asmcmdshare_logheader Removed $bgpipe.",
                          'y', 'n');
      }
      else
      {
        asmcmdshare_trace(3, "$asmcmdshare_logheader Could not remove " .
                          "$bgpipe: $!", 'y', 'n');
      }
    }
    else
    {
      asmcmdshare_trace(3, "$asmcmdshare_logheader File to remove not found: " .
                           "$bgpipe.", 'y', 'n');
    }
  }
  $asmcmdglobal_hash{'bgpipe'} = undef;
  $asmcmdglobal_hash{'bgpid'} = undef;
}

########
# NAME
#   asmcmdshare_closereadpipe
#
# DESCRIPTION
#   This function closes the filehandle to the read pipe, if it is open.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#   None.
#
########
sub asmcmdshare_closereadpipe
{
   if(defined($asmcmdshare_readhandle) &&
      defined (fileno($asmcmdshare_readhandle)))
   {
      # Empty the read pipe
      while(<$asmcmdshare_readhandle>)
      {};
      close $asmcmdshare_readhandle;
      asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
                           "$$asmcmdshare_pipetoread.", 'y', 'n');
   }
}

########
# NAME
#   asmcmdshare_closewritepipe
#
# DESCRIPTION
#   This function closes the filehandle to the write pipe, if it is open.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#   None.
#
########
sub asmcmdshare_closewritepipe
{
  if(defined($asmcmdshare_writehandle))
  {
    my $fno = fileno ($asmcmdshare_writehandle);
    if (defined($fno))
    {
      close $asmcmdshare_writehandle;
      asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
                           "$$asmcmdshare_pipetowrite.", 'y', 'n');
    }
    else  # fileno is undefined.
    {
      asmcmdshare_trace(3, "$asmcmdshare_logheader NOT closing pipe",
                           'y', 'n');
    }
  }
}

########
# NAME
#   asmcmdshare_removereadpipe
#
# DESCRIPTION
#   Removes the read pipe if it exists.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#   Should only be called after closing the read handle.
#
########
sub asmcmdshare_removereadpipe
{
   if(-p $$asmcmdshare_pipetoread)
   {
      asmcmdshare_trace(3, "$asmcmdshare_logheader ".
                           "Removed pipe $$asmcmdshare_pipetoread.", 'y', 'n');
      unlink $$asmcmdshare_pipetoread;
   }
}

########
# NAME
#   asmcmdshare_openreadpipe
#
# DESCRIPTION
#   This function opens the file handle for the read pipe
#
#   Steps:
#   Close the read filehandle if it is open.
#   Waits for the pipe to appear.
#   Opens the read handle with the pipe.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#   Has an internal alarm, hence will through an exception if the
#   open pipe operation times out.
#
########
sub asmcmdshare_openreadpipe
{
   my $waittime = shift;
   $waittime = $asmcmdshare_openpipewait if(!defined $waittime);

   $asmcmdshare_process_status = "OPENREADPIPE"
                                    if $asmcmdshare_process_status eq "RUNNING";
   eval
   {
     asmcmdshare_chkpipeexst($$asmcmdshare_pipetoread);

     alarm $waittime;

     # lrg8504705: asmcmdshare_trace (i.e. daemon) forks a process to execute
     # clsecho(actually grand child) to write trace messages to file.
     # clsecho and asmcmdshare_trace communicate through pipe. clsecho informs
     # the daemon that IO is complete by writing to a pipe. If the asmcmd
     # daemon recieves a signal (SIGUSR1), while reading this status from pipe,
     # the perl ASSERTS resulting in asmcmd daemon going down.
     # There is a bug in solaris(sparc) where in while reading from pipe if it
     # receives a signal it asserts(i.e. after handling the signal and comming
     # back to read again). Hence disabling this trace. we should not add any
     # additonal tracing before asmcmdshare_readcommands in dameon.
     my $trc_enabled = 1;
     if (!($^O =~ /solaris/i && $Config{archname} =~ /sun4-solaris/i))
     {
       $trc_enabled = 0;
     }

     if ($trc_enabled)
     {
       asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
                         "$$asmcmdshare_pipetoread in read mode", 'y', 'n');
     }

     open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
                     or die "Could not open the pipe $$asmcmdshare_pipetoread";
     alarm 0;

     if ($trc_enabled)
     {
       asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
                       "pipe $$asmcmdshare_pipetoread in read mode", 'y', 'n');
     }
   };
   if($@)
   {
      asmcmdshare_alarm_handler();
   }
   $asmcmdshare_process_status = "RUNNING";
   return;
}

########
# NAME
#   asmcmdshare_openwritepipe
#
# DESCRIPTION
#   This function opens the file handle for the write pipe
#
#   Steps:
#   Close the write filehandle if it is open.
#   Waits for the pipe to appear.
#   Opens the write handle with the pipe.
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
#
# NOTES
#   Has an internal alarm, hence will through an exception if the
#   open pipe operation times out.
#
########
sub asmcmdshare_openwritepipe
{
   my $waittime = shift;
   $waittime = $asmcmdshare_openpipewait if(!defined $waittime);

   $asmcmdshare_process_status = "OPENWRITEPIPE"
                                    if $asmcmdshare_process_status eq "RUNNING";
   eval
   {
     asmcmdshare_chkpipeexst($$asmcmdshare_pipetowrite);

     alarm $asmcmdshare_openpipewait;

     asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
                       "$$asmcmdshare_pipetowrite", 'y', 'n');

     open($asmcmdshare_writehandle, "> $$asmcmdshare_pipetowrite")
                    or die "Could not open the pipe $$asmcmdshare_pipetowrite";
     alarm 0;
     asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
                          "pipe $$asmcmdshare_pipetowrite", 'y', 'n');
   };
   if($@)
   {
      asmcmdshare_alarm_handler();
   }
   $asmcmdshare_process_status = "RUNNING";
   return;
}

########
# NAME
#   asmcmdshare_chkpipeexst
# DESCRIPTION
#   This function checks if a pipe is available or not.
#
# RETURNS
#   None. If the pipe does not available, it raises ALRM signal to 
#   trigger a timeout on open/write/read on pipe.
########
sub asmcmdshare_chkpipeexst
{
  my ($pipe) = @_;

  if (! -p $pipe) 
  {
    asmcmdshare_trace(3, "$asmcmdshare_logheader ERROR: pipe $pipe not found.",
                      'y', 'n');
    # raise ALRM signal to trigger daemon failure handle.
    kill 'ALRM', $$;
  }
  return;
}

########
# NAME
#   asmcmdshare_readstd
# DESCRIPTION
#   This function reads one line from the STDIN.
#   When called in a non-connection pooled session, it reads from STDIN.
#   When caleld by the asmcmd daemon in a connection pooled session, it reads
#   the pipe to the daemon (usually used for reading commands from foreground
#   processes) for the user input (passed from the foreground process).
#
# PARAMETERS
#   None.
#
# RETURNS
#   string (OUT) - The string that was read from the STDIN.
########
sub asmcmdshare_readstdin
{
   my $string;
   if($asmcmdglobal_hash{'isdaemon'})
   {
      $asmcmdshare_process_status = "USERINPUT";
      $string = asmcmdshare_readcommands();
      $asmcmdshare_process_status = "RUNNING";
      return $string;
   }
   elsif($asmcmdglobal_hash{'ispooled'})
   {
      $string = <CPSTDIN>;
   }
   else
   {
      $string = <STDIN>;
   }
   return $string;
}

########
# NAME
#   asmcmdshare_backup_stdio
# DESCRIPTION
#   This function backs up the STD I/O filehandles
#
# PARAMETERS
#   None.
#
# RETURNS
#   None.
########
sub asmcmdshare_backup_stdio
{
   open(CPSTDIN , ">&STDIN" );
}

#########
# NAME
#   asmcmdshare_execute_tool
#
# DESCRIPTION
#   To execute given tool with parameters and return the output
#
# PARAMETERS
#   $cmd      (IN) - command to execute
#   $winext   (IN) - suffix in Windows platform (EXE/BAT/...)
#   $dirs     (IN) - list of directories to search for.
#   $param    (IN) - parameter for the command.
#   $redirect (IN) - OPTIONAL: redirect stderr to stdout (default: 0)
#   $skiptrace(IN) - OPTIONAL: default: 0, If set to 1, then skip tracing.
#
# RETURNS
#   cmd output (which by default is only stdout) or execution error
###########
sub asmcmdshare_execute_tool
{
  my ($cmd, $wext, $param, $dirs) = @_ ;
  my $redirect = defined $_[4] ? $_[4] : 0;
  my $skiptrace = defined $_[5] ? $_[5] : 0;
  my $exec;
  my $dir;
  my @result;
  my $rc;
  my $sig;
  my $cmdfound = 0;
  my @eargs;

  $asmcmdglobal_hash{'utilsucc'} = "false" ;

  foreach $dir (@{$dirs})
  {
    $exec = $dir.$cmd;
    if ( $^O =~  /win/i)
    {
      $exec .= $wext;
    }

    if (-x $exec)
    {
      $cmdfound = 1;
      $exec .= " $param";

      # The error output of some tools actually go to stderr, but perl
      # backticks only capture stdout. In some cases, we want to look for
      # specific error messages in a tool's output. To do so, we redirect
      # stderr to stdout.
      if ($redirect)
      {
        $exec .= " 2>&1";
      }

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

      # 27688692: skip tracing when $skiptrace is set.
      if ($skiptrace == 0)
      {
        asmcmdshare_trace(3,"NOTE: Executing $exec..", 'n', 'n');
      }

      # execute the command
      eval
      {
        # 20434627: if SIG{CHLD} is IGNORED, perl always set $? to -1 and $! to
        # (no child process). It might be because when signal IGNORED, it seems 
        # somehow the child status is lost.
        local $SIG{CHLD} = 'DEFAULT';
        @result = `$exec`;
      };

      if ($@ eq '')
      {
        # If the tool we're executing, e.g. segfaults, the return code will be
        # 0, but we shouldn't consider the execution to be a success. So we
        # look at the signal id as well.
        $rc  = $CHILD_ERROR >> 8;             # get return code (high 8 bits) #
        $sig = $CHILD_ERROR & 0x7F;              # get signal id (low 7 bits) #
        $asmcmdglobal_hash{'utilsucc'} = "true" if (!$CHILD_ERROR);
      }
      else
      {
        @result = ($@);                 # execution error #
      }
      last;
    }
  }

  if (!$cmdfound)
  {
    # Something wrong. Executable not found.
    @eargs = (uc($cmd), join(' ', @{$dirs}));
    asmcmdshare_signal_exception(9529, \@eargs);
  }

  if ($asmcmdglobal_hash{'utilsucc'} ne "true")
  {
    asmcmdshare_trace(3, "NOTE: unable to execute $cmd... " .
                      "ret:$rc sig:$sig err:$!", 'y', 'n');
  }
  return @result;
}

########
# NAME
#   asmcmdshare_get_instance_name
#
# DESCRIPTION
#   This function retrieves the instance name of the ASM instance to which the
#   ASMCMD is connected to.
#
# PARAMETERS
#   dbh         (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   The instance name of the ASM instance.
########
sub asmcmdshare_get_instance_name
{
  my ($dbh) = shift;                                      # Database handle. #
  my ($sth);                                         # SQL statement handle. #
  my ($inst_name);                                      # ASM instance name. #
  my ($query);                                        # SQL query statement. #
  my ($row);                                         # Row of query results. #

  # SQL for getting the ASM instance software version.
  $query = 'select instance_name 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.
  $inst_name = $row->{'INSTANCE_NAME'};

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

  return $inst_name;
}

##############
# NAME
#   asmcmdshare_filter_invisible_cmds
#
# DESCRIPTION
#   To filter invisble coomands. Only visible command Names are printed
#
# PARAMETER
#   cmdlist   - hash of command names from each module
#
# RETURNS
#   -None-
##############
sub asmcmdshare_filter_invisible_cmds
{
  my %cmdlist = @_;

  foreach my $cmd (keys %cmdlist)
  {
    # exclude hidden commands
    if(!asmcmdshare_is_cmd_visible($cmd))
    {
      delete($cmdlist{$cmd});
    }
  }

  return asmcmdshare_print_cmds(sort(keys %cmdlist));
}

############### MASTER-SLAVE Support #######################################

##############
# NAME
#   asmcmdshare_setstate
#
# DESCRIPTION
#   Changes the state of the current process
#
# PARAMETER
#   newstate  - The process state might be one of the following strings:
#                 * 'WAITING'
#                 * 'RUNNNING'
#                 * 'CONNECTED'
#                 * 'UNCONNECTED'
#
# RETURNS
#   -None-
##############
sub asmcmdshare_setstate
{
  my ($newstate) = shift || '';

  # Avoid undefined state
  $asmcmdshare_state = "RUNNING" unless defined($asmcmdshare_state);
  my ($logmsg)   = "[WARNING]: Process state: '" . $asmcmdshare_state .
                   "' -> '" . $newstate . "'";
  asmcmdshare_trace( 1, $logmsg, 'y', 'n');
  $asmcmdshare_state = $newstate;
}

##############
# NAME
#   asmcmdshare_wait
#
# DESCRIPTION
#   Changes the state of the current process
#
# PARAMETER
#   newstate  - The process state might be one of the following strings:
#                 * 'WAITING'
#                 * 'RUNNNING'
#                 * 'CONNECTED'
#                 * 'UNCONNECTED'
#
# RETURNS
#   -None-
##############
sub asmcmdshare_wait
{
  my ($prevstate) = $asmcmdshare_state;
  asmcmdshare_setstate "WAITING";
  while ($asmcmdshare_wait_flag == 1)
  {
    sleep (1);
  }
  # reset the asmcmdshare_wait flag for the next wait
  $asmcmdshare_wait_flag = 1;
  asmcmdshare_setstate $prevstate;
}

##############
# NAME
#   asmcmdshare_waitslaves
#
# DESCRIPTION
#
#   In a master-slave context, this routine is called by the master process to
#   wait until all its slaves finished their tasks. This allows the master to
#   perform all the actions it needs before its slaves die.
#
# PARAMETER
#   NONE
#
# RETURNS
#   NONE
##############
sub asmcmdshare_waitslaves {

  if (defined($asmcmdglobal_hash{'ismaster'}))
  {
    asmcmdshare_trace(1, "Waiting for slaves to finish", 'y', 'n');
    while ($asmcmdshare_missing > 0)
    {
      sleep (1);
    }
    asmcmdshare_trace(1, "All slaves have finished", 'y', 'n');
  }
  else
  {
    asmcmdshare_trace(1,
      "[ERROR]: I am not a master process. Nothing to wait", 'n', 'n');
  }
}

##############
# NAME
#   asmcmdshare_packmsg
#
# DESCRIPTION
#
#   This function allows a process build a well formed message string before
#   sending it through a pipe.
#
#   This message format allows the receiver process to know what kind of data
#   the message contains before reading its content.
#
# PARAMETER
#
#   data (IN)  - Could be a scalar value or a reference to a data structure
#
# RETURNS
#   msg   - String that contains a serialized form of input data.
#
# NOTES
#
#   Is very important for the receiver process to know when the message is a
#   data scructure or a scalar.
##############
sub asmcmdshare_packmsg {
  my ($data) = shift;
  my ($type) = ref $data;
  my ($buf);
  my ($msg);

  unless(defined $data) {
    asmcmdshare_trace (1, "[FAIL]: Message undefined", 'y', 'n');
    return undef;
  }

  if ($type =~ m/ARRAY|HASH|SCALAR/)
  {
    asmcmdshare_trace(3,
      "Message type: reference to $type. packing message...", 'y', 'n');

    ## Create a string representation of the structure referenced by $data
    $buf = Dumper $data;
    $buf =~ s/\$VAR.*=\s//g;
    $buf =~ s/\n/ /g;
    $buf =~ s/\s+/ /g;
    $buf =~ s/;\s*$//g;

    $msg = "!!!!REFERENCE:#$buf#!!!!\n";
  }
  elsif( $type eq '' )
  {
    asmcmdshare_trace(3,
      "Message type: SCALAR. packing message...", 'y', 'n');

    $msg = "!!!!SCALAR:#$data#!!!!\n";
  }
  else
  {
    asmcmdshare_trace(1, "[ERROR]: Bad message -- $data\n", 'y'. 'n');
    $msg = undef;
  }

  return $msg;
}

##############
# NAME
#   asmcmdshare_unpackmsg
#
# DESCRIPTION
#
# PARAMETER
#   string (IN) String to convert into data.
#
# RETURNS
#   -None-
##############
sub asmcmdshare_unpackmsg
{
  my ($string) = shift;
  my ($ref);

  $string =~ m/^!!!!(.*)!!!!$/;
  $string =  $1;

  if (defined($string))
  {
    if ($string =~ m/^REFERENCE:#(.*)#$/)
    {
      $ref = eval( $1 ); # TODO: Validate first, this is insecure!!!
    }
    elsif ($string =~ m/^SCALAR:#(.*)#$/)
    {
      $ref = $1;
    }
    else
    {
      asmcmdshare_trace(1, "[ERROR]: Invalid message [$string]", 'n', 'n');
      $ref = undef;
    }
  }
  else
  {
    asmcmdshare_trace(1, "[ERROR]: undefined parameter for function ".
                         "asmcmdshare_unpackmsg", 'n', 'n');
    $ref = undef;
  }

  asmcmdshare_trace(3, "Message unpacked: [$ref]", 'y', 'n');
  return $ref;
}

########
# NAME
#   asmcmdshare_logdata
#
# DESCRIPTION
#
#   This function allows to print data structures on log and trace files
#
# PARAMETERS
#   level     (IN)  - See level parameter of asmcmdshare_trace function.
#   label     (IN)  - String printed as prefix of all the trace lines generated
#                     by this function.
#   data      (IN)  - Reference to the data to be traced.
#   timestamp (IN)  - See timestamp parameter of asmcmdshare_trace function.
#   term      (IN)  - See term parameter of asmcmdshare_trace function.
#
# RETURNS
#   NONE
#
########
sub asmcmdshare_logdata
{
  my ($level, $label, $data, $timestamp, $term) = @_;

  my ($buf, @lines);

  $buf = Dumper $data;
  $buf =~ s/\$.*=//;
  $buf =~ s/\n/-----/g;
  $buf =~ s/\s+/ /g;
  $buf =~ s/-----/\n/g;
  @lines = split( /\n/, $buf);

  foreach (@lines) {
    asmcmdshare_trace($level, "[$label]: $_", $timestamp, $term);
  }

}

########
# NAME
#   asmcmdshare_signal
#
# DESCRIPTION
#
#
# PARAMETERS
#
#
# RETURNS
#
########
sub asmcmdshare_signal
{
  my ($pid)     = shift;
  my ($success) = 1;

  eval {
    asmcmdshare_trace(5, "Sending signal USR1 from proc[$$] to " .
      "proc[$pid].", 'y','n');
    kill SIGUSR1, $pid;
  };
  if ($@) {
    # TODO: Use asmcmdshare_error with an appropriate error message instead of
    #       asmcmdshare_trace
    asmcmdshare_trace(1, "Could not send the signal to process [$pid]: $!",
      'y','n');
    $success = 0;
  }

  return $success;
}

##############
# NAME
#   asmcmdshare_killslave
#
# DESCRIPTION
#
#   In case of error, the master process can use this function to send the KILL
#   signal to the given slave
#
# PARAMETER
#   ctx       - (IN)  Process context, contains the PID of the process to kill
#
# NOTES
#
# RETURNS
#   -None-
##############
sub asmcmdshare_killslave
{
  my ($ctx) = shift;
  my ($id)  = $ctx -> {'id'};
  my ($pid) = $ctx -> {'pid'};

  eval {
    # Check if the process can be killed before try to kill it.
    kill(0, $pid) && kill(9, $pid);
  };
  if ($@) {
    asmcmdshare_trace(1, "[ERROR] Could not kill slave [$id]", 'y', 'n');
  }
}

##############
# NAME
#   asmcmdshare_mkfifo
#
# DESCRIPTION
#   Function used to create a named pipe
#
# PARAMETER
#   fifo_name - (IN)  File name for the new named pipe.
#
# NOTES
#
# RETURNS
#   1 on success, 0 otherwise.
##############
sub asmcmdshare_mkfifo
{
  my ($fifo_name) = shift;

  my ($success) = 0;

  eval
  {
    if (-p $fifo_name)
    {
      asmcmdshare_trace(4, "Pipe '$fifo_name' already exists", 'y', 'n');
      unlink $fifo_name;
    }
    elsif (-e $fifo_name)
    {
      asmcmdshare_trace(2,
        "[WARNING]: '$fifo_name' is not a pipe file",'y','n');
      unlink $fifo_name;
    }

    asmcmdshare_trace(3, "Creating a new pipe on '$fifo_name'.", 'y', 'n');
    mkfifo $fifo_name, 0664;
    $success = 1;

  };
  if ($@) {
    # handle failure...
    asmcmdshare_trace(1, "[ERROR]: Could not create the pipe [$fifo_name]",
      'y','n');
    $success = 0;
  }

  return $success;
}

##############
# NAME
#   asmcmdshare_connect
#
# DESCRIPTION
#
#   This function is called by master and slaves processes to avoid dead locks
#   when they create the communication channel.
#
# PARAMETER
#   ctx       - (IN)  Process context
#
# NOTES
#
# RETURNS
#   1 on success, 0 otherwise.
##############
sub asmcmdshare_connect
{
  my ($ctx) = shift;
  my ($success) = 0;
  my ($reader_pipe, $writer_pipe);
  my ($reader, $writer);
  my ($pid, $id);
  my ($waittime) = $asmcmdshare_openpipewait || 5; # Wait for 5s as default

  asmcmdshare_trace(3, "Context", 'y', 'n');
  asmcmdshare_logdata(3, "CONTEXT", $ctx, 'y', 'n');

  $reader_pipe = $ctx -> {'pipe_to_read'};
  $writer_pipe = $ctx -> {'pipe_to_write'};

  if (defined($asmcmdglobal_hash{'isslave'})) # Am I a slave process?
  {
    eval
    {
      asmcmdshare_trace(3, "Ready to connect, waiting for master", 'y', 'n');
      asmcmdshare_trace(3, "Waiting for pipe [$reader_pipe]" , 'y', 'n');

      asmcmdshare_setstate('OPENREADPIPE');
      alarm $waittime;
      while(!-p $reader_pipe)
      { # Wait until the pipe is created
        sleep(1);
      }
      asmcmdshare_trace(3, "Pipe [$reader_pipe] was found", 'y', 'n');
      open $reader, "< $reader_pipe";
      alarm 0;

      asmcmdshare_setstate('UNCONNECTED');
      $ctx -> {'reader_handler'} = $reader;
      asmcmdshare_trace(3, "[SUCCESS]: Process connected to receive messages ".
        "from master.", 'y', 'n');
      asmcmdshare_trace(3, "Creating pipe for writing [$writer_pipe]", 'y','n');

      asmcmdshare_setstate('OPENWRITEPIPE');
      asmcmdshare_mkfifo $writer_pipe;
      alarm $waittime;
      open $writer, "> $writer_pipe";
      alarm 0;
      asmcmdshare_setstate('UNCONNECTED');

      $ctx -> {'writer_handler'} = $writer;
      $success = 1;
    };
    if ($@)
    {
      asmcmdshare_trace(1,
        "[ERROR]: Could not connect slave process with master.", 'y', 'n');
      $success = 0;
    }

  }
  elsif(defined($asmcmdglobal_hash{'ismaster'}))# No, so I am the master process
  {
    $pid = $ctx -> {'pid'};
    $id  = $ctx -> {'id'};

    eval
    {
      asmcmdshare_trace(4, "Ready to connect to slave [$id]", 'y', 'n');
      asmcmdshare_mkfifo $writer_pipe;

      asmcmdshare_setstate('OPENWRITEPIPE');
      alarm $waittime;
      open $writer, "> $writer_pipe";
      alarm 0;
      asmcmdshare_setstate('UNCONNECTED');

      $ctx -> {'writer_handler'} = $writer;
      asmcmdshare_trace(4, "[SUCCESS]: Process connected to send messages to " .
        "slave [$id]", 'y', 'n');
      asmcmdshare_trace(4, "Ready to connect WITH slave $id for receiving " .
        "messages", 'y', 'n');
      asmcmdshare_setstate('OPENREADPIPE');

      alarm $waittime;
      while(!-p $reader_pipe)
      { # Wait until the pipe is created
        sleep(1);
      }
      asmcmdshare_trace(4, "Pipe [$reader_pipe] was found", 'y', 'n');
      open $reader, "< $reader_pipe";
      alarm 0;

      asmcmdshare_setstate('UNCONNECTED');
      $ctx -> {'reader_handler'} = $reader;
      $success = 1;
      asmcmdshare_trace(1, "[SUCCESS]: Process connected to receive messages ".
        "from slave $id.", 'y', 'n');
    };
    if ($@) {
      asmcmdshare_trace(1,
        "Could not connect master process with slave [$id]", 'y', 'n');
      $success = 0;
    }

  }

  asmcmdshare_trace(1, "Status: $success", 'y', 'n');

  asmcmdshare_setstate ('CONNECTED') if ($success == 1);

  return $success;
}

########
# NAME
#   asmcmdshare_sendmsg
#
# DESCRIPTION
#   This function is used to send a message from one process to another.
#
# PARAMETERS
#   ctx     - (IN) Process context, it contains all the information regarding
#                  the target process and the communication channel.
#   data    - (IN) Could be a scalar or a reference to any data structure. This
#                  is the message to send to the other process.
#
# RETURNS
#   1 on success, 0 otherwise.
#
# NOTES
#
#   See the function asmcmdshare_fork_slaves for more information about the
#   context structure ctx.
########
sub asmcmdshare_sendmsg
{
  my ($ctx, $data)    = @_;
  my ($pipe_to_write) = $ctx -> {'pipe_to_write'};
  my ($writer)        = $ctx -> {'writer_handler'};
  my ($success)       = 0;
  my ($fileno);
  my ($type);
  my ($msg);

  asmcmdshare_trace(3, "Inside asmcmdshare_sendmsg function", 'y', 'n');
  $fileno = fileno($writer);

  if(!defined($fileno))
  {
    asmcmdshare_trace(3, "Opening pipe for writing [$pipe_to_write]",
      'y', 'n');
    open($writer, "> $pipe_to_write")
      or die "[ERROR]: Could not open the pipe $pipe_to_write";
    $ctx -> {'writer_handler'} = $writer;
  }

  # Check the type of $data, only reference types are considered safe. Scalars,
  # such as numbers, strings or references.
  # Packing the message, $msg will be undef in case of error.
  $msg = asmcmdshare_packmsg $data;

  if (defined $msg)
  {
    asmcmdshare_trace(3, "Sending message...", 'y', 'n');
    print $writer $msg;
    close $writer;
    asmcmdshare_trace(3,
      "Message sent from PID($$) to PID(". $ctx -> {'pid'} .")", 'y', 'n');
    $success = 1;
  }
  else
  {
    asmcmdshare_trace(1, "Could not send message.", 'y', 'n');
    asmcmdshare_logdata(1, "MESSAGE", $data, 'n', 'n' );
    $success = 0;
  }

  asmcmdshare_trace(3, "Leaving asmcmdshare_sendmsg function", 'y', 'n');
  return $success;
}

##############
# NAME
#   asmcmdshare_recvmsg
#
# DESCRIPTION
#   This function is used to receive a message from other process.
#
# PARAMETERS
#   ctx     - (IN) Process context, it contains all the information regarding
#                  the source process and the communication channel.
#
# NOTES
#   See the function asmcmdshare_fork_slaves for more information about the
#   context structure ctx.
#
# RETURNS
#   msg       -       If the message is a scalar variable, msg holds the message
#                     itself, if the message is a data structure, msg is a
#                     reference to that data strcuture. If the message reception
#                     fails, undef value is returned.
##############
sub asmcmdshare_recvmsg
{
  my ($ctx) = shift;

  my ($reader) = $ctx -> {'reader_handler'};
  my ($pipe_to_read) = $ctx -> {'pipe_to_read'};
  my ($success) = 0;
  my ($string, $msg);
  my ($fileno);

  asmcmdshare_trace(5, "Inside recvmsg function", 'y', 'n');

  $fileno = fileno($reader);
  if(!defined $fileno)
  {
    asmcmdshare_trace(1, "Pipe is not opened for reading [$pipe_to_read]",
      'y', 'n');
    open($reader, "< $pipe_to_read")
             or die "PIPE: Could not open the pipe $pipe_to_read";
    $ctx -> {'reader_handler'} = $reader;
  }

  asmcmdshare_trace(3, "Reading message [$pipe_to_read]", 'y', 'n');

  while(!$success)
  {
    $string = <$reader>;
    $success = 1 if(defined $string);
  };

  asmcmdshare_trace(3, "Unpacking message", 'y', 'n');
  $msg = asmcmdshare_unpackmsg $string;

  if (defined $msg)
  {
    asmcmdshare_trace(3,
      "Message received from PID(". $ctx -> {'pid'} . ") successfully.",
      'y', 'n');
  }
  else
  {
    asmcmdshare_trace(1,
      "[FAIL]: Could not receive the message from PID(". $ctx -> {'pid'} . ")",
      'y', 'n');
    asmcmdshare_trace(1, "$string", 'y', 'n');
  }

  asmcmdshare_trace(3, "Leaving recvmsg function", 'n', 'n');
  return $msg;
}

##############
# NAME
#   asmcmdshare_slave_main
#
# DESCRIPTION
#
#   This is the main function for each slave process it's in charge of:
#     - Setup the signal manager
#     - Connect with the master process and receive the job parameters from it
#     - Run the specified callback
#
#
# PARAMETER
#   ctx       - (IN)  Process context
#
# NOTES
#   See the function asmcmdshare_fork_slaves for more information about the
#   context structure ctx.
#
# RETURNS
#   -None-
##############
sub asmcmdshare_slave_main
{
  my ($ctx) = shift;
  my ($string);
  my ($i);
  my ($continue) = 1;
  my ($id) = $ctx -> {'id'};
  my ($success) = 0;
  my ($function);
  my ($msg);
  my (@arr);
  my ($res);

  # Connecting signal handler
# $SIG{'USR1'} = \&asmcmdshare_signal_handler;
# $SIG{'USR2'} = \&asmcmdshare_signal_handler;
# $SIG{'INT'}  = \&asmcmdshare_signal_handler;
# $SIG{'QUIT'} = \&asmcmdshare_signal_handler;
# $SIG{ALRM} = sub { die "ALARM" };

  $0 = "asmcmd_slave_" . $$; # change the process name

  asmcmdshare_trace(3,
    "Starting ASMCMD slave process [$id] (PID = $$)", 'n', 'n');
  asmcmdshare_trace(3, "Master process PID: " . $ctx -> {'pid'}, 'n', 'n');

  asmcmdshare_setstate("UNCONNECTED");
  asmcmdshare_trace(5, "Connecting...", 'y', 'n');

  $success = asmcmdshare_connect($ctx);

  if ($success)
  {
    $msg = asmcmdshare_recvmsg($ctx);

    asmcmdshare_setstate("RUNNING");

    #Execute the callback
    $function = $ctx -> { 'callback' };
    $res = $function -> ( $ctx, $msg );

    asmcmdshare_trace(3, "DONE", 'y', 'n');
  }
  else
  {
    asmcmdshare_trace(1,
      "[ERROR]: Could not connect with master proccess.", 'y', 'n');
  }
}

########
# NAME
#   asmcmdshare_fork
#
# DESCRIPTION
#   This routine forks the present undivided process, creating a child process
#
# PARAMETERS
#   None.
#
# RETURNS
#   $pid - It returns the pid of the child to the parent and 0 to the child..
#
# NOTES
########
sub asmcmdshare_fork
{
  my ($pid);

  defined($pid = fork) || die "Could not fork: $!";
  return $pid if $pid;

  (setsid() != -1)  || die "Could not start a new session";

  return $pid;
}

##############
# NAME
#   asmcmdshare_fork_slaves
#
# DESCRIPTION
#   This routine forks the present undivided process, creating a set of slave
#   processes
#
#
# PARAMETER
#   slaves    - (OUT) List of slave descriptors
#   num       - (IN)  Amount of slaves to fork.
#   callbacks - (IN)  List of callbacks, one for each slave.
#
# NOTES
#
#   Each slave is described by a context hash, which contains information about
#   the communication channel. The master process holds an array of contexts
#   with as many elements as slaves had been created. Each slave holds its own
#   context structure.
#
#   CONTEXT STRUCTURE HELD BY MASTER
#
#     ctx :
#       id             - Slave identifier.
#       pid            - Target process PID.
#       pipe_to_write  - Output named pipe file name.
#       writer_handler - Output named pipe file descriptor.
#       pipe_to_read   - Input named pipe file name.
#       reader_handler - Input named pipe file descriptor.
#       callback       - Reference to the callback function (only for slave
#                        processes).
#
#
# RETURNS
#   This function fills the list slaves with one slave context for each process
#   created.
##############
sub asmcmdshare_fork_slaves
{
  my ($slaves, $num, $callbacks) = @_;
  my ($pid);
  my ($ctx);
  my ($id);
  my ($master_pid) = $$;

  # #17786672: FIFO file spec
  #  From master to slave - <TMP>/asmcmd_pipes/<MASTER_PID>.<SLAVE_PID>
  #  From slave to master - <TMP>/asmcmd_pipes/<SLAVE_PID>.<MASTER_PID>
  $asmcmdshare_pipehome = "$asmcmdglobal_hash{'tempdir'}/asmcmd_pipes";
  unless (-d $asmcmdshare_pipehome)
  {
    mkdir $asmcmdshare_pipehome, 0775;
  }
  else
  {
    my ($msg) = "Could not write $asmcmdshare_pipehome directory";
    die $msg unless (-w $asmcmdshare_pipehome);
  }


  for( $id = 0; $id < $num; $id++ )
  {
    $pid = asmcmdshare_fork;

    if ($pid)
    {
      # Report where is the slave trace file
      my $slave_trace = "$asmcmdglobal_trace_path/trace/asmcmd_slave_$pid.trc";
      asmcmdshare_trace(3, "Starting slave process $id: asmcmd_slave_$pid ".
                           "(PID = $pid)\nTrace file: $slave_trace", 'y','n');

      $ctx = {
        'id'             => $id,
        'pid'            => $pid,
        'pipe_to_write'  => "$asmcmdshare_pipehome/$master_pid.$pid",
        'pipe_to_read'   => "$asmcmdshare_pipehome/$pid.$master_pid",
        'writer_handler' => undef,
        'reader_handler' => undef
      };
      $slaves -> [$id] = $ctx;

      asmcmdshare_trace(5, "Context for slave $id:", 'n', 'n');
      asmcmdshare_logdata(5, "CTX($id)", $ctx, 'n', 'n');
    }
    else
    {
      $ctx = {
        'id'             => $id,
        'pid'            => $master_pid,
        'callback'       => $callbacks -> [$id],
        'pipe_to_write'  => "$asmcmdshare_pipehome/$$.$master_pid",
        'pipe_to_read'   => "$asmcmdshare_pipehome/$master_pid.$$",
        'writer_handler' => undef,
        'reader_handler' => undef
      };

      $asmcmdglobal_hash{'isslave'} = 1;
      $asmcmdglobal_hash{'ismaster'} = undef;
      $SIG{'USR1'} = \&asmcmdshare_signal_handler;
      $SIG{'USR2'} = \&asmcmdshare_signal_handler;
      $SIG{'INT'}  = \&asmcmdshare_signal_handler;
      $SIG{'QUIT'} = \&asmcmdshare_signal_handler;
      $SIG{ALRM} = sub { die "ALARM" };
      asmcmdshare_slave_main $ctx;
      exit 0;
    }
  }

  $asmcmdshare_missing = $num;
  $SIG{'USR1'} = \&asmcmdshare_signal_handler;
  $SIG{'USR2'} = \&asmcmdshare_signal_handler;
  $SIG{'INT'}  = \&asmcmdshare_signal_handler;
  $SIG{ALRM} = sub { die "ALARM" };
  $asmcmdglobal_hash{'isslave'} = undef;
  $asmcmdglobal_hash{'ismaster'} = 1;
  asmcmdshare_setstate('UNCONNECTED');

  return 0;
}

########
# NAME
#   asmcmdshare_check_reqd_priv
#
# DESCRIPTION
#   This routine checks whether the current command can run in the privilege
#   specified.
#
# PARAMETERS
#   -NONE-
#
# RETURNS
#   1  - if the command can be executed.
#   0  - if the command can NOT be executed.
#
# NOTES
#   All commands can be executed in SYSASM privilege.  Not all commands can be
#   executed in SYSDBA privilege.  Commands which can be executed in SYSDBA
#   privilege has an attribute "priv" set to "sysdba" in asmcommand.xml
#
########
sub asmcmdshare_check_reqd_priv
{
  my ($cmd)    = $asmcmdglobal_hash{'cmd'};
  my ($contyp) = $asmcmdglobal_hash{'contyp'};
  my ($ret)    = 0 ;

  # check to see if the given command can run as SYSASM, for SYSASM connections
#  if ((($contyp eq "sysasm") && (defined($hPrivSysAsm{$cmd}))))
#  {
#    $ret = 1;
#  }
#  elsif (($contyp eq "sysdba") && (defined($hPrivSysDba{$cmd})))
#  {
#    $ret = 1;
#  }

  if ( (($contyp eq "sysasm") && (defined($hPrivSysAsm{$cmd}))) ||
       (($contyp eq "sysdba") && (defined($hPrivSysDba{$cmd}))) )
  {
    $ret = 1;
  }
  return $ret;
}

########
# NAME
#   asmcmddisk_init_disk_attr
#
# DESCRIPTION
#   Declares and initializes a disk fields hash.
#
# PARAMETERS
#   <none>
#  
# RETURNS
#   An initialized disk fields hash.
#
########
sub asmcmdshare_init_disk_attr
{
  my (%dsk_info);

  # Initialize elements to null strings so that they are defined and
  # don't trigger Perl warnings if the fixed view does not return
  # any contents for a particular column.
  $dsk_info{'inst_id'} = '';
  $dsk_info{'group_number'} = '';
  $dsk_info{'disk_number'} = '';
  $dsk_info{'incarnation'} = '';
  $dsk_info{'mount_status'} = '';
  $dsk_info{'header_status'} = '';
  $dsk_info{'mode_status'} = '';
  $dsk_info{'state'} = '';
  $dsk_info{'redundancy'} = '';
  $dsk_info{'library'} = '';
  $dsk_info{'os_mb'} = '';
  $dsk_info{'total_mb'} = '';
  $dsk_info{'free_mb'} = '';
  $dsk_info{'name'} = '';
  $dsk_info{'failgroup'} = '';
  $dsk_info{'site_name'} = '';
  $dsk_info{'site_guid'} = '';
  $dsk_info{'site_status'} = '';
  $dsk_info{'label'} = '';
  $dsk_info{'fgroup_label'} = '';
  $dsk_info{'site_label'} = '';
  $dsk_info{'path'} = '';
  $dsk_info{'udid'} = '';
  $dsk_info{'product'} = '';
  $dsk_info{'create_date'} = '';
  $dsk_info{'mount_date'} = '';
  $dsk_info{'repair_timer'} = '';
  $dsk_info{'reads'} = '';
  $dsk_info{'writes'} = '';
  $dsk_info{'read_errs'} = '';
  $dsk_info{'write_errs'} = '';
  $dsk_info{'read_time'} = '';
  $dsk_info{'write_time'} = '';
  $dsk_info{'bytes_read'} = '';
  $dsk_info{'bytes_written'} = '';
  $dsk_info{'voting_file'} = '';
  $dsk_info{'group_name'} = '';

  return %dsk_info;
}

########
# NAME
#   asmcmdshare_get_dsk
#
# DESCRIPTION
#   This function queries the (g)v$asm_disk(_stat) tables to retrieve
#   disk information based on user-specified query criteria.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   group  (IN) - optional: disk group number to limit results by group.
#   disk_pattern (IN) - optional: search pattern to limit results.
#   dnum   (IN) - optional: disk number to limit results.
#   discovery (IN) - 1 queries from non-stat view; 0 queries from _stat view.
#   global (IN) - 1 queries from the gv$ global view, 0 from v$ local view.
#   missing(IN) - 1 shows missing disks; 0 does not show them.
#   found_hash(OUT) - hash for missing disks. Required if missing = 1.
#
# RETURNS
#   An array of hashes references.  Each hash reference represents one
#   row of results.  Each element in the hash represents the value
#   for a particular column of the view.
#
# NOTES
########
sub asmcmdshare_get_dsk
{
  my ($dbh, $group, $disk_pattern, $dnum, $discovery, $global, $missing,
      $found_hash) = @_;
  my ($sth, $row);
  my (@dsk_list);           # The return array of hashes; see RETURNS above. #
  my ($view);
  my (@what , @from, @where, @order, @binds);

  # Change all allow wild card characters to '%'.
  $disk_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g
    if (defined ($disk_pattern));

  # 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 ($missing)         # for missing disks we must use gv$asm_disk
  {                     # and exclude member disks 
    push (@from, 'gv$asm_disk');
    push (@where, "(gv\$asm_disk.header_status = 'CANDIDATE' 
                    or gv\$asm_disk.header_status = 'PROVISIONED'
                    or gv\$asm_disk.header_status = 'FORMER')");
  }
  elsif ($discovery && $global)
  {
    push (@from, 'gv$asm_disk');
  }
  elsif ($discovery && !$global)
  {
    push(@from, 'v$asm_disk');
  }
  elsif (!$discovery && $global)
  {
    push (@from, 'gv$asm_disk_stat');
  }
  else
  {
    push (@from, 'v$asm_disk_stat');
  }
 
  if (defined($group))
  {
    push (@where, 'group_number = ?');
    push (@binds, [$group, SQL_INTEGER]);
  }  

  if (defined($dnum))
  {
    push (@where, 'disk_number = ?');
    push (@binds, [$dnum, SQL_INTEGER]);
  }

  if (defined($disk_pattern))
  {
    push (@where, 'path like ?');
    push (@binds, [$disk_pattern, SQL_VARCHAR]);
  }

  push(@what, '*');            # select all rows

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

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

    # Initialize elements to null strings so that they are defined and
    # don't trigger Perl warnings if the fixed view does not return
    # any contents for a particular column.
    %dsk_info = asmcmdshare_init_disk_attr();

    # Assign only if defined.
    $dsk_info{'inst_id'} = $row->{'INST_ID'}
      if (defined ($row->{'INST_ID'}));
    $dsk_info{'group_number'} = $row->{'GROUP_NUMBER'}
      if (defined ($row->{'GROUP_NUMBER'}));
    $dsk_info{'disk_number'} = $row->{'DISK_NUMBER'}
      if (defined ($row->{'DISK_NUMBER'}));
    $dsk_info{'incarnation'} = $row->{'INCARNATION'}
      if (defined ($row->{'INCARNATION'}));
    $dsk_info{'mount_status'} = $row->{'MOUNT_STATUS'}
      if (defined ($row->{'MOUNT_STATUS'}));
    $dsk_info{'header_status'} = $row->{'HEADER_STATUS'}
      if (defined ($row->{'HEADER_STATUS'}));
    $dsk_info{'mode_status'} = $row->{'MODE_STATUS'}
      if (defined ($row->{'MODE_STATUS'}));
    $dsk_info{'state'} = $row->{'STATE'}
      if (defined ($row->{'STATE'}));
    $dsk_info{'redundancy'} = $row->{'REDUNDANCY'}
      if (defined ($row->{'REDUNDANCY'}));
    $dsk_info{'library'} = $row->{'LIBRARY'}
      if (defined ($row->{'LIBRARY'}));
    $dsk_info{'os_mb'} = $row->{'OS_MB'}
      if (defined ($row->{'OS_MB'}));
    $dsk_info{'total_mb'} = $row->{'TOTAL_MB'}
      if (defined ($row->{'TOTAL_MB'}));
    $dsk_info{'free_mb'} = $row->{'FREE_MB'}
      if (defined ($row->{'FREE_MB'}));
    $dsk_info{'name'} = $row->{'NAME'}
      if (defined ($row->{'NAME'}));
    $dsk_info{'failgroup'} = $row->{'FAILGROUP'}
      if (defined ($row->{'FAILGROUP'}));
    $dsk_info{'site_name'} = $row->{'SITE_NAME'}
      if (defined ($row->{'SITE_NAME'}));
    $dsk_info{'site_guid'} = $row->{'SITE_GUID'}
      if (defined ($row->{'SITE_GUID'}));
    $dsk_info{'site_status'} = $row->{'SITE_STATUS'}
      if (defined ($row->{'SITE_STATUS'}));
    $dsk_info{'label'} = $row->{'LABEL'}
      if (defined ($row->{'LABEL'}));
    $dsk_info{'fgroup_label'} = $row->{'FAILGROUP_LABEL'}
      if (defined ($row->{'FAILGROUP_LABEL'}));
    $dsk_info{'site_label'} = $row->{'SITE_LABEL'}
      if (defined ($row->{'SITE_LABEL'}));
    $dsk_info{'path'} = $row->{'PATH'}
      if (defined ($row->{'PATH'}));
    $dsk_info{'udid'} = $row->{'UDID'}
      if (defined ($row->{'UDID'}));
    $dsk_info{'product'} = $row->{'PRODUCT'}
      if (defined ($row->{'PRODUCT'}));
    $dsk_info{'create_date'} = $row->{'CREATE_DATE'}
      if (defined ($row->{'CREATE_DATE'}));
    $dsk_info{'mount_date'} = $row->{'MOUNT_DATE'}
      if (defined ($row->{'MOUNT_DATE'}));
    $dsk_info{'repair_timer'} = $row->{'REPAIR_TIMER'}
      if (defined ($row->{'REPAIR_TIMER'}));
    $dsk_info{'reads'} = $row->{'READS'}
      if (defined ($row->{'READS'}));
    $dsk_info{'writes'} = $row->{'WRITES'}
      if (defined ($row->{'WRITES'}));
    $dsk_info{'read_errs'} = $row->{'READ_ERRS'}
      if (defined ($row->{'READ_ERRS'}));
    $dsk_info{'write_errs'} = $row->{'WRITE_ERRS'}
      if (defined ($row->{'WRITE_ERRS'}));
    $dsk_info{'read_time'} = $row->{'READ_TIME'}
      if (defined ($row->{'READ_TIME'}));
    $dsk_info{'write_time'} = $row->{'WRITE_TIME'}
      if (defined ($row->{'WRITE_TIME'}));
    $dsk_info{'bytes_read'} = $row->{'BYTES_READ'}
      if (defined ($row->{'BYTES_READ'}));
    $dsk_info{'bytes_written'} = $row->{'BYTES_WRITTEN'}
      if (defined ($row->{'BYTES_WRITTEN'}));
    $dsk_info{'voting_file'} = $row->{'VOTING_FILE'}
      if (defined ($row->{'VOTING_FILE'}));
    $dsk_info{'failgroup_type'} = $row->{'FAILGROUP_TYPE'}
      if (defined ($row->{'FAILGROUP_TYPE'}));

    push (@dsk_list, \%dsk_info);

    # If missing disk list requested, we create a hash of found disks keyed
    # by instance name and path. Later (in asmcmddisk_missing) we cross
    #  check this using the disk list and the active instances. 
    if ($missing)
    {
      my ($key);
      $key =  $dsk_info{'path'} . $dsk_info{'inst_id'};    
      $found_hash->{$key} = 1
        if (!defined($found_hash->{$key}));
    }
  }    

  asmcmdshare_finish($sth);

  return (@dsk_list);
}

return 1;

OHA YOOOO