MINI MINI MANI MO

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

# Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      asmcmdbase - ASM CoMmanD line interface (Base Module)
#
#    DESCRIPTION
#      ASMCMD is a Perl utility that provides easy nagivation of files within
#      ASM diskgroups.  This module contains the functionality of all
#      the commands that have been supported since Oracle 10g.  Namely,
#      they are the ASM file system related commands.  This module is also
#      responsible for the CONNECT and DISCONNECT functionality of ASMCMD.
#
#    NOTES
#      usage: asmcmdcore [-v] [-a <sysasm|sysdba>] [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    anovelo    11/02/17 - 26389461: Allow sys directories to be deleted
#    emsu       09/26/17 - Add -l to showpatches, --active to showversion
#    anovelo    07/21/17 - 26389461: Ignore dir permissions with sysasm conn
#    anovelo    03/10/17 - 25514391: Use sysdba privileges to connect in MC
#    wanhlee    02/09/17 - 22895947: Handle new kfod getclstype return value
#    emsu       11/17/16 - 24760407: Factor out cluster state query
#    anovelo    11/16/16 - 12640351: Add --dest_dbname option to 'cp'
#    diguzman   10/20/16 - 24923984: lsct shows wrong IOS info
#    anovelo    10/18/16 - 24706236: Add option --time_style to 'ls'
#    diguzman   10/13/16 - 12825292: add ls -h option
#    diguzman   10/10/16 - 10219399: add wildcard support to cp command
#    prabbala   09/10/16 - bug19777340: add dbuniquename for dbms_diskgroup.copy
#    abhmanju   07/11/16 - add sparse_merge_begin and sparse_merge_end to cp
#    diguzman   06/30/16 - 23721855: lsct connects using $OSID when target
#                          option is set
#    diguzman   05/30/16 - 19654070: Little change at _no_instance_cmd routine
#    diguzman   05/24/16 - 23073738: asmcmdshare_set_instance signature change
#    dacavazo   05/04/16 - Export asmcmdbase_find_sql()
#    diguzman   03/08/16 - 21456380: add --target option to lsct, rm and cp
#    abhmanju   01/15/16 - Bug 21275171 and 21172775 add sparse_option to cp
#                          cmd and add setsparseparent cmd
#    saklaksh   01/05/16 - 22447438: lsof -G to return err on undefined dg
#    dacavazo   12/07/15 - 22223809: undef function before dl_find_symbol
#    dacavazo   08/06/15 - 21453939: skip removed files on process_ls
#    dacavazo   07/07/15 - 21385763: made group name optional on lsct
#    aramacha   07/08/15 - 20402145: parse rhost/rport/rsid for Flex-ASM
#                          remote copy
#    sanselva   05/12/15 - 20721939: quote passwd to prevent SQL injection risk
#    diguzman   04/20/15 - 20134010: Check if cd argument isn't empty or undef
#    jesugonz   01/12/15 - 19494254: filter out invalid file descriptors in lsof
#    ykatada    10/03/14 - #19617921: use bind variables to SELECTs
#    cgraybl    09/05/14 - bug19065962: add logical sector size to lsdg
#    prabbala   09/25/14 - 19468869:show du output as 0 on dir only with aliases
#    shlian     09/09/14 - change showpatches/showversion to work w/o ASM inst
#    shlian     07/10/14 - changed lsof command to be case insensitive
#    hppancha   03/28/14 - change spawnutil to utilsucc
#    siyarlag   05/12/14 - fix connection error on Flex ASM
#    pvenkatr   11/22/13 - #17824302 - replace DOS style \ to Unix style / on
#                          DG file names.
#    pvenkatr   11/04/13 - #17395893 - Using PERL2C interface to pickup
#                          connection string - connection only to ASM instance
#    pvenkatr   09/11/13 - #17432676 - Fixed spelling mistake.
#    pvenkatr   08/08/13 - 17254040 - Fixed splitting string based on ':'
#                          properly.
#    pvenkatr   08/05/13 - 17267662 - fixed spelling mistake.
#    pvenkatr   06/30/13 - Added error string while PERL2C API fails.
#    manuegar   05/02/13 - Bug13951456 Support bind parameters
#    pvenkatr   03/31/13 - LRG# 8842034 - replaced DOS '\' to Unix '/'.
#    pvenkatr   02/01/13 - using asmcmdshare_execute_tool
#    sanselva   12/27/12 - XbranchMerge sanselva_lrg-8658536 from
#                          st_rdbms_12.1.0.1
#    mchimang   12/10/12 - XbranchMerge mchimang_lrg-8504705 from main
#    sanselva   12/10/12 - XbranchMerge sanselva_lrg-8543480 from main
#    sanselva   12/05/12 - Dont show hidden cmds in asmcmdbase_get_asmcmd_cmds
#    moreddy    11/26/12 - 14142633 reconnect on demand for remote asm
#    pvenkatr   11/10/12 - #15848691 - compute kfod path correctly.
#    pvenkatr   09/27/12 - #14676720 deleting sys/usr alias on same dir
#    pvenkatr   09/26/12 - Added createclientcluster command
#    pvenkatr   08/20/12 - #14493584 - pwcopy/pwmove/cp on target dir/file with
#                          RO permission.
#    pvenkatr   07/04/12 - 13989320 Wrong '/' in Copying message.
#    pvenkatr   03/16/12 - Bug 13837760 - permissions
#    adileepk   11/29/11 - Fix for bug-12661648. Correcting the exit status for
#                          rm command when it executes successfully.
#    mchimang   11/18/11 - pass flag in dbms_diskgroup.getfilephyblksize
#    adileepk   09/27/11 - Bifurcating asmcmdbase_process_cp so that it can be
#                          reused by pwcopy command. Fix for bug 12984604.
#    moreddy    09/07/11 - Correct error message in cp to 8009
#    pvenkatr   08/24/11 - Removed flags hash table - using from XML
#    adileepk   06/19/11 - Connection Pooling.
#    mchimang   05/17/10 - Changes for password file copy
#    shmubeen   05/23/11 - bug fix# 12538042
#    adileepk   11/01/10 - Changes made to integrate the parser module with
#                          asmcmd.
#    moreddy    08/23/10 - bug 6969662 improve rm command performance
#    pvenkatr   07/27/10 - Bug 9778018 Added check for windows syntax \\
#    moreddy    05/06/10 - bug 8667038 NLS for error messages
#    amitroy    04/21/10 - BUG 8933243 - USE DIFFERENT CHARACTER IN ASMCMD TO
#                          "HIDE COLUMN DETAILS" INSTEAD OF -H; REPLACE -a
#                          WITh --absolutepath FOR ls
#    mchimang   04/29/10 - Added check to see if files are deleted during du cmd
#    pvenkatr   03/31/10 - Syntax, descripiton, example - all from XML
#    mchimang   03/26/10 - Rectified the DU calculation for the bug 9311197.
#    moreddy    03/22/10 - Adding more tracing
#    sanselva   03/10/10 - fix parsing remote_conn_str for remote cp
#    amitroy    12/30/09 - support for copying a file from local OS to local
#                          ASM when a file with same name exists
#    moreddy    01/19/10 - Adding tracing messages
#    pvenkatr   12/03/09 - Bug # 9072284 Added service as optional param for cp
#    pvenkatr   11/04/09 - Bug # 8788329:support to cp from/to ASM/remote-os
#    pvenkatr   09/03/09 - Help message from xml file.
#    sanselva   06/25/09 - remove -r options from cp
#    sanselva   05/12/09 - remove type checking for cp since done on server side
#    sanselva   04/06/09 - ASMCMD long options and consistency
#    heyuen     03/23/09 - update docs
#    heyuen     12/03/08 - export rm_sql
#    heyuen     10/14/08 - use dynamic modules
#    heyuen     10/03/08 - stop cp when a file is invalid
#    heyuen     09/10/08 - make lsof deterministic
#    heyuen     08/26/08 - add voting file
#    heyuen     08/02/08 - add -V
#    heyuen     08/01/08 - fix windows \
#    heyuen     07/28/08 - use command properties array
#    heyuen     06/16/08 - update help
#    heyuen     04/15/08 - bug 6957288, reorder help messages
#    heyuen     03/31/08 - add -p to ls
#    heyuen     02/20/08 - increase cp performance
#    heyuen     12/17/07 - change error number ranges
#    heyuen     08/02/07 - add lsof
#    siyarlag   09/17/07 - bug/5903321 no quotes for diskgroup name
#    heyuen     08/02/07 - refresh
#    heyuen     06/26/07 - add support for multiple source files in cp
#    hqian      06/07/07 - #5131203: add disk group name to lsct results
#    heyuen     05/25/07 - add return codes for errors
#    dfriedma   05/24/07 - Remove unbalanced column
#    hqian      05/24/07 - Use SYSASM as connection default, since bug 5873184
#                          is fixed
#    heyuen     05/09/07 - add support for cp with relative paths
#    pbagal     04/13/07 - Add ASMCMD comment in all SQL
#    heyuen     04/02/07 - changed help message for cp, fixed remote db
#                          connection
#    hqian      03/09/07 - Improve error msg: give msgid
#    hqian      03/02/07 - -c and -g for ls and lsdg
#    jilim      11/16/06 - asmcmd cp feature, asmcmdbase_process_cp and
#                          its sub-modules
#    hqian      02/08/07 - lrg-2839533: temporary revert default connection
#                          type back to sysdba
#    hqian      08/17/06 - remove SQL dependency on init_global
#    hqian      07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks
#    averhuls   07/06/06 - prevent ls from displaying volume info.
#    hqian      06/15/06 - move asmcmdbase_ls_calc_min_col_wid to shared
#                          module
#    hqian      02/02/06 - Bug-5007830: signal out of asmcmd if ORA-03114
#    hqian      02/01/06 - Fix process_mkdir(): mkdir +dg with uninit $cre
#    hqian      01/27/06 - merge two error schemes into one:
#                          asmcmdbase_display_msg
#    hqian      01/25/06 - Split off asmcmdshare.pm from this module
#    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 #################################
#
# Top Level Command Processing Routines
#   asmcmdbase_init
#   asmcmdbase_process_cmd
#   asmcmdbase_process_mkdir
#   asmcmdbase_process_rm
#   asmcmdbase_process_mkalias
#   asmcmdbase_process_rmalias
#   asmcmdbase_process_pwd
#   asmcmdbase_process_cd
#   asmcmdbase_process_ls
#   asmcmdbase_process_find
#   asmcmdbase_process_du
#   asmcmdbase_process_lsdg
#   asmcmdbase_process_lsct
#   asmcmdbase_process_help
#   asmcmdbase_process_cp
#   asmcmdbase_process_setsparseparent
#   asmcmdbase_process_lsof
#   asmcmdbase_process_showclustermode
#   asmcmdbase_process_showpatches
#   asmcmdbase_process_showversion
#   asmcmdbase_process_showclusterstate
#
# Internal Command Processing Routines
#   asmcmdbase_ls_process_file
#   asmcmdbase_ls_get_subdirs
#   asmcmdbase_ls_subdir_print
#   asmcmdbase_ls_print
#   asmcmdbase_ls_print_hdr
#   asmcmdbase_ls_init_col_wid
#   asmcmdbase_lsdg_init_col_wid
#   asmcmdbase_lsdg_print
#   asmcmdbase_find_one
#   asmcmdbase_find_match
#   asmcmdbase_rm_prompt_conf
#   asmcmdbase_do_file_copy
#
# Sort Order Routines
#   asmcmdbase_name_forward
#   asmcmdbase_name_backward
#   asmcmdbase_time_forward
#   asmcmdbase_time_backward
#   asmcmdbase_rm_recur_order
#   asmcmdbase_levels
#
# Parameter Parsing Routines
#   asmcmdbase_parse_remote_conn_str
#   asmcmdbase_getchr_noecho
#   asmcmdbase_is_cmd
#   asmcmdbase_is_wildcard_cmd
#   asmcmdbase_is_no_instance_cmd
#   asmcmdbase_parse_int_args
#   asmcmdbase_parse_int_cmd_line
#
# Error Routines
#   asmcmdbase_syntax_error
#
# Initialization Routines
#   asmcmdbase_check_insttype
#   asmcmdbase_init_global
#
# SQL Routines
#   asmcmdbase_mkdir_sql
#   asmcmdbase_rm_sql
#   asmcmdbase_mkalias_sql
#   asmcmdbase_rmalias_sql
#   asmcmdbase_is_rbal
#   asmcmdbase_get_alias_path
#   asmcmdbase_get_ct
#   asmcmdbase_connect
#   asmcmdbase_disconnect
#   asmcmdbase_find_sql
#   asmcmdbase_get_cluster_state
#
# Help Routines
#   asmcmdbase_get_asmcmd_cmds
#
#############################################################################

package asmcmdbase;
require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(asmcmdbase_init
                 asmcmdbase_process_cmd
                 asmcmdbase_process_help
                 asmcmdbase_is_cmd
                 asmcmdbase_parse_int_args
                 asmcmdbase_parse_int_cmd_line
                 asmcmdbase_check_insttype
                 asmcmdbase_init_global
                 asmcmdbase_syntax_error
                 asmcmdbase_connect
                 asmcmdbase_disconnect
                 asmcmdbase_get_asmcmd_cmds
                 asmcmdbase_rm_sql
                 asmcmdbase_get_alias_path
                 asmcmdbase_cp_core
                 asmcmdbase_set_sparse_parent
                 asmcmdbase_find_sql
                 asmcmdbase_get_cluster_state
                );

use strict;
use DBI qw(:sql_types);
use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat);
use Math::BigInt;
use File::Glob qw(:bsd_glob);
use asmcmdglobal;
use asmcmdshare;
use asmcmdparser;

#use Term::ReadKey;                     # Currently not bundled with Oracle.#
use List::Util qw[min max];

use POSIX qw(:termios_h);
require DynaLoader;

############################ Global Constants ###############################
#
# The following list is used primarily for is_cmd.  All other data from XML.
#
my  (%asmcmdbase_cmds) = (cd      => {},
                          cp      => {},
                          du      => {},
                          find    => {},
                          help    => {},
                          ls      => {},
                          lsct    => {},
                          lsdg    => {},
                          mkalias => {},
                          mkdir   => {},
                          pwd     => {},
                          rmalias => {},
                          rm      => {},
                          lsof    => {},
                          showclustermode => {},
                          showpatches => {},
                          showversion => {},
                          showclusterstate => {},
                          setsparseparent => {}
                          );

my $ASMCMDBASE_SPACE     = ' ';                 # Constant string for a space. #
my $ASMCMDBASE_SIXMONTH  = 183;                # Number of days in six months. #
my $ASMCMDBASE_DATELEN   = 4;                     # Length of the date string. #
my $ASMCMDBASE_MAXPASSWD = 256;             # Max length of user passwd input. #

# ASMCMD Column Header Names:
# Below are the names of the column headers for ls and lsdg.  These headers
# are the ASMCMD equivalent of the columns of v$asm_alias, v$asm_file,
# v$asm_diskgroup, and v$asm_operation.  In the comment to the right of each
# constant is the fixed view column name that this column name corresponds to.
my $ASMCMDBASE_LS_HDR_TYPE       = 'Type';               # TYPE in v$asm_file. #
my $ASMCMDBASE_LS_HDR_REDUND     = 'Redund';       # REDUNDANCY in v$asm_file. #
my $ASMCMDBASE_LS_HDR_STRIPED    = 'Striped';         # STRIPED in v$asm_file. #
my $ASMCMDBASE_LS_HDR_TIME       = 'Time';  # MODIFICATION_TIME in v$asm_file. #
my $ASMCMDBASE_LS_HDR_SYSCRE     = 'Sys';     # SYSTEM_CREATED in v$asm_alias. #
my $ASMCMDBASE_LS_HDR_BSIZE      = 'Block_Size';   # BLOCK_SIZE in v$asm_file. #
my $ASMCMDBASE_LS_HDR_BLOCKS     = 'Blocks';           # BLOCKS in v$asm_file. #
my $ASMCMDBASE_LS_HDR_BYTES      = 'Bytes';             # BYTES in v$asm_file. #
my $ASMCMDBASE_LS_HDR_SPACE      = 'Space';             # SPACE in v$asm_file. #
my $ASMCMDBASE_LS_HDR_USER       = 'User';               # USER in v$asm_file. #
my $ASMCMDBASE_LS_HDR_GROUP      = 'Group';             # GROUP in v$asm_file. #
my $ASMCMDBASE_LS_HDR_PERM       = 'Permission';   # PERMISSION in v$asm_file. #

# These declarations have to be declared with the 'our' keyword
# in order for eval() to work correctly in asmcmdbase_lsdg_print().
our $ASMCMDBASE_LSDG_HDR_INSTID  = 'Inst_ID';              # Instance ID for
                                                           # gv$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_SECTOR  = 'Sector'; # SECTOR_SIZE in v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_LSECTOR = 'Logical_Sector';  # LOGICAL_SECTOR_SIZE in #
                                                            # v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_REBAL   = 'Rebal';  # OPERATION from v$asm_operation. #
our $ASMCMDBASE_LSDG_HDR_BLOCK   = 'Block';   # BLOCK_SIZE in v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_AU      = 'AU';            # ALLOCATION_UNIT_SIZE in  #
                                                            # v$asm_diskgroup. #

our $ASMCMDBASE_LSDG_HDR_STATE   = 'State';        # STATE in v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_TYPE    = 'Type';          # TYPE in v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_TMB     = 'Total_MB';  # TOTAL_MB in v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_FMB     = 'Free_MB';    # FREE_MB in v$asm_diskgroup. #

# REQUIRED_MIRROR_FREE_MB from v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_RMFMB   = 'Req_mir_free_MB';

# USABLE_FILE_MB from v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_UFMB    = 'Usable_file_MB';

# OFFLINE_DISKS from v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_ODISK   = 'Offline_disks';

# VOTING_FILE from v$asm_diskgroup. #
our $ASMCMDBASE_LSDG_HDR_VOTING_FILE  = 'Voting_files';

our $ASMCMDBASE_LSDG_HDR_NAME    = 'Name';          # NAME in v$asm_diskgroup. #

our (%asmcmdbase_lsof_header) = ('path_kffof', 'Path',
                                 'dbname_kffof', 'DB_Name',
                                 'instancename_kffof', 'Instance_Name'
                                 );
# for remote connection
our ($rusr, $rpswd, $rident, $rhost, $rsid, $rport);

# PLSQL constants
my $PLSQL_DATAFILE    = 2;
my $PLSQL_DATAFILE_CP = 12;
my $PLSQL_NUMBER      = 22;
my $PLSQL_VARCHAR     = 1024;
my $PLSQL_PWFILE      = 28;

sub is_asmcmd
{
  return 1;
}


################# Top Level Command Processing Routines ######################
########
# NAME
#   asmcmdbase_init
#
# DESCRIPTION
#   This function initializes the asmcmdbase module.  For now it simply
#   registers its callbacks with the asmcmdglobal module.
#
# PARAMETERS
#   None
#
# RETURNS
#   Null
#
# NOTES
#   Only asmcmdcore_main() calls this routine.
########
sub init
{
  # All of the arrays defined in the asmcmdglobal module must be
  # initialized here.  Otherwise, an internal error will result.
  push (@asmcmdglobal_command_callbacks, \&asmcmdbase_process_cmd);
  push (@asmcmdglobal_help_callbacks, \&asmcmdbase_process_help);
  push (@asmcmdglobal_command_list_callbacks, \&asmcmdbase_get_asmcmd_cmds);
  push (@asmcmdglobal_is_command_callbacks, \&asmcmdbase_is_cmd);
  push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmdbase_is_wildcard_cmd);
  push (@asmcmdglobal_syntax_error_callbacks, \&asmcmdbase_syntax_error);
  push (@asmcmdglobal_no_instance_callbacks, \&asmcmdbase_is_no_instance_cmd);
  %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdbase_cmds);

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


########
# NAME
#   asmcmdbase_process_cmd
#
# DESCRIPTION
#   This routine calls the appropriate routine to process the command specified
#   by $asmcmdglobal_hash{'cmd'}.
#
# PARAMETERS
#   dbh       (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   1 if command is found; 0 if not.
#
# NOTES
#   Only asmcmdcore_shell() calls this routine.
########
sub asmcmdbase_process_cmd 
{
  my ($dbh) = @_;
  my ($succ) = 0;
 
  # Get current command from global value, which is set by 
  # asmcmdbase_parse_asmcmd_args()and by asmcmdcore_shell().
  my($cmd) = $asmcmdglobal_hash{'cmd'};

  # Declare and initialize hash of function pointers, each designating a 
  # routine that processes an ASMCMD command.  Now that ASMCMD is divided
  # into modules, the help command needs to be removed from this list,
  # because it's a global command, not a module specific command.
  my (%cmdhash) = ( cd      => \&asmcmdbase_process_cd ,
                    du      => \&asmcmdbase_process_du ,
                    find    => \&asmcmdbase_process_find,
                    ls      => \&asmcmdbase_process_ls,
                    lsct    => \&asmcmdbase_process_lsct,
                    lsdg    => \&asmcmdbase_process_lsdg,
                    mkalias => \&asmcmdbase_process_mkalias,
                    mkdir   => \&asmcmdbase_process_mkdir,
                    pwd     => \&asmcmdbase_process_pwd,
                    rm      => \&asmcmdbase_process_rm,
                    rmalias => \&asmcmdbase_process_rmalias,
                    cp      => \&asmcmdbase_process_cp,
                    lsof    => \&asmcmdbase_process_lsof,
                    showclustermode => \&asmcmdbase_process_showclustermode,
                    showpatches => \&asmcmdbase_process_showpatches,
                    showversion => \&asmcmdbase_process_showversion,
                    showclusterstate => 
                                   \&asmcmdbase_process_showclusterstate,
                    setsparseparent => \&asmcmdbase_process_setsparseparent
                 );

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

  return $succ;
}


########
# NAME
#   asmcmdbase_process_lsof
#
# DESCRIPTION
#   This top-level routine processes the lsof command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() calls this function.
########
sub asmcmdbase_process_lsof
{
  my ($dbh) = shift;
  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($dgname, $dbname, $instname, $gnum);
  my ($sth, $row);
  my (@what, @from, @where, @order, @binds);
  my (@lsof_list);
  my ($k, $v, $h);
  my (%min_col_wid, $print_format, $print_string, @what_print);
  my ($kffilf_invalid) = 0x00004000;

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

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  $dgname = $args{'G'} if (defined($args{'G'}));
  $dbname = $args{'dbname'} if (defined($args{'dbname'}));
  $instname = $args{'C'} if (defined($args{'C'}));

  push (@what, 'dbname_kffof');
  push (@what, 'instancename_kffof');
  push (@what, 'path_kffof');

  # 'flags_kffof' should  be the last element of @what, it gets popped later
  push (@what, 'flags_kffof');

  push (@from, 'x$kffof');

  # Bug19494254: Filter out invalid file descriptors
  # $kffilf_invalid represents the same flag as in kff.h:KFFILF_INVALID
  push (@where, "bitand(flags_kffof, $kffilf_invalid) = 0");

  #filter disk group
  if (defined($args{'G'}))
  {
    #get disk group name
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $args{'G'});
    if (!defined($gnum))
    {
      my (@eargs) = ($args{'G'});
      asmcmdshare_error_msg(8001, \@eargs);
      return;
    }

    push (@where, "group_kffof = ?");
    push (@binds, [$gnum, SQL_INTEGER]);
  }

  #filter database
  if (defined($args{'dbname'}))
  {
    push (@where, "upper(dbname_kffof) = ?");
    push (@binds, [uc($args{'dbname'}), SQL_VARCHAR]);
  }

  #filter instance
  if (defined($args{'C'}))
  {
    push (@where, "upper(instancename_kffof) = ?");
    push (@binds, [uc($args{'C'}), SQL_VARCHAR]);
  }

  push (@order, 'dbname_kffof');
  push (@order, 'instancename_kffof');
  push (@order, 'path_kffof');

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

  if (!defined ($sth))	
  {	
    asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');	
    if ($asmcmdglobal_hash{'mode'} eq 'n')	
    {	
      $asmcmdglobal_hash{'e'} = -1;	
    }	
  }
 
  #initialize the min_col_wid array
  foreach(@what)
  {
    $min_col_wid{$_} = length($asmcmdbase_lsof_header{$_});
  }

  #get the rows
  while (defined($row = asmcmdshare_fetch($sth)))
  {
    my(%file_info) = ();
    while (($k,$v) = each(%{$row}))
    {
      $k =~ tr/[A-Z]/[a-z]/;
      # skip 'flags_kffof' column
      next if ($k eq 'flags_kffof');
      $file_info{$k}    = $v;
      $min_col_wid{$k} = max($min_col_wid{$k}, length($v));
    }
    push (@lsof_list, \%file_info);
  }

  asmcmdshare_finish($sth);

  # don't print 'flags_kffof'
  pop (@what);

  #create print format
  $print_format = '';

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

  #print header
  if (!defined ($args{'suppressheader'}) )
  {
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, $asmcmdbase_lsof_header{$_});
    }
    $print_string = sprintf($print_format, @what_print);
    asmcmdshare_print($print_string);
  }

  #print rows
  foreach $h (@lsof_list)
  {
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, $h->{$_});
    }
    $print_string = sprintf($print_format, @what_print);
    asmcmdshare_print($print_string);
  }

}


########
# NAME
#   asmcmdbase_process_mkdir
#
# DESCRIPTION
#   This top-level routine processes the mkdir command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() calls this function.
########
sub asmcmdbase_process_mkdir 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($dir);                         # Create directory with this path name. #
  my ($cre);             # Last level of in the path $dir; create directory  #
                                                           # with this name. #

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

  # Process creation one path at a time.
  while (defined ($dir = shift (@{$args{'mkdir'}}))) 
  {
    my $backup1 = $1;
    $dir = asmcmdshare_make_absolute($dir);
    $dir =~ s,/+$,,;                              # Remove all trailing '/'. #
    $dir =~ s,/([^/]+)$,,;    # Do not normalize the creation level, remove. #
    $cre = $1;                      # Save creation level of path $dir here. #
    
    undef $cre if(defined $backup1 && $backup1 eq $1);
    # If the user entered only '+dg' as an argument, then we have to
    # parse that as creating 'dg' in directory '+'.  This will result
    # in an error, which is the correct behavior.
    if (!defined ($cre))
    {
      $cre = $dir;                                 # Set $cre to e.g. '+dg'. #
      $cre =~ s,^\+,,;                      # Get rid of the '+' from '+dg'. #
      $dir = '+';                                   # Set parent dir to '+'. #
    }

    %norm = asmcmdshare_normalize_path($dbh, $dir, 0, \$ret);
    next if ($ret != 0);            # Skip creation if normalization failed. #

    # Fix bug that allowed creation under '+'.  Setting group name to '+'
    # guarantees failure if trying to create under '+', which is the desired
    # outcome.  Oracle will provide the error code.
    if ($dir eq '+')
    {
      $asmcmdglobal_hash{'gname'} = '+';
    }

    $dir = $norm{'path'}->[0];
    $dir .= '/' . $cre;                    # Reattach creation name to path. #

    # Run creation SQL. #
    asmcmdbase_mkdir_sql($dbh, $asmcmdglobal_hash{'gname'}, $dir);
  }

  return;
}

########
# NAME
#   asmcmdbase_process_rm
#
# DESCRIPTION
#   This top-level routine processes the rm command.
#
# PARAMETERS
#   dbh   (IN) - initialize database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() calls this routine. 
#   Note also that removing a user alias removes the respective system alias
#   as well, and vice versa.  Only one of the user/system pair needs to match
#   a wildcard or be under a directory recursively (if -r) for the other to
#   be deleted as well.  So please be careful when using rm!
########
sub asmcmdbase_process_rm
{
  my $dbh  = shift;
  my $sid  = undef;
  my $osid = undef;

  my %args;                               # Argument hash used by getopts(). #
  my %norm;        # See asmcmdshare_normalize_path() return value comments. #
  my @recur_list;                 # List of hashes of fields of all entries  #
                                            # recursively under a directory. #
  my %deleted_hash;                               # Hash of deleted entries. #
  my @entries;         # List of hashes of fields of matching entries under  #
                                                              # a directory. #
  my $ret;                       # asmcmdbase_parse_int_args() return value. #
  my $alias_path;                      # User-entered raw path for deletion. #
  my $alias_name;                                    # Alias name of a path. #
  my $is_dir;            # Flag: 'Y' if alias is a directory; 'N' otherwise. #
  my $gnum;                                                  # Group number. #
  my $gname;                                                   # Group name. #
  my $par_id;                                       # Parent ID of an alias. #
  my $hash_str;                             # Hash string for %deleted_hash. #
  my $iter;                        # Iteration variable for foreach() loops. #
  my $spprserr = 0;           # Whether to suppress errors on normalization. #
  my $i;                               # Iteration variable for for() loops. #
  my ($sth, $dir);
  my @eargs;
  my @entries2;
  my $stmt;
  my $tgt;

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

  if ($args{'target'})
  {
    $tgt                         = $args{'target'};
    $sid                         = asmcmdshare_set_instance($tgt);
    $asmcmdglobal_hash{'target'} = $tgt;

    if (!defined($sid))
    {
      my @eargs = ("target", $tgt);
      # ASMCMD-8608 "invalid value for '%s' option: %s"
      asmcmdshare_error_msg(8608, \@eargs);
      return 0;
    }

    return 0 if ($sid eq '');

    asmcmdbase_disconnect($dbh);
    if ($ENV{'ORACLE_SID'} ne $sid)
    {
      $osid              = $ENV{'ORACLE_SID'};
      $ENV{'ORACLE_SID'} = $sid;
    }
    $dbh = asmcmdbase_connect(undef);

    if (!defined($dbh))
    {
      asmcmdshare_trace(1, "failed to connect to target instance " .
                        "[$ENV{'ORACLE_SID'}]", 'y', 'n');
    }
  }

  # First check flags and args to see if we should ask user for confirmation
  # before proceeding.  We prompt the user when the -f flag is not set, when
  # the mode is interactive, and when at least one of these conditions
  # is true:
  # 1) -r is set,
  # 2) at least one argument of 'rm' contains a wildcard.
  
  # First reset $ret
  $ret = 0;

  if (! defined ($args{'f'}) && ($asmcmdglobal_hash{'mode'} eq 'i')) 
  {
    if (defined ($args{'r'})) 
    {
      $ret = asmcmdbase_rm_prompt_conf();         # Prompt for confirmation. #
      return unless ($ret == 1);
    }
    else 
    {
      for ($i = 0; $i < @{$args{'rm'}}; $i++) 
      {
        if (${$args{'rm'}}[$i] =~ m,$ASMCMDGLOBAL_WCARD_CHARS,) 
        {
          $ret = asmcmdbase_rm_prompt_conf();
          return unless ($ret == 1);
          last;
        }
      }
    }
  }

  # Process each alias entry one at a time. (rm can take multiple alias
  # arguments.
  $spprserr = 1 if (defined ($args{'r'}));
  while (defined ($alias_path = shift (@{$args{'rm'}}))) 
  {
    # A) First process all entries under $alias_path, if -r.
    if (defined ($args{'r'}))
    {
      # See if path is valid. Get all paths if $path contains a wildcard.
      %norm = asmcmdshare_normalize_path($dbh, $alias_path, '0', \$ret);
      next if ($ret != 0);

      for ($i = 0; $i < @{ $norm{'path'} }; $i++)
      {
        $alias_name = $norm{'path'}->[$i];
        $alias_name =~ s,.*/([^/]+)$,$1,;   # Get last level of path for name. #
        $gnum = $norm{'gnum'}->[$i];
        $par_id = $norm{'par_id'}->[$i];
        asmcmdshare_get_subdirs($dbh, \@entries, undef, $gnum, undef, $par_id,
                                $alias_name, undef, 0, 0);
        $is_dir = $entries[$i]->{'alias_directory'};
        if (defined($is_dir) && ($is_dir eq 'Y'))
        {
          $gname = asmcmdshare_get_gname_from_gnum($dbh, $gnum);
          $dir=$norm{'path'}->[$i];
          # call fixed package
          $stmt = <<STMT;
             begin
          dbms_diskgroup.dropdir('$dir');
          exception when others then
          raise;
          end;
STMT
          $sth = $dbh->prepare($stmt);
          $ret = $sth->execute();
          if (!defined($ret))
          {
            asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
            if ($asmcmdglobal_hash{'mode'} eq 'n')
            {
               $asmcmdglobal_hash{'e'} = -1;
            }
            return;
          }
          # reconnect
          asmcmdbase_disconnect($dbh) if defined ($dbh);
          #undef since we connect to local asm instance
          $dbh = asmcmdbase_connect(undef);
        }
      }
    }

    # B) Last process $alias_path itself.
    %norm = asmcmdshare_normalize_path($dbh, $alias_path, $spprserr, \$ret);
    next if ($ret != 0);
    
    # Remove one entry at a time, if $alias_path contains '*' and has multiple
    # matches.
    for ($i = 0; $i < @{ $norm{'path'} }; $i++) 
    {
      $alias_name = $alias_path = $norm{'path'}->[$i];
      $alias_name =~ s,.*/([^/]+)$,$1,;   # Get last level of path for name. #
      $par_id = $norm{'par_id'}->[$i];
      $gnum = $norm{'gnum'}->[$i];

      # If parent index is -1, then the directory must be a diskgroup or '+';
      # thus we cannot remove it.
      if ($par_id != -1) 
      {
        asmcmdshare_get_subdirs($dbh, \@entries2, undef, $gnum, undef, $par_id, 
                               $alias_name, undef, 0, 0);
        $is_dir = $entries2[$i]->{'alias_directory'};

        # Bug 14676720: if system & user alias are in same directory,
        # file gets deleted while processing first alias, skip the second.
        #
        if (defined($is_dir))
        {
          $gname = asmcmdshare_get_gname_from_gnum ($dbh, $gnum);
        
          # Run SQL to remove an entry.     
          asmcmdbase_rm_sql($dbh, $gname, $alias_path, $is_dir);
        }
      }
    }
  }

  if (defined($osid))
  {
    asmcmdbase_disconnect($dbh);
    $ENV{'ORACLE_SID'} = $osid;
    $dbh = asmcmdbase_connect(undef);
  }

  return;
}

########
# NAME
#   asmcmdbase_process_mkalias
#
# DESCRIPTION
#   This top-level routine processes the mkalias command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_mkalias 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($sys_a);                       # User-specified existing system alias. #
  my ($usr_a);                 # User-specified new user alias for creation. #
  my ($cre);             # Last level of in the path $dir; create directory  #
                                                           # with this name. #

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

  ($sys_a, $usr_a) = @{$args{'mkalias'}};

  $usr_a = asmcmdshare_make_absolute($usr_a);
  $usr_a =~ s,/+$,,;                              # Remove all trailing '/'. #
  $usr_a =~ s,/([^/]+)$,,;      # Remove last level, which is to be created. #
  $cre = $1;                                           # Save creation name. #

  # If the user entered only '+dg' as an argument, then we have to
  # parse that as creating the alias 'dg' in directory '+'.  This
  # will result in an error, which is the correct behavior.
  if (!defined ($cre))
  {
    $cre = $usr_a;                                 # Set $cre to e.g. '+dg'. #
    $cre =~ s,^\+,,;                        # Get rid of the '+' from '+dg'. #
    $usr_a = '+';                                   # Set parent dir to '+'. #
  }

  # Check if system alias exists.
  %norm = asmcmdshare_normalize_path($dbh, $sys_a, 0, \$ret);
  return if ($ret != 0);
  $sys_a = $norm{'path'}->[0];

  # Check if directory to contain new user alias exists.
  %norm = asmcmdshare_normalize_path($dbh, $usr_a, 0, \$ret);
  return if ($ret != 0);

  # Fix bug that allowed creation under '+'.  Setting group name to '+'
  # guarantees failure if trying to create under '+', which is the desired
  # outcome.  Oracle will provide the error code.
  $asmcmdglobal_hash{'gname'} = '+' if ($usr_a eq '+');

  $usr_a = $norm{'path'}->[0];
  $usr_a .= '/' . $cre;                    # Reattach creation name to path. #

  # Run SQL to create user alias.
  asmcmdbase_mkalias_sql($dbh, $asmcmdglobal_hash{'gname'}, $sys_a, $usr_a);

  return;
}

########
# NAME
#   asmcmdbase_process_rmalias
#
# DESCRIPTION
#   This top-level routine processes the rmalias command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_rmalias 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($alias_path);                    # User-entered raw path for deletion. #
  my ($recurse) = 0;                        # Boolean: 1 if -r; 0 otherwise. #

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

  $recurse = 1 if (defined ($args{'r'}));

  # Process each alias entry one at a time. (rmalias can take multiple alias
  # arguments.
  while (defined ($alias_path = shift (@{$args{'rmalias'}}))) 
  {
    # Make sure alias exists.
    %norm = asmcmdshare_normalize_path($dbh, $alias_path, 0, \$ret);
    next if ($ret != 0);
    $alias_path = $norm{'path'}->[0];

    # Run SQL to delete user alias.
    asmcmdbase_rmalias_sql ($dbh, $asmcmdglobal_hash{'gname'}, 
                            $alias_path, $recurse);
  }
}

########
# NAME
#   asmcmdbase_process_pwd
#
# DESCRIPTION
#   This top-level routine processes the pwd command.
#
# PARAMETERS
#   None.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_pwd {
  my (%args);
  my ($ret);

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

  asmcmdshare_print("$asmcmdglobal_hash{'cwdnm'}\n");
  return;
}

########
# NAME
#   asmcmdbase_process_cd
#
# DESCRIPTION
#   This top-level routine processes the cd command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_cd 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($dir);              # Change current directory to this directory path. #
  my ($maxval);  # Reference index value for non-directory aliases in 10gR2. #
  my ($ref_id);                  # Reference index value for an alias entry. #
  my ($gnum);                                                # Group number. #

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

  ($dir) = @{$args{'cd'}};

  # 20134010: Check $dir is defined and not empty.
  if (!defined($dir) || $dir eq '')
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # Check if path is valid.
  %norm = asmcmdshare_normalize_path($dbh, $dir, 0, \$ret);
  return if ($ret != 0);                  # Error should already be printed. #

  # Since we support wildcards for CD now, make sure there is only one
  # match.  Otherwise, report error.
  if (@{ $norm{'path'} } > 1)
  {
    # asmcmd: $dir: ambiguous
    my (@eargs) = ($dir);
    asmcmdshare_error_msg(8005, \@eargs);
    return;
  }

  $dir = $norm{'path'}->[0];
  $ref_id = $norm{'ref_id'}->[0];
  $gnum = $norm{'gnum'}->[0];

  # Calculate maxval:
  # $maxval is (group_number + 1) * 2^24 - 1.
  $maxval = (($gnum + 1) << 24) - 1;
  $maxval = -2 if ($gnum == -1);     # We're in '+', no need to calc maxval. #

  # Reference index value for non-directory aliases is 0 in 10gR1 and $maxval
  # in 10gR2.  You can't cd to a file, so display error.
  if (($ref_id == $maxval) || ($ref_id == 0)) 
  {
    # asmcmd: "entry '%s' does not refer to a valid directory"
    my (@eargs) = ($dir);
    asmcmdshare_error_msg(8006, \@eargs);
    return;
  }

  # Update global values for new current directory.
  $asmcmdglobal_hash{'cwdnm'} = $dir;
  $asmcmdglobal_hash{'cwdref'} = $ref_id;

  return;
}

########
# NAME
#   asmcmdbase_process_ls
#
# DESCRIPTION
#   This top-level routine processes the ls command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_ls 
{
  my $dbh = shift;

  my %args;              # Argument hash used by getopts().                   #
  my %min_col_wid;       # Hash of mininum column widths for each ls column.  #
  my %subdirs;           # Hash of array pointers, each array containing      #
                         # entries under a directory.                         #
  my %norm;              # See asmcmdshare_normalize_path() return value      #
                         # comments. One entry returned.                      #
  my @paths;             # Array of normalized paths; $norm{'path'}           #
                         # dereferenced.                                      #
  my @ref_ids;           # Reference indexes for @paths; $norm{'ref_id'}      #
                         # dereferenced.                                      #
  my @par_ids;           # Parent indexes for @paths; $norm{'par_id'}         #
                         # dereferenced.                                      #
  my @dg_nums;           # Diskgroup numbers for @paths; $norm{'gnum'}        #
                         # dereferenced.                                      #
  my @entry_list;        # List of entries (hashes) that $alias matches after #
                         # normalization; includes full column values for     #
                         # every file and directory.                          #
  my $cur_date;          # Current date in Julian Date.                       #
  my $time_format;       # Time format used to print dates.                   #
  my $get_file_info = 0; # boolean: true if we want file info as well.        #
  my $alias;             # User-entered alias to be listed.                   #
  my $ret;               # asmcmdbase_parse_int_args() return value.          #
  my $i;                 # Iteration variable for for() loops.                #

  $cur_date = asmcmdshare_cur_julian_date ($dbh);
 
  # Get option parameters, if any.
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);
  
  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  ($alias) = @{$args{'ls'}} if (defined $args{'ls'});
  $alias = '' unless defined ($alias);     # Defaults to '', or current dir. #

  # If user specified time format, use that. Otherwise, use MON DD HH24:MI:SS #
  $time_format = $args{'time_style'} if defined $args{'time_style'};
  $time_format = 'MON DD HH24:MI:SS' unless defined $time_format; 
  
  # replace windows style '\\' to Unix style '\/'
  $alias =~ s/\\/\//g;

  # See if any entries exist; if so, find all matches for $alias.
  %norm = asmcmdshare_normalize_path($dbh, $alias, 0, \$ret);
  return unless ($ret == 0);

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

  # Obtain all fields for each match; store in @entry_list, an array of
  # hashes, each containing one entry with all fields.
  for ($i = 0; $i < @paths; $i++) 
  {
    my %entry_info;

    $entry_info{'path'}            = $paths[$i];
    $entry_info{'name'}            = $paths[$i];
    $entry_info{'name'}            =~ s,.*/(.*)$,$1,;
    $entry_info{'name_print'}      = $entry_info{'name'};
    $entry_info{'reference_index'} = $ref_ids[$i];
    $entry_info{'parent_index'}    = $par_ids[$i];
    $entry_info{'group_number'}    = $dg_nums[$i];

    # In ASMCMD diskgroups and '+' are both treated as virtual directories.
    # Ref index for diskgroup is defined to be (group_number * 2^24).  Ref
    # index for '+' is define to be -1.  
    if (($entry_info{'reference_index'} == -1) ||
        ($entry_info{'reference_index'} == $entry_info{'group_number'} << 24)) 
    {
      # If the entry is a diskgroup of '+', we treat it as a directory.
      $entry_info{'alias_directory'} = 'Y';
      push (@entry_list, \%entry_info);   # Save hash entry fieldss in list. #
    }
    else 
    {
      # Get file info only if we need it.
      $get_file_info = 1 if (defined($args{'l'}) || defined($args{'s'}) ||
                             defined($args{'permission'}));

      # asmcmdshare_normalize_path currently does not return all the 
      # column values in v$asm_alias and v$asm_file, so get the remaining
      # column values.
      my $prev_size = scalar @entry_list;
      asmcmdshare_get_subdirs($dbh, \@entry_list, $time_format, 
                              $entry_info{'group_number'}, 
                              $entry_info{'reference_index'}, 
                              $entry_info{'parent_index'}, 
                              $entry_info{'name'}, undef, 0, $get_file_info);

      # Continue processing the row only if a new row was added (the file has
      # not been removed since the first query)
      if (scalar @entry_list > $prev_size)
      {
        $entry_list[-1]->{'path'} = $entry_info{'path'};
        $entry_list[-1]->{'name_print'} = $entry_info{'name'};

        # If entry is a file, process the file column values from v$asm_file.
        if ($entry_list[-1]->{'alias_directory'} eq 'N') 
        {
          asmcmdbase_ls_process_file ($dbh, $entry_list[-1], \%args, $cur_date,
                                      $get_file_info);
        }
      }
    }
  }

  # Sort @entry_list based on combinations of the -r and -t flags.  See sort
  # routines on sort orderings and priorities.
  if (defined ($args{'t'}) && defined ($args{'reverse'})) 
  {
    @entry_list = sort asmcmdbase_time_backward @entry_list;
  }
  elsif (defined ($args{'t'}) && !defined ($args{'reverse'})) 
  {
    @entry_list = sort asmcmdbase_time_forward @entry_list;
  }
  elsif (!defined ($args{'t'}) && defined ($args{'reverse'})) 
  {
    @entry_list = sort asmcmdbase_name_backward @entry_list;
  }
  else
  {
    @entry_list = sort asmcmdbase_name_forward @entry_list;
  }

  # Diskgroups get processed separately.
  if ($entry_list[0]->{'reference_index'} == -1) 
  {                                                  # We're doing an 'ls +'. #
    if (defined ($args{'d'})) 
    {
      # 'ls -d +' should list the information for '+', but it has none, so just 
      # print '+'.
      asmcmdshare_print("+\n");
    }
    else 
    {
      # Without -d, we list all directories under '+', which are all the 
      # diskgroups.
      asmcmdbase_lsdg_print ($dbh, undef, \%args);
    }

    return;
  }

  # We take care of the case of 'ls -d <dg_name>'.
  if (($entry_list[0]->{'parent_index'} == -1) &&
      (defined ($args{'d'}))) 
  {
    # In case pwd is '+dg_name' and we do 'ls -d' with no argument, assign
    # $alias to current directory, which is a diskgroup name.
    $alias = $entry_list[0]->{'name'} if ($alias eq '');

    # Trim the leading '+', as asmcmdbase_lsdg_print doesn't need it. #
    $alias =~ s,^\+,,;

    $alias =~ s,/+$,,;               # Trim ending '+', in cases of 'ls /+'. #

    # Print only diskgroups specified by $alias.
    asmcmdbase_lsdg_print ($dbh, $alias, \%args);

    return;
  }

  # Prepare column widths for printing.
  asmcmdbase_ls_init_col_wid(\%min_col_wid);         # Min width of headers. #
  asmcmdshare_ls_calc_min_col_wid(\@entry_list, \%min_col_wid);
                                                  # Min width of all values. #

  # Get list of subdirs for each directory entry.  Note that we loop through
  # @entry_list twice here; first time to get all the sub-entries so that the
  # minimum column width can be calculated.  We then save the sub-entries in
  # a hash so that the second loop and print them without having to run the 
  # SQL again.
  for ($i = 0; $i < @entry_list; $i++) 
  {
    if (($entry_list[$i]->{'alias_directory'} eq 'Y') &&
        (!defined ($args{'d'}))) 
    {
      my @list;
      @list = asmcmdbase_ls_get_subdirs($dbh, $entry_list[$i]->{'group_number'},
                                        $entry_list[$i]->{'reference_index'},
                                        $time_format, \%args, \%min_col_wid, 
                                        $cur_date);

      # Save the list of sub-entries in a hash so that next for loop we don't
      # have to call asmcmdbase_ls_get_subdirs again. 
      $subdirs{ $entry_list[$i]->{'reference_index'} } = \@list;
    }
  }

  # Print column headers, now that we have the correct minimum width.
  # Print iff all of the following numbers are true:
  # 1) either -l or -s are set;
  # 2) --suppressheader is not set;
  # 3) any one or more of the following letters is true:
  #    a) -d is set;
  #    b) the first path matched points to a file;
  #    c) asmcmdshare_normalize_path() returns more than one path;
  #    d) the only path returned by asmcmdshare_normalize_path() is a directory,
  #       it has at least one entry in it. 
  if ((defined ($args{'l'}) || defined ($args{'s'}) ||
       defined($args{'permission'})) &&
      (!defined ($args{'suppressheader'})) &&
      ((defined ($args{'d'}))                       ||
       ($entry_list[0]->{'alias_directory'} eq 'N') ||
       (@entry_list > 1)                            ||
       (@{ $subdirs{ $entry_list[0]->{'reference_index'} } } != 0)))
  {
    asmcmdbase_ls_print_hdr (\%args, \%min_col_wid);
  }

  # Print one row at a time.
  for ($i = 0; $i < @entry_list; $i++) 
  {
    if (($entry_list[$i]->{'alias_directory'} eq 'Y') &&
        (!defined ($args{'d'}))) 
    {
      # Print the directory and its sub-entries if it's a dir and no -d. #
      if (@entry_list > 1) 
      {
        asmcmdshare_print("\n" . $entry_list[$i]->{'path'} . "/:\n");
      }

      asmcmdbase_ls_subdir_print($dbh, 
                             $subdirs{ $entry_list[$i]->{'reference_index'} },
                             \%args, \%min_col_wid);
    }
    else 
    {                                    # Otherwise print the entry itself. #
      asmcmdbase_ls_print($entry_list[$i], \%args, \%min_col_wid);
    }
  }

  return;
}

########
# NAME
#   asmcmdbase_process_find
#
# DESCRIPTION
#   This top-level routine processes the find command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_find 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (@match);                       # Array of hashes of all entry matches. #
  my ($ret);                    # asmcmdshare_parse_int_args() return value. #
  my ($path);            # Conduct search under this path, wildcard allowed. #
  my ($search);                           # Search string, wildcard allowed. #
  my ($type);                       # File type to search for, if -t is set. #
  my ($iter);                      # Iteration variable for foreach() loops. #

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

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  # Get type if --type is set. #
  $type = $args{'type'} if (defined($args{'type'}));    
  ($path, $search) = @{$args{'find'}};

  # Run the internal find routine.
  @match = asmcmdbase_find_sql($dbh, $path, $search, $type, undef, 0, 1);
  return if (@match == 0);

  # Sort entries
  @match = sort { $a->{'full_path'} cmp $b->{'full_path'} } @match;

  # Print each entry.
  foreach $iter (@match)
  {
    $iter->{'full_path'} .= '/' if ($iter->{'alias_directory'} eq 'Y');
    asmcmdshare_print("$iter->{'full_path'}\n");
  }
  return;
}

########
# NAME
#   asmcmdbase_process_du
#
# DESCRIPTION
#   This top-level routine processes the du command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_du 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my ($ret);                     # temporary return value from a subroutine. #
  my (@match);                       # Array of hashes of all entry matches. #
  my (%file_info);      # Hash of v$asm_file column values for file entries. #
  my ($dir);  # User-entered directory path, under which du looks for files. #
  my ($i);                             # Iteration variable for for() loops. #
  my ($uMB); # Total amount of space used in MB, not counting redund copies. #
  my ($mirUMB);          # Same as $uMB, but accouting for redundant copies. #
  my ($uMBBInt) = Math::BigInt->new('0');             # BigInt copy of $uMB. #
  my ($mirUMBBInt) = Math::BigInt->new('0');       # BigInt copy of $mirUMB. #
  my ($redund) = ' ';    # Redundancy value for files; UNPROT, MIRROR, HIGH. #
  my ($redundBInt);                       # BigInt copy of redundancy value. #
  my ($spaceBInt);              # Column 'space' from v$asm_file, in BigInt. #
  my ($oneMBBInt) = Math::BigInt->new('1048576');          # BigInt for 1MB. # 
  my ($resBInt);                      # Result BigInt for BigInt operations. #
  my ($perlv);                                # Current Perl Version Number. #
  my ($print_string);
  # Get option parameters, if any.
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  if(defined $args{'du'})
  {
    ($dir) = @{$args{'du'}};
  }
  else 
  {
    $dir = '';
  }

  # See if path is valid. asmcmdbase_find_sql returns empty array even in 
  # failure.  This check helps to differentiate valid empty dir against failure.
  asmcmdshare_normalize_path ($dbh, $dir, 0, \$ret);
  return if ($ret == -1);

  # Find all system alias only under $dir.
  @match = asmcmdbase_find_sql($dbh, $dir, '*', undef, undef, 1, 0);
  # bug19468869: If no system alias found, print du output as 0.
  if (@match == 0)
  {
    goto print_op;
  }

  # Calculate space used one file at at time and tally up the total.
  for ($i = 0; $i < @match; $i++) 
  {
    # Get file information from v$asm_file.
    asmcmdshare_get_file($dbh, $match[$i]->{'group_number'}, 
                         $match[$i]->{'file_number'}, undef, \%file_info);

    # The files could be deleted after their name is collected in 
    # the array @match but before information could be collected from
    # from the function asmcmdshare_get_file.
    if (!defined ($file_info{'space'}) ||
        !defined ($file_info{'redundancy'})) {
        asmcmdshare_trace(2, "NOTE: Could not get information about the file"
                             ." $match[$i]->{'full_path'}. It could be"
                             ." deleted.", 'y', 'n'); 
        next;
    }

    # Must use Math::BigInt to preserve accuracy.
    $spaceBInt = Math::BigInt->new( $file_info{'space'} );

    # Divide by 1MB to get units in MB.
    $resBInt = $spaceBInt / $oneMBBInt;
    $spaceBInt = $resBInt;

    # Add to mirrored total
    $resBInt = $mirUMBBInt + $spaceBInt;
    $mirUMBBInt = $resBInt;

    # Calculate space used when not counting mirrored copies.
    $redund = $file_info{'redundancy'};
    if ('UNPROT' eq $redund) {
       $redundBInt = Math::BigInt->new("1");
    } elsif ('MIRROR' eq $redund ) {
       $redundBInt = Math::BigInt->new("2");
    } else {
       $redundBInt = Math::BigInt->new("3");
    }
    
    $resBInt = $spaceBInt / $redundBInt;
    $spaceBInt = $resBInt;

    # Add to unmirrored total.
    $resBInt = $uMBBInt + $spaceBInt;
    $uMBBInt = $resBInt;
  }

print_op:
  # Get Perl version number.  The syntax for the members of Math::BigInt
  # between versions is neither backward nor forward compatible.  Certain
  # routines exist in one version but not another, and vice versa.  Other
  # routines changed names over different version number.  Some routines' 
  # definition even changed.  All of this inconsistency is a source of major 
  # headache.  
  # 
  # The ideal is to check the version number of Math::BigInt; however, the 
  # older version of BigInt, which is bundled with Perl 5.6.1 and 10gR1, does
  # not offer any capability to retrieve the version number.
  # 
  # Although checking the Perl version number is not foolproof, 
  # it's the best we can do in order to decide which routines to call.
  $perlv = $];

  if ($perlv <= 5.006001)
  {                                   # Perl 5.6.1 (or earlier) as in 10gR1. #
    $uMB = $uMBBInt->stringify();                 # Covert back to a string. #
    $mirUMB = $mirUMBBInt->stringify();
  }
  else
  {                                     # Any Perl version later than 5.6.1. #
    $uMB = $uMBBInt->bstr();                      # Covert back to a string. #
    $mirUMB = $mirUMBBInt->bstr();
  }

  $uMB =~ s,^\+,,;               # Trim off the leading '+' (positive sign). #
  $mirUMB =~ s,^\+,,;
 
  if (! defined ($args{'suppressheader'})) 
  {
    $print_string = sprintf ("%7s%20s\n", 'Used_MB', 'Mirror_used_MB');
    asmcmdshare_print($print_string);
  }

  $print_string = sprintf ("%7s%20s\n", $uMB, $mirUMB);
  asmcmdshare_print($print_string);

  return;
}

########
# NAME
#   asmcmdbase_process_lsdg
#
# DESCRIPTION
#   This top-level routine processes the lsdg command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.  This routine does the 
#   equivalent of 'ls -ls +'.
########
sub asmcmdbase_process_lsdg 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($gname);                                    # User-entered group name. #

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

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  $gname = undef;
  ($gname) = @{$args{'lsdg'}} if(defined($args{'lsdg'}));

  # lsdg always returns all columns, so both -l and -s flags are needed.
  $args{'l'} = 'l';
  $args{'s'} = 's';

  asmcmdbase_lsdg_print ($dbh, $gname, \%args);

  return;
}

########
# NAME
#   asmcmdbase_process_lsct
#
# DESCRIPTION
#   This top-level routine processes the lsct command.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() can call this routine.
########
sub asmcmdbase_process_lsct
{
  my $dbh    = shift;
  my $global = 0;                               # True iff query global view. #
  my $inst   = undef;
  my $osid   = undef;
  my $row    = '';                     # One row of one client, for printing. #
  my $sid    = undef;
  my $gname;                                # Group name, as entered by user. #
  my $iter;                         # Iteration variable for foreach() loops. #
  my $print_string;
  my $ret;                        # asmcmdbase_parse_int_args() return value. #
  my $tgt;
  my %args;                                # Argument hash used by getopts(). #
  my %min_col_wid;                         # Minimum column width to display. #
  my @client;                   # Array of hashes of clients in v$asm_client. #
  my @header;
  my @rowval;

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

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  if ($args{'target'})
  {
    $tgt                         = $args{'target'};
    $asmcmdglobal_hash{'target'} = $tgt;
    $inst                        = 'ios' if ($tgt =~ m/IOS/i);
    $sid                         = asmcmdshare_set_instance($tgt);

    if (!defined($sid))
    {
      my @eargs = ("target", $tgt);
      # ASMCMD-8608 "invalid value for '%s' option: %s"
      asmcmdshare_error_msg(8608, \@eargs);
      return 0;
    }

    return 0 if ($sid eq '');

    asmcmdbase_disconnect($dbh);
    if ($ENV{'ORACLE_SID'} ne $sid)
    {
      $osid              = $ENV{'ORACLE_SID'};
      $ENV{'ORACLE_SID'} = $sid;
    }
    $dbh = asmcmdbase_connect(undef);

    if (!defined($dbh))
    {
      asmcmdshare_trace(1, "failed to connect to target instance " .
                        "[$ENV{'ORACLE_SID'}]", 'y', 'n');
    }
  }

  # Check for the -g flag.
  if (defined($args{'g'}))
  {
    $global = 1;
  }

  ($gname) = @{$args{'lsct'}} if (defined($args{'lsct'}));
  @client  = asmcmdbase_get_ct($dbh, $gname, $global, $inst);
  return if (@client == 0);

  @client = sort { $a->{'instance_name'} cmp $b->{'instance_name'} } @client;

  $min_col_wid{'db_name'}            = length('DB_Name');
  $min_col_wid{'status'}             = length('Status');
  $min_col_wid{'software_version'}   = length('Software_Version');
  $min_col_wid{'compatible_version'} = length('Compatible_version');
  $min_col_wid{'instance_name'}      = length('Instance_Name');
  $min_col_wid{'group_name'}         = length('Disk_Group');

  asmcmdshare_ls_calc_min_col_wid (\@client, \%min_col_wid);

  if ($global)
  {
    $row = "%11s  ";
    push @header, ('Instance_ID');
  }

  # First print header.  These new columns are 10gR2 or later only.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) >= 0 )
  { # For 10gR2 compatibility, we have some new columns in v$asm_client. #
    $row .= "%-" . $min_col_wid{'db_name'}            . "s  "  .
            "%-" . $min_col_wid{'status'}             . "s  "  .
            "%"  . $min_col_wid{'software_version'}   . "s  "  .
            "%"  . $min_col_wid{'compatible_version'} . "s  "  .
            "%-" . $min_col_wid{'instance_name'}      . "s  "  .
            "%-" . $min_col_wid{'group_name'}         . "s\n";

    push @header, ('DB_Name', 'Status', 'Software_Version',
                   'Compatible_version', 'Instance_Name', 'Disk_Group');

    if (!defined ($args{'suppressheader'}))
    {
      $print_string = sprintf($row, @header);
      asmcmdshare_print($print_string);
    }
  }
  else
  { # For 10gR1 backward-compatibility, no new columns in v$asm_client. #
    $row .= "%-" . $min_col_wid{'db_name'}       . "s  "  .
            "%-" . $min_col_wid{'status'}        . "s  "  .
            "%-" . $min_col_wid{'instance_name'} . "s  "  .
            "%-" . $min_col_wid{'group_name'}    . "s\n";

    $row .= "%-8s  %-12s  %-20s %-s\n";
    push @header, ('DB_Name', 'Status', 'Instance_Name', 'Disk_Group');

    if (!defined ($args{'suppressheader'}))
    {
      $print_string = sprintf ($row, @header);
      asmcmdshare_print($print_string);
    }
  }

  # Print one row at a time.
  foreach $iter (@client)
  {
    @rowval = ();
    if ($global)
    {
      push @rowval, ($iter->{'inst_id'});
    }

    if (asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                $ASMCMDGLOBAL_VER_10gR2) >= 0)
    {                                                    # >= 10gR2 version. #
      push @rowval, ($iter->{'db_name'}, $iter->{'status'},
                     $iter->{'software_version'},
                     $iter->{'compatible_version'},
                     $iter->{'instance_name'},
                     $iter->{'group_name'});
      $print_string = sprintf $row, @rowval;
      asmcmdshare_print($print_string);
    }
    else
    {                                                       # 10gR1 version. #
      push @rowval, ($iter->{'db_name'}, $iter->{'status'},
                     $iter->{'instance_name'},
                     $iter->{'group_name'});
      $print_string = sprintf $row, @rowval;
      asmcmdshare_print($print_string);
    }
  }

  if (defined($osid))
  {
    asmcmdbase_disconnect($dbh);
    $ENV{'ORACLE_SID'}           = $osid;
    $asmcmdglobal_hash{'target'} = 'ASM';
    $dbh                         = asmcmdbase_connect(undef);

    if (!defined($dbh))
    {
      asmcmdshare_trace(1, "failed to connect to original target " .
                        "[$ENV{'ORACLE_SID'}]", 'y', 'n');
    }
  }
  return;
}

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

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

  return $succ;
}

########
# NAME
#   asmcmdbase_is_remote_syntax
#
# DESCRIPTION
#   To check whether given string is of remote-file syntax
#
# PARAMETERS
#   file (IN) - name of the file
#
# RETURNS
#   1 if remote syntax and 0 if not
#
# NOTES:  Checks with Windows file name syntax also.
###########
sub asmcmdbase_is_remote_syntax
{
  my ($file) = shift ;               # file name to check for.
  my ($remote) = 0 ; 

  if ( $^O =~ /win/i )
  {
    #windows OS (both 32 & 64 bit OS).
    # the valid syntax are x:\dir\file & \\server\share\dir\file

    if ((($file !~ /^[a-z]:/i) &&  ($file !~ /^\\\\/)) && ($file =~ m':'))
    {
      # not starting with x: or \\ and found a ':' -> 
      # this is of format usr@server.port.inst:file used for remote syntax
      $remote = 1 ;
    }
  }
  else
  {
    $remote = 1  if ($file =~ m':');
  }
  return $remote ;
}

########
# NAME
#   asmcmdbase_process_cp
#
# DESCRIPTION
#   This top-level routine processes the cp command.
#
# PARAMETERS
#   dbh   (IN) - initialize database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() calls this routine.  
########
sub asmcmdbase_process_cp
{
  my $dbh  = shift;                                    # local auto-variables #
  my $osid = undef;
  my $sid  = undef;
  my $dst;
  my $ret;
  my $src;
  my $tgt;
  my %args;

  # Get option parameters 
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);
  
  # Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);
  
  if ($args{'target'})
  {
    $tgt = $args{'target'};
    $sid = asmcmdshare_set_instance($tgt);
    if (!defined($sid))
    {
      my @eargs = ("target", $tgt);
      # ASMCMD-8608: "invalid value for '%s' option: %s"
      asmcmdshare_error_msg(8608, \@eargs);
      return 0;
    }

    return 0 if ($sid eq '');

    if ($ENV{'ORACLE_SID'} ne $sid)
    {
      asmcmdbase_disconnect($dbh);
      $osid = $ENV{'ORACLE_SID'};
      $ENV{'ORACLE_SID'} = $sid;
      $dbh = asmcmdbase_connect(undef);
    }
  }

  asmcmdbase_cp_core($dbh, %args);

  if (defined($osid))
  {
    asmcmdbase_disconnect($dbh);
    $ENV{'ORACLE_SID'} = $osid;
    $dbh = asmcmdbase_connect(undef);
  }
}

########
# NAME 
#   asmcmdbase_process_setsparseparent
#
# DESCRIPTION
#   This top-level routine processes the setsparseparent command.
#
# PARAMETERS
#   dbh   (IN) - initialize database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_cmd() calls this routine.
########
sub asmcmdbase_process_setsparseparent
{
  my ($dbh) = shift;                         # local auto-variables #
  my (%args);
  my ($ret);
  # get option parameters
  $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args);
  return unless defined ($ret);

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);

  asmcmdbase_set_sparse_parent($dbh, %args);
  return;
}

#########
# NAME
#   asmcmdbase_process_showclustermode
#
# DESCRIPTION
#   To get the cluster mode
#
# PARAMETERS
#  -NONE-
#
# RETURNS
#  Cluster mode
###########
sub asmcmdbase_process_showclustermode
{
  my ($op) = "op=getclstype";
  my ($mode);

  # Environment variable 'ORACLE_HOME' will be set, otherwise
  # ASMCMD init code will bail out.

  # directories in which kfod can be found.
  my (@patharr) =("$ENV{'ORACLE_HOME'}/bin/",
                  "$ENV{'ORACLE_HOME'}/rdbms/bin/");

  my (@result) = asmcmdshare_execute_tool ( "kfod", ".exe", $op, \@patharr);
  $mode = $result[0];   # expecting the first line output only.
  chomp($mode);     # Remove the newline character at the end of the string
  asmcmdshare_print("$mode\n");
}

#######
# NAME
#   asmcmdbase_process_showpatches 
# 
# DESCRIPTION
#   This routine displayes a list of patches applied
#
# PARAMETERS
#   dbh (IN)  - initialized database handle, can be null.
#
# RETURNS
#   NONE
#######
sub asmcmdbase_process_showpatches
{
  my $dbh = shift;
  my %args;
  my @result;
  my $line;

  # Environment variable 'ORACLE_HOME' will be set, otherwise
  # ASMCMD init code will bail out.

  # directories in which kfod and crsctl can be found.
  my @patharr =("$ENV{'ORACLE_HOME'}/bin/",
                "$ENV{'ORACLE_HOME'}/rdbms/bin/");


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

  if (defined($args{'l'}))
  {
    # get detailed output (includes release patch level, list of patches, and
    # release patch string [i.e. vsnnum_full])
    @result = asmcmdshare_execute_tool("crsctl",
                                       ".exe",
                                       "query crs releasepatch",
                                       \@patharr);
    foreach $line (@result)
    {
      # replace "Oracle Clusterware" with "Oracle ASM" if we're not displaying
      # an error
      if ($asmcmdglobal_hash{'utilsucc'} eq "true")
      {
        $line =~ s/Oracle Clusterware/Oracle ASM/g;
      }
      asmcmdshare_print($line);
    }
  }
  else
  {
    @result = asmcmdshare_execute_tool("kfod",
                                       ".exe",
                                       "op=patches",
                                       \@patharr);
    if ($asmcmdglobal_hash{'utilsucc'} eq "true")
    {
      foreach $line (@result)
      {
        asmcmdshare_print($line);
      }
    }
  }
}

######
# NAME
#   asmcmdbase_process_showversion
#
# DESCRIPTION
#   This routine displays the Rolling migration version
#
# PARAMETERS
#   $dbh(IN)  - initialized database handle, can be null.
#
# RETURNS
#   NONE
# 
# NOTE
#   If there is no ASM instance (dbh is Null), then the query for
#   releasepatch will not be run and only "ASM version" & 
#   softwarepatch will be printed.
#######
sub asmcmdbase_process_showversion
{
  my $dbh = shift;
  my $result = "";
  my %args;
  my $qry;
  my ($sth,$row);
  my @lines;
  my $line;
  my @patharr;

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

  # Environment variable 'ORACLE_HOME' will be set, otherwise ASMCMD init code
  # will bail out.

  # directories in which kfod, crsctl, ocrconfig, etc. can be found
  @patharr = ("$ENV{'ORACLE_HOME'}/bin/",
              "$ENV{'ORACLE_HOME'}/rdbms/bin/");

  if (defined($args{'active'}))
  {
    # the cluster has to be configured in order to retrieve active version, so
    # bail out if it isn't
    @lines = asmcmdshare_execute_tool("ocrcheck",
                                      ".exe",
                                      "-config",
                                      \@patharr);

    if ($asmcmdglobal_hash{'utilsucc'} eq "true")
    {
      @lines = asmcmdshare_execute_tool("crsctl",
                                        ".exe",
                                        "query crs activeversion -f",
                                        \@patharr);
      foreach $line (@lines)
      {
        # replace "Oracle Clusterware" with "Oracle ASM" if we're not
        # displaying an error
        if ($asmcmdglobal_hash{'utilsucc'} eq "true")
        {
          $line =~ s/Oracle Clusterware/Oracle ASM/g;
        }
        asmcmdshare_print($line);
      }
    }
    else
    {
      # display the error from 'ocrcheck -config'
      foreach $line (@lines)
      {
        asmcmdshare_print($line);
      }
    }
  }
  else
  {
    # display vsnnum
    asmcmdshare_print ("ASM version         : $asmcmdglobal_hash{'acver'}\n");

    # releasepatch here is equivalent to the active patch level in crsctl
    if (defined($args{'releasepatch'}))
    {
      # Only run the query when there is an ASM instance.
      if (defined($dbh))
      {
        $qry  = "SELECT SYS_CONTEXT";
        $qry .= "('sys_cluster_properties','current_patchlvl')";
        $qry .= " as PATCHLVL from dual";

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

        asmcmdshare_print("Release patchlevel  : $row->{'PATCHLVL'}\n");
      }
      else
      {
        asmcmdshare_print("Information about release patchlevel is "
                          ."unavailable since no ASM instance connected\n");
      }
    }

    # note that softwarepatch here is equivalent to the release patch level in
    # crsctl
    if (defined($args{'softwarepatch'}))
    {
      @lines = asmcmdshare_execute_tool("kfod",
                                        ".exe",
                                        "op=patchlvl nohdr=TRUE",
                                        \@patharr);

      if ($asmcmdglobal_hash{'utilsucc'} eq "true")
      {
        foreach $line (@lines)
        {
          chomp($line);
          asmcmdshare_print("Software patchlevel : $line\n");
        }
      }
    }
  }
}


#######
# NAME
#   asmcmdbase_get_cluster_state
#
# DESCRIPTION
#   Queries ASM instance for cluster state
#
# PARAMETERS
#   $dbh (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Cluster state (string)
#######
sub asmcmdbase_get_cluster_state
{
  my $dbh = shift;
  my $qry;
  my $sth;
  my $row;

  $qry  = "SELECT SYS_CONTEXT('sys_cluster_properties','cluster_state') ";
  $qry .= "as STATE from dual";

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

  return $row->{'STATE'};
}


#######
# NAME
#   asmcmdbase_process_showclusterstate
#
# DESCRIPTION
#   To display rolling migration state
#
# PARAMETERS
#   $dbh (IN)  - initialized database handle, must be non-null.
#
# RETURNS
#   NONE
#######
sub asmcmdbase_process_showclusterstate
{
  my $dbh   = shift;
  my $state = asmcmdbase_get_cluster_state($dbh);
  asmcmdshare_print("$state\n");
  return;
}

########
# NAME
#   asmcmdbase_cp_core
#
# DESCRIPTION
#   This routine processes the cp command and the pwcopy command.
#
# PARAMETERS
#   dbh   (IN) - initialize database handle, must be non-null.
#
# RETURNS
#   Null.
#
########
sub asmcmdbase_cp_core
{
  use Text::ParseWords;
  my $dbh = shift;                                   # local auto-variables #
  my $ret;
  my $qry;
  my $i;

  my (%args) = @_;                                               # cmd args #
  my @arg_tokens;

  my @src_strs = ();                                        # src file args #
  my @src_paths;                                           # src file paths #
  my @src_fdata;                                            # src file data #
  my @src_norm_paths;
  my $src_path;

  my $tgt;                                                # instance target #
  my $tgt_str;                                       # target file/dir args #
  my $tgt_path;

  my $dest_dbname;                           # Root directory for sys alias #

  my $sp_merge_begin = 0;
  my $sp_merge_end   = '';

  my $src_remote_inst = 0;
  my $tgt_remote_inst = 0;
  my $remote_conn_str = '';

  my $src_dbh = $dbh;
  my $tgt_dbh = $dbh;
  my $pos;

  my $src_rem_ct = 0;       # number of source remote connection parameters #
  my $driver;
  
  my $success_files = {}; # reference to a hash storing the list of src and #
                          # dest files that were successfully copied.       #

  if (defined($args{'target'}))
  {
    $tgt = ls($args{'target'});
  }

  # if optional --service parameter is provided, set it in global for later use
  if (defined($args{'service'}))
  {
    $asmcmdglobal_hash{'service'} = $args{'service'};
  }
  else
  {
    # use +ASM as default if no service was provided.  Otherwise the
    # service name from previous CP will be used, and could be wrong.
    $asmcmdglobal_hash{'service'} = "+ASM" ;
  }

  if (defined($args{'port'}))
  {
    $asmcmdglobal_hash{'port'} = $args{'port'};
  }
  else
  {
    $asmcmdglobal_hash{'port'} = "1521" ;
  }

  if (defined($args{'dest_dbname'}))
  {
    $dest_dbname = $args{'dest_dbname'};
  }

  if (defined($args{'sparse'}))
  {
    $asmcmdglobal_hash{'sparse'} = "1";
  }
  else
  {
    $asmcmdglobal_hash{'sparse'} = "0";
  }

  if (defined($args{'sparse_merge_begin'}) && 
      defined($args{'sparse_merge_end'}))
  {
    $asmcmdglobal_hash{'sparse_merge_begin'} = "1";
    $asmcmdglobal_hash{'sparse_merge_end'} = $args{'sparse_merge_end'};
    $sp_merge_begin = 1;
    $sp_merge_end = $args{'sparse_merge_end'};
  }
  else
  {
    if (defined($args{'sparse_merge_begin'}) || 
        defined($args{'sparse_merge_end'}))
    {
      asmcmdshare_error_msg(8034, undef);
      return $success_files;
    }
    $asmcmdglobal_hash{'sparse_merge_begin'} = "0";
    $asmcmdglobal_hash{'sparse_merge_end'} = '';
  }

  asmcmdshare_trace(3, "NOTE: The service used is "
          ."$asmcmdglobal_hash{'service'}", 'y', 'n');

  asmcmdshare_trace(3, "NOTE: The port used is "
          ."$asmcmdglobal_hash{'port'}", 'y', 'n');

  asmcmdshare_trace(3, "NOTE: sparse option is set to "
          ."$asmcmdglobal_hash{'sparse'}", 'y', 'n');

  if ($sp_merge_begin == 1)
  {
    asmcmdshare_trace(3, "NOTE: sparse_merge_begin option is set to "
           ."$asmcmdglobal_hash{'sparse_merge_begin'}", 'y', 'n');
    asmcmdshare_trace(3, "NOTE: sparse_merge_end option is set to "
           ."$asmcmdglobal_hash{'sparse_merge_end'}", 'y', 'n');
  }

  if (defined($args{'dbuniquename'}))
  {
    $asmcmdglobal_hash{'dbuniquename'} = $args{'dbuniquename'};
    asmcmdshare_trace(3, "NOTE: dbuniquename is set to " . 
                      $asmcmdglobal_hash{'dbuniquename'}, 'y', 'n');
  }

  # last element is the target
  $tgt_str = pop(@{$args{'cp'}});

  # remaining is the source
  @src_strs = @{$args{'cp'}};

  # sparse merge: we do not support multiple source files in the argument 
  if ($sp_merge_begin == 1 && @src_strs > 1)
  {
    my (@eargs) = ($src_rem_ct, @src_strs);
    asmcmdshare_error_msg(8031, \@eargs);
    return $success_files;
  }

  # we are not supporting multi-src files from a remote instance yet #
  foreach (@src_strs)
  {
    $src_rem_ct ++ if ( 1 == asmcmdbase_is_remote_syntax ($_)) ;
  }

  # more than one source, and one of them is remote
  if ($src_rem_ct > 0 && @src_strs > 1)
  {
    my (@eargs) = ($src_rem_ct, @src_strs);
    asmcmdshare_error_msg(8009, \@eargs);
  }

  if ($src_rem_ct == 1)
  {
    # sparse_merge: source file must be on local ASM instance
    if ($sp_merge_begin == 1)
    {
      my (@eargs) = (@src_strs);
      asmcmdshare_error_msg(8036, \@eargs);
      return $success_files;
    }
    $src_remote_inst = 1;
    $pos = index($src_strs[0], ':');
    if (-1 != $pos )
    {
      $remote_conn_str = substr ($src_strs[0], 0, $pos);
      push (@src_paths, substr ($src_strs[0], $pos + 1, 
            length($src_strs[0]) - $pos - 1));
    }
    else
    {
      $remote_conn_str = $src_strs[0];
      push (@src_paths, "");
    }

    #get and parse connection data
    return $success_files 
            unless defined (asmcmdbase_parse_remote_conn_str($remote_conn_str));
    asmcmdshare_trace(3, "NOTE: Completed parsing source remote connection".
                      " string", 'n', 'n');
  }
  else
  {
    @src_paths = @src_strs;
  }
  
  # Check whether the target is of remote syntax
  $tgt_remote_inst = 1 if ( asmcmdbase_is_remote_syntax ($tgt_str) == 1 ) ;

  # sparse_merge: target must be on local ASM instance
  if ($sp_merge_begin == 1)
  {
    if ($tgt_remote_inst == 1)
    {
      my (@eargs) = ($tgt_str);
      asmcmdshare_error_msg(8032, \@eargs);
      return $success_files;
    }
    else
    {
      # check if file associated with sparse_merge_end is of remote syntax
      if (asmcmdbase_is_remote_syntax($sp_merge_end) == 1)
      {
        my (@eargs) = ($sp_merge_end);
        asmcmdshare_error_msg(8030, \@eargs);
        return $success_files;
      }
    }
  }

  # both src & target can't be of remote instance(s) #
  if ($src_remote_inst == 1 && $tgt_remote_inst == 1)
  {
    my (@eargs) = (@src_strs, $tgt_str);
    asmcmdshare_error_msg(8008, \@eargs);
    return $success_files;
  }

  if ( $tgt_remote_inst  == 1 )
  {
    $pos = index($tgt_str, ':');
    if(-1 != $pos )
    {
      $remote_conn_str = substr($tgt_str, 0, $pos);
      $tgt_path = substr ($tgt_str, $pos + 1, length($tgt_str) - $pos - 1 );
    }
    else
    {
      $remote_conn_str = $tgt_str;
      $tgt_path = "";
    }
   
    # get and parse connection data
    return $success_files 
            unless defined (asmcmdbase_parse_remote_conn_str($remote_conn_str));
    asmcmdshare_trace(3, "NOTE: Completed parsing target remote connection"
                        ." string", 'n', 'n');
  }
  else
  {
    $tgt_path = $tgt_str;
  }

  # assert we don't have src and tgt remote_inst
  {
    my @eargs=();
    asmcmdshare_assert(!($src_remote_inst && $tgt_remote_inst), \@eargs);
  }

  # get remote handler if needed
  if ($src_remote_inst || $tgt_remote_inst)
  {
    # ### remote copy ### #
    my ($remote_dbh, $pswd);
    asmcmdshare_trace(3, "NOTE: Connecting to remote instance.. ", 'n', 'n');
    # connect to remote instance first #
    asmcmdshare_trace(3, "Remote identifier $rident", 'y', 'n');
    $remote_dbh = asmcmdbase_connect($rident, \$driver);
    # added contyp, bug-5402303
    if (!defined ($remote_dbh))
    {
      # Connection failed; record error. #
      my (@eargs) = $rident if(defined($rident));
      my ($msg) = "ERROR:asmcmdbase_process_cp01:Connection Failed ";
      $msg .= "with $driver" if defined($driver);
      asmcmdshare_trace(1, $msg, 'y', 'n');
      asmcmdshare_error_msg(8201, \@eargs);
      return $success_files;
    }
    asmcmdbase_check_insttype($remote_dbh);

    $src_dbh = $remote_dbh if ($src_remote_inst);
    $tgt_dbh = $remote_dbh if ($tgt_remote_inst);
  }

  # If dest_dbname was specified, modify the value of the root directory
  if (defined ($dest_dbname))
  {
    # Only allow alphanumeric characters for the creation of the root 
    # directory of the system alias.
    if ($dest_dbname !~  m/^[a-zA-Z0-9]+$/ )
    {
      my (@eargs) = ($dest_dbname);
      asmcmdshare_error_msg(8037, \@eargs);
      return;
    }

    # If no target has been specified, or is different from ASM, IOS and APX,
    # use ASM as the default.
    if (!defined($tgt) || $tgt !~ m/^(ASM|IOS|APX)$/)
    {
      $tgt = "asm";
    }
    # Set the root directory of the specified target.
    $qry = "alter session set \"_" . $tgt . "_root_directory\"=\"" 
           . $dest_dbname . "\"";
    $ret = asmcmdshare_do_stmt($dbh, $qry);
  }

  # perform file copy and path completion
  $success_files = asmcmdbase_do_file_copy($src_remote_inst, $tgt_remote_inst,
                                           $src_dbh, $tgt_dbh, $remote_conn_str,
                                           \@src_paths, $tgt_path);
  return $success_files;
}

########
# NAME
#   asmcmdbase_set_sparse_parent
#
# DESCRIPTION
#   This routine processes the setsparseparent command.
#
# PARAMETERS
#   dbh   (IN) - initialize database handle, must be non-null.
#
# RETURNS
#   Null.
#
########
sub asmcmdbase_set_sparse_parent
{
  use Text::ParseWords;
  my ($dbh) = shift;                                     # local auto-variables

  my (%args) = @_;                                       # cmd args
  my (@arg_tokens);

  my (@child_strs) = ();                                 # child file args
  my (@child_paths);                                     # child file paths
  my ($parent_str);                                      # parent file args
  my ($parent_path);                                     # parent file path

  my ($parent_rem_inst) = 0;
  my ($child_rem_ct) = 0;         #number of child remote connection parameters
  my ($child_dbh) = $dbh;
  my ($parent_dbh) = $dbh;
  
  my ($success_files) = 0; # count of child files that were successfully linked

  # last element is the sparse parent
  $parent_str = pop(@{$args{'setsparseparent'}});

  # remaining is the child/children
  @child_strs = @{$args{'setsparseparent'}};

  # multi-child files from a remote instance is not supported
  foreach (@child_strs)
  {
    $child_rem_ct ++ if (asmcmdbase_is_remote_syntax ($_) == 1) ;
  }

  # if a child is remote
  if ($child_rem_ct > 0)
  {
    # more than one child, and one of them is remote
    if (@child_strs > 1)
    {
      my (@eargs) = ($child_rem_ct, @child_strs);            
      asmcmdshare_error_msg(8018, \@eargs);
      return $success_files;
    }
    else
    {
      # if a child is remote
      my (@eargs) = (@child_strs);
      asmcmdshare_error_msg(8015, \@eargs);
      return $success_files;
    }
  }
  else
  {
    @child_paths = @child_strs;
  }

  # Check whether the parent is of remote syntax
  $parent_rem_inst = 1 if (asmcmdbase_is_remote_syntax ($parent_str) == 1);

  # parent can't be of remote instance(s)
  if ($parent_rem_inst == 1)
  {
    my (@eargs) = ($parent_str);
    asmcmdshare_error_msg(8019, \@eargs);
    return $success_files;
  }
  else
  {
    $parent_path = $parent_str;
  }
 
 # perform setsparseparent operation and path completion
 $success_files = asmcmdbase_set_sparse_parent_child
                  ($child_dbh, $parent_dbh, \@child_paths, $parent_path);
 return $success_files;
}

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

#################### Internal Command Processing Routines #####################
########
# NAME
#   asmcmdbase_ls_process_file
#
# DESCRIPTION
#   This routine determines the date format for files.  Dates older than six
#   months are displayed in MON DD YYYY format, while others are displayed in 
#   MON DD HH24:MI:SS unless the user specified a different format by using the
#   --time_style option. The routine also finds the corresponding system 
#   filename for an alias and the corresponding alias for a system filename.
#
# PARAMETERS
#   dbh            (IN)     - initialized database handle, must be non-null.
#   entry_info_ref (IN/OUT) - reference to hash of column values for a file or
#                             directory entry.
#   args_ref       (IN)     - reference to hash of user-entered command opions.
#   cur_date       (IN)     - current julian date from dual.
#   file_info      (IN)     - boolean: whether we have file info.
#
# RETURNS
#   Null.
#
# NOTES
#   Does not overwrite existing hashed values in %entry_info.
########
sub asmcmdbase_ls_process_file 
{
  my ($dbh, $entry_info_ref, $args_ref, $cur_date, $file_info) = @_;

  my ($related_alias);     # The user or system alias for its corresponding  #
                                       # system or user alias, respectively. #

  # Calculate dates only if we have file info from v$asm_file.
  if ($file_info)
  {
    # Use separate date format if date older than half a year.
    if (($cur_date - $entry_info_ref->{'julian_date'}) >= $ASMCMDBASE_SIXMONTH) 
    {                  # If older than six months, use 'MON DD YYYY' format. #
      $entry_info_ref->{'date_print'} = $entry_info_ref->{'mod_date'};
    }
    else 
    {                           # Otherwise, use 'MON DD HH24:MI:SS' format. #
      $entry_info_ref->{'date_print'} = $entry_info_ref->{'mod_time'};
    }
  }

  # Find system alias only if we have info from v$asm_file, i.e., if we're
  # doing ls -l or ls -s.
  if ($file_info && $entry_info_ref->{'system_created'} eq 'N') 
  {                      # If user-alias for file, than obtain system-alias. #
    $related_alias = asmcmdbase_get_alias_path ($dbh, 
                                             $entry_info_ref->{'group_number'},
                                             $entry_info_ref->{'file_number'}, 
                                             'Y');

    # Always display in form 'user alias => system alias' for user aliases.
    $entry_info_ref->{'name_print'} .= " => $related_alias";
  }
  elsif (defined ($args_ref->{'absolutepath'}) && 
         ($entry_info_ref->{'system_created'} eq 'Y')) 
  {
    # If system-alias and -a, then find corresponding user-alias, if any. #
    #   In the (near) future, v$file joined with v$alias will not return volume
    #   information. Until then, we'll need the code below to supress volume 
    #   info.
    my($name, $not_used) = split /\./, $entry_info_ref->{'name_print'}, 2;
    if (($name eq 'volume') || ($name eq 'DRL'))
    {
      # do not print volume information
      return;
    }

    $related_alias = asmcmdbase_get_alias_path ($dbh, 
                                             $entry_info_ref->{'group_number'},
                                             $entry_info_ref->{'file_number'}, 
                                             'N');

    $entry_info_ref->{'name_print'} = "$related_alias => " .
      $entry_info_ref->{'name_print'};
  }

  return;
}

########
# NAME
#   asmcmdbase_ls_get_subdirs
#
# DESCRIPTION
#   This routine is a wrapper function for asmcmdshare_get_subdirs() for ls. It
#   retrieves all entries under a directory by calling 
#   asmcmdshare_get_subdir(), and sorts the entries based on the -t and/or 
#   -r flags.  It also calculates the minimum column width of each column value
#   based on the length of each of the results returned by
#   asmcmdbase_get_subdirs().
#
# PARAMETERS
#   dbh             (IN)  - initialized database handle, must be non-null.
#   gnum            (IN)  - group number of the directory.
#   par_id          (IN)  - parent index of the directory.
#   time_format     (IN)  - format for the datetime.
#   args_ref        (IN)  - reference to hash of ls options entered by user.
#   min_col_wid_ref (OUT) - reference to hash of minimum column width.
#   cur_date        (IN)  - current julian date from dual.
#
# RETURNS
#   An array of hashes, each hash containing column values of a file or 
#   directory from v$asm_alias and v$asm_file.
#
#   ls is tricky because it can list entries in two levels: 
#     1) 'ls <file>' or 'ls -d <dir>'
#     2) 'ls <dir>'
#
#   In case 1), ls lists the specified alias entry itself, whereas in 2), ls
#   lists the contents of <dir>.  For 1), asmcmdbase_process_ls() calls 
#   asmcmdbase_ls_print() directly, whereas in 2), asmcmdbase_process_ls() 
#   calls asmcmdbase_ls_get_subdirs() and asmcmdbase_ls_subdir_print() for 
#   each <dir>, and asmcmdbase_ls_subdir_print() calls asmcmdbase_ls_print().
########
sub asmcmdbase_ls_get_subdirs 
{
  my ($dbh, $gnum, $par_id, $time_format, $args_ref, $min_col_wid_ref, 
      $cur_date) = @_;

  my (@entries);# Entries under a dir returned by asmcmdshare_get_subdirs(). #
  my ($get_file_info) = 0;     # boolean: true if we want file info as well. #
  my ($i);                             # Iteration variable for for() loops. #

  # Get file info only if we need it.
  $get_file_info = 1 if (defined($args_ref->{'l'}) || 
                         defined($args_ref->{'s'}) ||
                         defined($args_ref->{'permission'}));

  # Get all entries under a directory in a diskgroup.
  asmcmdshare_get_subdirs ($dbh, \@entries, $time_format, $gnum, undef, 
                           $par_id, undef, undef, 0, $get_file_info);

  # Obtain more information on each entry, if available.
  for ($i = 0; $i < @entries; $i++) 
  {
    # By default, the name to be print by ls is the "name" column from
    # v$asm_alias.  However, asmcmdbase_ls_process_file() can modify this 
    # print value if the entry is a file and not a directory.  For instance, 
    # user aliases for files are printed in the form 
    # 'user_alias => system_alias'.
    $entries[$i]->{'name_print'} = $entries[$i]->{'name'};

    # If it's a file, then process v$asm_file info.
    if ($entries[$i]->{'alias_directory'} eq 'N') 
    {
      asmcmdbase_ls_process_file ($dbh, $entries[$i], $args_ref, $cur_date,
                                  $get_file_info);
    }
  }

  # Sort @entries according to -t and -r flags (4 combinations).
  if (defined ($args_ref->{'t'}) && defined ($args_ref->{'reverse'})) 
  {
    @entries = sort asmcmdbase_time_backward @entries;
  }
  elsif (defined ($args_ref->{'t'}) && !defined ($args_ref->{'reverse'})) 
  {
    @entries = sort asmcmdbase_time_forward @entries;
  }
  elsif (!defined ($args_ref->{'t'}) && defined ($args_ref->{'reverse'})) 
  {
    @entries = sort asmcmdbase_name_backward @entries;
  }
  else 
  {
    @entries = sort asmcmdbase_name_forward @entries;
  }

  # Prepare column widths for printing.
  asmcmdshare_ls_calc_min_col_wid(\@entries, $min_col_wid_ref);

  return @entries;
}

########
# NAME
#   asmcmdbase_ls_subdir_print
#
# DESCRIPTION
#   This routine prints each entry in @list by calling asmcmdbase_ls_print() 
#   on each entry.
#
# PARAMETERS
#   dbh             (IN) - initialized database handle, must be non-null.
#   list_ref        (IN) - reference to array of entries to be printed.
#   args_ref        (IN) - reference to hash of ls options entered by user.
#   min_col_wid_ref (IN) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdbase_process_ls() calls this routine, after first calling 
#   asmcmdbase_ls_get_subdirs() to retrieve all entries under a directory.  
#
#   ls is tricky because it can list entries in two levels: 
#     1) 'ls <file>' or 'ls -d <dir>'
#     2) 'ls <dir>'
#
#   In case 1), ls lists the specified alias entry itself, whereas in 2), ls
#   lists the contents of <dir>.  For 1), asmcmdbase_process_ls() calls 
#   asmcmdbase_ls_print() directly, whereas in 2), asmcmdbase_process_ls() 
#   calls asmcmdbase_ls_get_subdirs() and asmcmdbase_ls_subdir_print() for each 
#   <dir>, and asmcmdbase_ls_subdir_print() calls asmcmdbase_ls_print().
########
sub asmcmdbase_ls_subdir_print 
{
  my ($dbh, $list_ref, $args_ref, $min_col_wid_ref) = @_;

  my ($i);                             # Iteration variable for for() loops. #

  # Print one line at a time.
  for ($i = 0; $i < @{$list_ref}; $i++) 
  {
    asmcmdbase_ls_print($list_ref->[$i], $args_ref, $min_col_wid_ref);
  }

  return;
}

########
# NAME
#   asmcmdbase_ls_print
#
# DESCRIPTION
#   This routine prints one single alias entry, whether a directory or a file.
#
# PARAMETERS
#   dbh             (IN) - initialized database handle, must be non-null.
#   entry_ref       (IN) - reference to hash of column values of an entry.
#   args_ref        (IN) - reference to hash of ls options entered by user.
#   min_col_wid_ref (IN) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   asmcmdbase_process_ls() can call this routine directly or indirectly
#   through asmcmdbase_ls_subdir_print().
########
sub asmcmdbase_ls_print 
{
  my ($entry_ref, $args_ref, $min_col_wid_ref) = @_;

  my $row_l = '';     # printf() format string for ls -l.                     #
  my $row_s = '';     # printf() format string for ls -s.                     #
  my $row_p = '';     # printf() format string for ls -p.                     #
  my $row_n = '';     # printf() format string for alias name.                #
  my $row = '';       # Concatenated printf() format string with all options. #
  my @print_args;
  my $print_string;
  
  # In the (near) future, v$file joined with v$alias will not return volume
  # information. Until then, we'll need the code belowe to supress volume info.
  my($name, $not_used) = split /\./, $entry_ref->{'name_print'}, 2;
  if (($name eq 'volume') || ($name eq 'DRL'))
  {
    # do not print volume information
    return;
  }

  # Set formats based on minimum column widths in %min_col_wid.
  $row_l = "%-" . $min_col_wid_ref->{'type'}       . "s  " .
           "%-" . $min_col_wid_ref->{'redundancy'} . "s  " .
           "%-" . $min_col_wid_ref->{'striped'}    . "s  " ;


  $row_l .= "%-" . $min_col_wid_ref->{'date_print'} . "s  "; 

  $row_l .= "%-" . $min_col_wid_ref->{'system_created'} . "s  ";

  $row_s = "%" . $min_col_wid_ref->{'block_size'} . "s  " .
           "%" . $min_col_wid_ref->{'blocks'} . "s  " .
           "%" . $min_col_wid_ref->{'bytes'} . "s  " .
           "%" . $min_col_wid_ref->{'space'} . "s  ";

  $row_p = "%" . $min_col_wid_ref->{'user'} . "s  " .
           "%" . $min_col_wid_ref->{'group'} . "s  " .
           "%" . $min_col_wid_ref->{'perm'} . "s  ";

  $row_n = "%-s\n";

  # Now concatenate the format strings based on whether -l and/or -s flags.
  $row .= $row_l if (defined ($args_ref->{'l'}));                   # ls -l. #
  $row .= $row_s if (defined ($args_ref->{'s'}));                   # ls -s. #
  $row .= $row_p if (defined ($args_ref->{'permission'}));# ls --permission. #
  $row .= $row_n;                  # Always display alias name; concatenate! #

  if ($entry_ref->{'alias_directory'} eq 'Y') 
  {                                        # Directries have the suffix '/'. #
    $entry_ref->{'name_print'} .= '/';
  }

  #
  # some values may be undefined, if so, init with space
  #
  $entry_ref->{'type'}       = $ASMCMDBASE_SPACE if 
                                        (!defined($entry_ref->{'type'}));
  $entry_ref->{'redundancy'} = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'redundancy'}));
  $entry_ref->{'striped'}    = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'striped'}));
  $entry_ref->{'date_print'} = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'date_print'}));
  $entry_ref->{'space'}      = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'space'}));

  $entry_ref->{'block_size'} = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'block_size'}));
  $entry_ref->{'blocks'}     = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'blocks'}));
  $entry_ref->{'bytes'}      = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'bytes'}));

  $entry_ref->{'user'}       = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'user'}));
  $entry_ref->{'group'}      = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'group'}));
  $entry_ref->{'perm'}       = $ASMCMDBASE_SPACE if
                                        (!defined($entry_ref->{'perm'}));

  @print_args = ();

  if (defined($args_ref->{'l'}))                                    # ls -l.  #
  {
    push (@print_args, $entry_ref->{'type'});
    push (@print_args, $entry_ref->{'redundancy'});
    push (@print_args, $entry_ref->{'striped'});
    push (@print_args, $entry_ref->{'date_print'});
    push (@print_args, $entry_ref->{'system_created'});
  }

  if (defined($args_ref->{'s'}))                                    # ls -s.  #
  {
    my $bsize;
    my $bytes;
    my $space;

    # If the current entry belongs to a directory, the size fields are empty.
    # This function decides to call asmcmdshare_humanBytes if the -h option is
    # set and the entry is not a directory. 
    my $humanize = sub { $_[0] =~ /\d/ && defined($args_ref->{'h'}) };

    $bsize = $humanize->($entry_ref->{'block_size'})
             ? asmcmdshare_humanBytes($entry_ref->{'block_size'})
             : $entry_ref->{'block_size'};

    $bytes = $humanize->($entry_ref->{'bytes'})
             ? asmcmdshare_humanBytes($entry_ref->{'bytes'})
             : $entry_ref->{'bytes'};

    $space = $humanize->($entry_ref->{'space'})
             ? asmcmdshare_humanBytes($entry_ref->{'space'})
             : $entry_ref->{'space'};

    push (@print_args, $bsize);
    push (@print_args, $entry_ref->{'blocks'});
    push (@print_args, $bytes);
    push (@print_args, $space);
  }

  if (defined($args_ref->{'permission'}))                # ls --permission -l. #
  {
    push (@print_args, $entry_ref->{'user'});
    push (@print_args, $entry_ref->{'group'});
    push (@print_args, $entry_ref->{'perm'});
  }

  push(@print_args, $entry_ref->{'name_print'});

  $print_string = sprintf ($row, @print_args);
  asmcmdshare_print($print_string);

  return;
}

########
# NAME
#   asmcmdbase_ls_print_hdr
#
# DESCRIPTION
#   This routine prints the column headers for ls.
#
# PARAMETERS
#   args_ref        (IN) - reference to hash of ls options entered by user.
#   min_col_wid_ref (IN) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
########
sub asmcmdbase_ls_print_hdr 
{
  my ($args_ref, $min_col_wid_ref) = @_;

  my ($hdr_l);                           # printf() format string for ls -l. #
  my ($hdr_s);                           # printf() format string for ls -s. #
  my ($hdr_p);                           # printf() format string for ls -p. #
  my ($hdr_n);                      # printf() format string for alias name. #
  my ($hdr) = '';    # Concatenated printf() format string with all options. #
  my (@print_args);
  my ($print_string);

  # Set formats based on minimum column widths in %min_col_wid.
  $hdr_l = "%-" . $min_col_wid_ref->{'type'}          . "s  " .
           "%-" . $min_col_wid_ref->{'redundancy'}    . "s  " .
           "%-" . $min_col_wid_ref->{'striped'}       . "s  " .
           "%-" . $min_col_wid_ref->{'date_print'}    . "s  " .
           "%-" . $min_col_wid_ref->{'system_created'}. "s  ";

  $hdr_s = "%"  . $min_col_wid_ref->{'block_size'}    . "s  " .
           "%"  . $min_col_wid_ref->{'blocks'}        . "s  " .
           "%"  . $min_col_wid_ref->{'bytes'}         . "s  " .
           "%"  . $min_col_wid_ref->{'space'}         . "s  ";

  $hdr_p = "%-" . $min_col_wid_ref->{'user'}          . "s  " .
           "%-" . $min_col_wid_ref->{'group'}         . "s  " .
           "%-" . $min_col_wid_ref->{'perm'}          . "s  ";

  $hdr_n = "%-s\n";

  # Now concatenate the format strings based on whether -l and/or -s flags.
  $hdr .= $hdr_l if (defined ($args_ref->{'l'}));                # ls -l.     #
  $hdr .= $hdr_s if (defined ($args_ref->{'s'}));                # ls -s.     #
  $hdr .= $hdr_p if (defined ($args_ref->{'permission'}));       # ls --perm. #
  $hdr .= $hdr_n;


  @print_args = ();

  if (defined($args_ref->{'l'}))                                     # ls -l. #
  {
    push (@print_args, $ASMCMDBASE_LS_HDR_TYPE);
    push (@print_args, $ASMCMDBASE_LS_HDR_REDUND);
    push (@print_args, $ASMCMDBASE_LS_HDR_STRIPED);
    push (@print_args, $ASMCMDBASE_LS_HDR_TIME);
    push (@print_args, $ASMCMDBASE_LS_HDR_SYSCRE);
  }

  if (defined($args_ref->{'s'}))                                     # ls -s. #
  {
    push (@print_args, $ASMCMDBASE_LS_HDR_BSIZE);
    push (@print_args, $ASMCMDBASE_LS_HDR_BLOCKS);
    push (@print_args, $ASMCMDBASE_LS_HDR_BYTES);
    push (@print_args, $ASMCMDBASE_LS_HDR_SPACE);
  }

  if (defined($args_ref->{'permission'}))                            # ls pl. #
  {
    push (@print_args, $ASMCMDBASE_LS_HDR_USER);
    push (@print_args, $ASMCMDBASE_LS_HDR_GROUP);
    push (@print_args, $ASMCMDBASE_LS_HDR_PERM);
  }

  push (@print_args, 'Name');

  $print_string = sprintf $hdr, @print_args;
  asmcmdshare_print($print_string);

  # Do not print header for ls with neither -l nor -s.

  return;
}

########
# NAME
#   asmcmdbase_ls_init_col_wid
#
# DESCRIPTION
#   This routine initializes the minimum column width hash with default values
#   for ls.  These default values are determined by the length of the values 
#   of the printed column headers.  These header values are not necessarily 
#   the same as the column names in v$asm_alias or v$asm_file but look 
#   similar.
#
# PARAMETERS
#   min_col_wid_ref (OUT) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   Must call this routine or asmcmdbase_lsdg_init_col_wid() before calling 
#   asmcmdshare_ls_calc_min_col_wid().
########
sub asmcmdbase_ls_init_col_wid 
{
  my $min_col_wid_ref = shift;
  my $min_time_len;

  # Default time format is either 'MON DD YYYY' or 'MON DD HH:MI:SS', which
  # are at most $ASMCMDBASE_DATELEN characters long.  However, if the length
  # of $ASMCMDBASE_LS_HDR_TIME is larger, then that becomes the minimum 
  # length.
  $min_time_len = $ASMCMDBASE_DATELEN;
  $min_time_len = length($ASMCMDBASE_LS_HDR_TIME) 
                  if (length($ASMCMDBASE_LS_HDR_TIME) > $ASMCMDBASE_DATELEN);

  $min_col_wid_ref->{'type'}           = length($ASMCMDBASE_LS_HDR_TYPE);
  $min_col_wid_ref->{'redundancy'}     = length($ASMCMDBASE_LS_HDR_REDUND);
  $min_col_wid_ref->{'striped'}        = length($ASMCMDBASE_LS_HDR_STRIPED);
  $min_col_wid_ref->{'date_print'}     = $min_time_len;
  $min_col_wid_ref->{'system_created'} = length($ASMCMDBASE_LS_HDR_SYSCRE);
  $min_col_wid_ref->{'block_size'}     = length($ASMCMDBASE_LS_HDR_BSIZE);
  $min_col_wid_ref->{'blocks'}         = length($ASMCMDBASE_LS_HDR_BLOCKS);
  $min_col_wid_ref->{'bytes'}          = length($ASMCMDBASE_LS_HDR_BYTES);
  $min_col_wid_ref->{'space'}          = length($ASMCMDBASE_LS_HDR_SPACE);
  $min_col_wid_ref->{'user'}           = length($ASMCMDBASE_LS_HDR_USER);
  $min_col_wid_ref->{'group'}          = length($ASMCMDBASE_LS_HDR_GROUP);
  $min_col_wid_ref->{'perm'}           = length($ASMCMDBASE_LS_HDR_PERM);

  return;
}

########
# NAME
#   asmcmdbase_lsdg_init_col_wid
#
# DESCRIPTION
#   This routine initializes the minimum column width hash with default values
#   for lsdg.  These default values are determined by the length of the values 
#   of the printed column headers.  These header values are not necessarily 
#   the same as the column names in v$asm_diskgroup but look similar.
#
# PARAMETERS
#   min_col_wid_ref (OUT) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   Must call this routine or asmcmdbase_ls_init_col_wid() before calling 
#   asmcmdshare_ls_calc_min_col_wid().
########
sub asmcmdbase_lsdg_init_col_wid 
{
  my ($min_col_wid_ref) = shift;

  $min_col_wid_ref->{'inst_id'}           = length($ASMCMDBASE_LSDG_HDR_INSTID);
  $min_col_wid_ref->{'sector_size'}       = length($ASMCMDBASE_LSDG_HDR_SECTOR);
  $min_col_wid_ref->{'logical_sector_size'}
                                         = length($ASMCMDBASE_LSDG_HDR_LSECTOR);
  $min_col_wid_ref->{'rebalance'}         = length($ASMCMDBASE_LSDG_HDR_REBAL);
  $min_col_wid_ref->{'block_size'}        = length($ASMCMDBASE_LSDG_HDR_BLOCK);
  $min_col_wid_ref->{'allocation_unit_size'} = length($ASMCMDBASE_LSDG_HDR_AU);
  $min_col_wid_ref->{'state'}             = length($ASMCMDBASE_LSDG_HDR_STATE);
  $min_col_wid_ref->{'type'}              = length($ASMCMDBASE_LSDG_HDR_TYPE);
  $min_col_wid_ref->{'total_mb'}          = length($ASMCMDBASE_LSDG_HDR_TMB);
  $min_col_wid_ref->{'free_mb'}           = length($ASMCMDBASE_LSDG_HDR_FMB);
  $min_col_wid_ref->{'required_mirror_free_mb'} 
                                          = length($ASMCMDBASE_LSDG_HDR_RMFMB);
  $min_col_wid_ref->{'usable_file_mb'}    = length($ASMCMDBASE_LSDG_HDR_UFMB);
  $min_col_wid_ref->{'offline_disks'}     = length($ASMCMDBASE_LSDG_HDR_ODISK);
  $min_col_wid_ref->{'voting_files'}      = length($ASMCMDBASE_LSDG_HDR_VOTING_FILE);

  return;
}

########
# NAME
#   asmcmdbase_lsdg_print
#
# DESCRIPTION
#   This routine is responsible for printing diskgroups and their column
#   values.  Both the command 'ls +' and the command 'lsdg' use this routine
#   to print their results.
#
# PARAMETERS
#   dbh      (IN) - initialized database handle, must be non-null.
#   gname    (IN) - group name, as entered by user.
#   args_ref (IN) - reference to hash of lsdg options entered by user.
#
# RETURNS
#   Null.
#
# NOTES
#   Both asmcmdbase_process_ls() and asmcmdbase_process_lsdg() can call this
#   routine.
########
sub asmcmdbase_lsdg_print 
{
  my ($dbh, $gname, $args_ref) = @_;

  my (@dgroups);     # Array of hashes of diskgroup column values, one hash  #
                                                       # for each diskgroup. #
  my (%min_col_wid);                         # hash of minimum column width. #
  my ($row_g);                           # printf() format string for ls -g. #
  my ($row_l);                           # printf() format string for ls -l. #
  my ($row_s);                           # printf() format string for ls -s. #
  my ($row_n);                          # printf() format string for ls -ls. #
  my ($row) = '';    # Concatenated printf() format string with all options. #
  my ($rebal);      # 'Y' if diskgroup currently rebalancing; 'N' otherwise. #
  my ($i);                             # Iteration variable for for() loops. #
  my ($discovery) = 0;           # True iff do discovery when querying view. #
  my ($global) = 0;                            # True iff query global view. #
  my ($print_string);     # generate the string to be printed using sprintf. #
  my (@print_what);                              # The fields to be printed. #

  # Check for the -c flag.
  if (defined($args_ref->{'discovery'}))
  {
    $discovery = 1;
  }

  # Check for the -g flag.
  if (defined($args_ref->{'g'}))
  {
    $global = 1;
  }

  # Obtain the list of diskgroups.  If $gname is not undefined, then only
  # information on that diskgroup is obtained.
  @dgroups = asmcmdshare_get_dg($dbh, $gname, $discovery, $global);

  # If non returned, then diskgroup is not found.
  if (@dgroups == 0) 
  {
    my (@eargs) = ($gname);
    asmcmdshare_error_msg(8001, \@eargs) if (defined ($gname));
    return;
  }

  # Sort results
  if (defined ($args_ref->{'reverse'})) 
  {                              # Reverse alphabetical order by group name. #
    @dgroups = sort asmcmdbase_name_backward @dgroups;
  }
  else 
  {                                      # Alphabetical order by group name. #
    @dgroups = sort asmcmdbase_name_forward @dgroups;
  }

  # Calculate column width.
  asmcmdbase_lsdg_init_col_wid (\%min_col_wid);
  asmcmdshare_ls_calc_min_col_wid (\@dgroups, \%min_col_wid);

  # Set up width values for instance ID for gv$asm_diskgroup.
  $row_g = "%"  . $min_col_wid{'inst_id'}              . "s  ";

  # Set up width values for printf().
  $row_l = "%-" . $min_col_wid{'state'}                . "s  " .
           "%-" . $min_col_wid{'type'}                 . "s  " .
           "%-" . $min_col_wid{'rebalance'}            . "s  ";

  $row_s = "%"  . $min_col_wid{'sector_size'}          . "s  " .
           "%"  . $min_col_wid{'logical_sector_size'}  . "s  " .
           "%"  . $min_col_wid{'block_size'}           . "s  " .
           "%"  . $min_col_wid{'allocation_unit_size'} . "s  " .
           "%"  . $min_col_wid{'total_mb'}             . "s  " .
           "%"  . $min_col_wid{'free_mb'}              . "s  ";

  # Column in 10gR2 or later.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) >= 0 )
  {
    $row_s .= "%" . $min_col_wid{'required_mirror_free_mb'} . "s  ";
    $row_s .= "%" . $min_col_wid{'usable_file_mb'}          . "s  ";
    $row_s .= "%" . $min_col_wid{'offline_disks'}           . "s  ";
  }

  # Column in 11gR2 or later.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_11gR2) >= 0 )
  {
    $row_s .= "%" . $min_col_wid{'voting_files'} . "s  ";
  }

  $row_n = "%-s\n";

  $row .= $row_g if (defined ($args_ref->{'g'}));                   # ls -g. #
  $row .= $row_l if (defined ($args_ref->{'l'}));                   # ls -l. #
  $row .= $row_s if (defined ($args_ref->{'s'}));                   # ls -s. #
  $row .= $row_n;                               # Always display group name. #

  # Now generate the string to be printed based on user-specified flags.
  @print_what = ();

  if (defined ($args_ref->{'g'}))
  {
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_INSTID");
  }

  if (defined ($args_ref->{'l'}))
  {
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_STATE");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_TYPE");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_REBAL");
  }

  if (defined ($args_ref->{'s'}))
  {
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_SECTOR");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_LSECTOR");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_BLOCK");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_AU");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_TMB");
    push (@print_what, "$ASMCMDBASE_LSDG_HDR_FMB");
    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_10gR2) >= 0 )
    {                                           # Version is 10gR2 or later. #
      push (@print_what, "$ASMCMDBASE_LSDG_HDR_RMFMB");
      push (@print_what, "$ASMCMDBASE_LSDG_HDR_UFMB");
      push (@print_what, "$ASMCMDBASE_LSDG_HDR_ODISK");
    }

    # Column in 11gR2 or later.
    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_11gR2) >= 0 )
    {
      push (@print_what, "$ASMCMDBASE_LSDG_HDR_VOTING_FILE");
    }
  }

  # Always include the name.
  push (@print_what, "$ASMCMDBASE_LSDG_HDR_NAME");
  # Now print the header if all these conditions are met:
  # 1) at least one of these flags are set: -g, -l, or -s;
  # 2) the --suppressheader flag is not set; and
  # 3) we have at least one row of results.
  if ((defined ($args_ref->{'g'}) ||
       defined ($args_ref->{'l'}) ||
       defined ($args_ref->{'s'}))  &&
      !defined ($args_ref->{'suppressheader'})   &&
      (@dgroups > 0))
  {
    $print_string = sprintf($row, @print_what);
    asmcmdshare_print($print_string);
  }

  # Print one row at a time.
  for ($i = 0; $i < @dgroups; $i++) 
  {
    $dgroups[$i]->{'name'} .= '/';

    # Generate the string to be printed.
    @print_what = ();

    if (defined ($args_ref->{'g'}))
    {
      push(@print_what, $dgroups[$i]->{'inst_id'});
    }

    if (defined ($args_ref->{'l'}))
    {
      # Find out whether this diskgroup is currently rebalacing.
      $rebal = asmcmdbase_is_rbal ($dbh, $dgroups[$i]->{'group_number'});

      push(@print_what, $dgroups[$i]->{'state'});
      push(@print_what, $dgroups[$i]->{'type'});
      push(@print_what, $rebal);

    }

    if (defined ($args_ref->{'s'}))
    {
      if (!defined($dgroups[$i]->{'total_mb'}))
      {
        $dgroups[$i]->{'total_mb'}=0;
      }

      if (!defined($dgroups[$i]->{'free_mb'}))
      {
        $dgroups[$i]->{'free_mb'}=0;
      }

      push(@print_what, $dgroups[$i]->{'sector_size'});
      push(@print_what, $dgroups[$i]->{'logical_sector_size'});
      push(@print_what, $dgroups[$i]->{'block_size'});
      push(@print_what, $dgroups[$i]->{'allocation_unit_size'});
      push(@print_what, $dgroups[$i]->{'total_mb'});
      push(@print_what, $dgroups[$i]->{'free_mb'});

      if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                   $ASMCMDGLOBAL_VER_10gR2) >= 0 )
      {                                         # Version is 10gR2 or later. #
        push(@print_what, $dgroups[$i]->{'required_mirror_free_mb'});
        push(@print_what, $dgroups[$i]->{'usable_file_mb'});
        push(@print_what, $dgroups[$i]->{'offline_disks'});
      }

      if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                   $ASMCMDGLOBAL_VER_11gR2) >= 0 )
      {
        push(@print_what, $dgroups[$i]->{'voting_files'});
      }
    }

    # Always include the disk group name.
    push(@print_what, $dgroups[$i]->{'name'});

    # Now evaluate the printf() expression.
    $print_string = sprintf($row, @print_what);
    asmcmdshare_print($print_string);
  }

  return;
}

########
# NAME
#   asmcmdbase_find_sql
#
# DESCRIPTION
#   This routine uses a search string to search under paths for aliases with
#   names that match the search string.  
#
# PARAMETERS
#   dbh              (IN) - initialized database handle, must be non-null.
#   path             (IN) - path under which to search, can be full or 
#                           relative; can contain the wildcard '*'.
#   search           (IN) - the search string; can contain the wildcard '*'.
#   type             (IN) - optional; the file type to search for if -t is set
#   par_id           (IN) - optional; if defined, then return only matches
#                           that have this parent index.
#   sys_non_dir_only (IN) - boolean; return only system alias names of 
#                           files if true.
#   with_diskgroup   (IN) - boolean; search v$asm_diskgroup in addition to 
#                           v$asm_alias if true; search only v$asm_alias
#                           otherwise.
#
# RETURNS
#   An array of hashes of search result, each hash with v$asm_alias column
#   values.  The hash also contains the full path to that file or directory.
#   If the search yields no matches, then return an empty array.
#
# NOTES
#   By specifying a parent index, asmcmdbase_find_sql() returns at most one
#   match.  Use this feature when you have only an alias name by want the 
#   full path to it.
#
########
sub asmcmdbase_find_sql 
{
  my ($dbh, $path, $search, $type, $par_id, $sys_non_dir_only, 
      $with_diskgroup) = @_;

  my (%path_rslt); # See asmcmdshare_normalize_path() return value comments. #
  my (@srch_rslt);  # Array of hashes, one for each entry that has an alias  #
                                                # name that matches $search. #
  my (@results);     # Array of hashes, one for each entry that is found in  #
                                                              # this search. #
  my (@dgroup);# Array of hashes of all diskgroups whose name match $search. #
  my ($fnd_str);      # The full path to an alias that is found under $path. #
  my ($gname);                              # Group name for an entry entry. #
  my ($iter);                      # Iteration variable for foreach() loops. #
  my ($ret);#Is -1 iff asmcmdshare_normalize_path() fails to normalize path. #


  # First search under v$asm_alias for alias names that match $search.

  # See if path is valid.  Get all paths if $path contains a wildcard.
  %path_rslt = asmcmdshare_normalize_path ($dbh, $path, 0, \$ret);
  return @results if ($ret == -1);         # return empty array if bad path. #


  # Obtain a list of all aliases in v$asm_alias that 1) matches the search 
  # string $search, 2) has the parent index $par_id (if defined), and 3)
  # is a system alias for a file (if $sys_non_dir_only is true).  
  asmcmdshare_run_find_sql ($dbh, \@srch_rslt, $par_id, $search, 
                            $type, $sys_non_dir_only);

  # Now we need to see if the entries returned from
  # asmcmdshare_run_find_sql() fall under one of the paths returned 
  # from asmcmdshare_normalize_path().
  foreach $iter (@srch_rslt) 
  {
    $gname = asmcmdshare_get_gname_from_gnum($dbh, $iter->{'group_number'});

    # See if the alias falls under the list of paths in %path_rslt.
    $fnd_str = asmcmdbase_find_one($dbh, $path_rslt{'path'}, 
                                   $path_rslt{'ref_id'},
                                   $iter->{'name'}, $gname, 
                                   $iter->{'group_number'},
                                   $iter->{'parent_index'});

    # If defined, then $fnd_str contains the full path to matching alias.
    if (defined ($fnd_str)) 
    {
      $iter->{'full_path'} = $fnd_str; # Hash the path into the entry alias. #
      push (@results, $iter);        # Add the hash to the array of matches. #
    }
  }


  # Now search under v$asm_diskgroup for diskgroup names that match $search.

  # Search for diskgroups only of all of these conditions are met:
  # 1) $sys_non_dir_only is false;
  # 2) $with_diskgroup is true;
  # 3) $type is undefined;
  # 4) The path we're searching under normalizes to '+'.
  # 5) Either $par_id is undefined or is -1.  
  if ( (! $sys_non_dir_only)       && 
       ($with_diskgroup)           &&
       (! defined ($type))         &&
       ($path_rslt{'path'}->[0] eq '+') &&
       ( (! defined ($par_id)) || ($par_id == -1) ) )

  {
    # Get list of all diskgroups that match $search.
    @dgroup = asmcmdshare_get_dg ($dbh, $search, 0, 0);  

    foreach $iter (@dgroup)
    {
      # Construct full path for asmcmdbase_process_find().
      $iter->{'full_path'} = '+' . $iter->{'name'};

      # In ASMCMD, a diskgroup is considered a pseudo-directory.  We need to 
      # set this to 'Y' so that asmcmdbase_process_find() knows to append the
      # '/' suffix to the path to denote that this path points to a directory.
      $iter->{'alias_directory'} = 'Y';

      # Save in return array.
      push (@results, $iter);
    }
  }

  return @results;
}

########
# NAME
#   asmcmdbase_find_one
#
# DESCRIPTION
#   This routine checks if the alias with the name $name and parent index
#   $index resides under any of the paths in the array referenced by 
#   $paths_ref.  For instance, '+dgroup/ORCL/DATAFILE/SYSTEM.256.1' resides
#   under '+dgroup/ORCL', but '+dgroup/ZRCL/DATAFILE/SYSTEM.284.4' does not.
#
# PARAMETERS
#   dbh         (IN) - initialized database handle, must be non-null.
#   paths_ref   (IN) - reference to an array of paths under which to look for
#                      the alias name $name.
#   par_ids_ref (IN) - reference to an array parallel to the array $paths_ref
#                      references; this array contains the corresponding 
#                      reference indices for deepest directory in each path;
#                      these reference indices are used as possible parent
#                      indices for $name; if $index matches one of the indices,
#                      in @{$par_ids_ref}, then $name must be under the
#                      corresponding path in @{paths_ref}.
#   name        (IN) - name of the alias that we're searching.
#   gname       (IN) - group name of the group in which the search is done.
#   gnum        (IN) - corresponding group number of $gname.
#   index       (IN) - compare this index against all indices in @{$par_ids_ref}
#                      to determine if $name is under a path in @{$paths_ref};
#                      $index can be the parent index, grandparent index, great
#                      grandparent index, etc., of $name, depending on how
#                      deep $name resides in the directory tree.
#
# RETURNS
#   Full path to $name if $name is found under any of the paths in 
#   @{paths_ref}; undefined if not found.
#
# NOTES
#   The alias $name is already known to exist in v$asm_alias at this point, as
#   asmcmdbase_find_sql() has already SQL-searched the fixed view for the 
#   search string, and $name is one of the matches that's returned.  So here 
#   the question is not whether $name is present in v$asm_alias but whether 
#   $name resides under one of the paths in @{$paths_ref}.
#
#   The algorithm here is to see if any of the 'ancestor' indices of $name
#   matches any one of the indices in @{$par_ids_ref}.  If yes, then $name
#   is found under the desired location.
#
#   Note that $index can match at most one index in @{$par_ids_ref}, because
#   all the paths we're searching are derived from one path string that can
#   have wildcards.  Thus, all paths are unique.
########
sub asmcmdbase_find_one 
{
  my ($dbh, $paths_ref, $par_ids_ref, $name, $gname, $gnum, $index) = @_;

  my ($sql);                # SQL query string for getting the parent index. #
  my ($sth);                                         # SQL statement handle. #
  my ($row);              # One row results returned from the SQL execution. #
  my ($prefix);                  # One path in @{$par_ids_ref}; returned by  #
              # asmcmdbase_find_match() only if $index matches the index of  #
                    # $prefix; $prefix concatenated to $name forms the full  #
                                                            # path to $name. #

  # This condition is true if $index matches *none* of the indices in 
  # @{$par_ids_ref}, in which case $prefix would be undefined.  Otherwise,
  # $prefix contains the corresponding path of the matching index, and
  # the loop terminates because a match is found.
  while (! defined($prefix = asmcmdbase_find_match($paths_ref, $par_ids_ref, 
                                                $index))) 
  {
    # If we're in this loop, then we know $index did not match, in which case
    # we need to update $index to the value of the parent index that 
    # corresponds to the reference index $index.
    if ($index == -1) 
    {   # Special case: we're already at '+', and still no match, so return. #
      return undef;
    }
    elsif ($index == ($gnum << 24)) 
    { 
      # If we're at diskgroup level, assign index to -1, which is defined
      # to be the reference index for '+' in ASMCMD.
      $index = -1;
      $name = $gname . '/' . $name;       # Continue building the full path. #
    }
    else 
    {
      # Otherwise, we update $index to the index of the parent using this 
      # SQL statement.
      $sql = "select name, parent_index from v\$asm_alias
                  where reference_index=?";

      $sth = asmcmdshare_do_prepare($dbh, $sql);
      $sth->bind_param(1,$index);
      asmcmdshare_do_execute($sth);

      $row = asmcmdshare_fetch($sth);
      asmcmdshare_finish($sth);
      return undef if (! defined ($row));

      $index = $row->{'PARENT_INDEX'};
      $name = $row->{'NAME'} . '/' . $name;           # Build the full path. #
    }
  }

  # We get here only if there is a match.

  $name = $prefix . '/' .  $name;          # Append prefix to get full path. #
  $name =~ s,^\+/,\+,;      # Change '+/' to '+', removing the possible '/'. #

  return $name;
}

########
# NAME
#   asmcmdbase_find_match
#
# DESCRIPTION
#   This routine checks to see if $index matches any of the indices in 
#   @{$par_ids_ref}.
#
# PARAMETERS
#   dbh         (IN) - initialized database handle, must be non-null.
#   paths_ref   (IN) - reference to an array of paths under which to look for
#                      the alias name $name.
#   par_ids_ref (IN) - reference to an array parallel to the array $paths_ref
#                      references; this array contains the corresponding 
#                      reference indices for deepest directory in each path;
#                      these reference indices are used as possible parent
#                      indices for $name; if $index matches one of the indices,
#                      in @{$par_ids_ref}, then $name must be under the
#                      corresponding path in @{paths_ref}.
#   index       (IN) - compare this index against all indices in @{$par_ids_ref}
#                      to determine if $name is under a path in @{$paths_ref};
#                      $index can be the parent index, grandparent index, great
#                      grandparent index, etc., of $name, depending on how
#                      deep $name resides in the directory tree.
#
# RETURNS
#   Corresponding path in @{$paths_ref} if there is a match; undefined
#   otherwise.
########
sub asmcmdbase_find_match 
{
  my ($paths_ref, $par_ids_ref, $index) = @_;

  my ($i);

  for ($i = 0; $i < @{$par_ids_ref}; $i++) 
  {
    return $paths_ref->[$i] if ($par_ids_ref->[$i] == $index);
  }

  return undef;
}

########
# NAME
#   asmcmdbase_rm_prompt_conf 
#
# DESCRIPTION
#   This routine prompts the user to confirm a delete for the command 'rm'.
#
# PARAMETERS
#   None.
#
# RETURNS
#   1 if user response is 'y'; 0 otherwise.
#
# NOTES
#   Only asmcmdbase_process_rm() calls this routine.
########
sub asmcmdbase_rm_prompt_conf 
{
  my ($response);                       # String to store the user response. #

  asmcmdshare_print "You may delete multiple files and/or directories. \n"; 
  asmcmdshare_printprompt 'Are you sure? (y/n) ';
  $response = asmcmdshare_readstdin();
  chomp ($response);

  return 1 if ($response eq 'y');
  return 0;
}



#######
# NAME
#   asmcmdbase_print_action
#
# DESCRIPTION
#   This routine prints the following message for the command used
#   Command                                            Message
#   cp a b                                             "copying <a> -> <b>"
#   pwcopy a b                                         "copying <a> -> <b>"
#   mv a b                                             "moving <a> -> <b>"
#   cp --sparse_merge_begin a b --sparse_merge_end c   "merging <a> through <c>"
#                                                      "to <b>"
#
# PARAMETERS
#   fromfile    (IN)  - source file name
#   tofile      (IN)  - target file name
#   throughfile (IN)  - name of sparse file indicating the ending
#                       depth in sparse merge operation
#
# RETURNS
#   NONE
#
# NOTES
#   This function handles both OS File names & ASM file names
#######
sub asmcmdbase_print_action
{
  my ($fromfile, $tofile, $throughfile) = @_;


  # only on Windows
  if ($^O =~ /win/i)
  {
    #
    # DOS style \\ is replaced for reg exp search earlier to UNIX style /.
    # To print correctly, replace it back.
    # Only if the files are OS files.
    #

    if ( $fromfile !~ /^\+/ )
    {
      $fromfile =~ s/\//\\/g;
    }

    if ( $tofile !~ /^\+/ )
    {
      $tofile =~ s/\//\\/g;
    }
    if ( $throughfile !~ /^\+/)
    {
      $throughfile =~ s/\//\\/g;
    }
  }

  if ($asmcmdglobal_hash{'cmd'} eq "cp" || 
      $asmcmdglobal_hash{'cmd'} eq "pwcopy") 
  {
    if ($asmcmdglobal_hash{'sparse_merge_begin'} eq "1")
    {
      asmcmdshare_print "merging $fromfile through $throughfile to $tofile\n";
    }
    else
    {
      asmcmdshare_print "copying $fromfile -> $tofile\n";
    }
  }
  if ($asmcmdglobal_hash{'cmd'} eq "pwmove") 
  {
    asmcmdshare_print "moving $fromfile -> $tofile\n";
  }
  if ($asmcmdglobal_hash{'cmd'} eq "setsparseparent")
  {
    asmcmdshare_print "setting parent of $fromfile to $tofile\n";
  }
}

########
# NAME
#   asmcmdbase_do_file_copy
# DESCRIPTION
#   This routine copy specified src file(s) to target file(dir) using 
#   dbms_diskgroup PL/SQL pkgs. Source cannot be a directory.
#
# PARAMETERS
#   src_fname   (IN) - the array of src file names.
#   tgt_fdname  (IN) - target file name or directory.
#   remote_dbh  (IN) - remote ASM instance handle given by DBI->connect()
#                      NULL(undef) for local copy mode
#
# RETURNS
#   Count of files copied successfully; 0 on error.
#
# NOTES
########
sub asmcmdbase_do_file_copy 
{
  my ($src_remote_inst, $tgt_remote_inst, 
      $src_dbh, $tgt_dbh, $remote_conn_str, 
      $src_paths, $tgt_path) = @_;

  my @norm_src_fname;
  my @norm_tgt_fname;
  my @src_finfo;
  my @tgt_paths;

  my $src_fil;
  my $tgt_fil;

  my $src_sth;
  my $tgt_sth;

  my $ret;
  my %norm;
  my @eargs;

  my @paths;
  my @ref_ids;
  my @par_ids;
  my @dg_nums;
  my @entry_list;
  my $name;

  my $i;
  my $tgt_name;
  my %invalid_files;

  # to print with correct '/' on windows
  my $fromfile;         # normalized source file name
  my $tofile;           # normalized target file name
  my $remotefname;      # host name + file name.

  # $asmcmdglobal_hash{'service'} is always set in asmcmdbase_process_cp. If no
  # value specified, +ASM is assumed.
  my $servicename        = $asmcmdglobal_hash{'service'};    # opt param for cp
  my $sparse             = $asmcmdglobal_hash{'sparse'};
  my $copy_success_files = {};
  my $success_count      = 0;
  # sparse merge operation
  my $sparse_merge_begin = $asmcmdglobal_hash{'sparse_merge_begin'};
  my $sparse_merge_end   = $asmcmdglobal_hash{'sparse_merge_end'};

  # dbuniquename. 
  my ($dbuniquename) = $asmcmdglobal_hash{'dbuniquename'};
  $dbuniquename = "" if (!defined($dbuniquename));
  asmcmdshare_trace(3, "dbuniquename: $dbuniquename", 'y', 'n');

  # normalize and validate paths first iterate through each source file
  foreach my $src_path (@{$src_paths})
  {
    my @src_paths;
    my $src_name;
    my @src_names;

    my $tgt_fname = $tgt_path;
    my @tgt_names;
    my @tmp;

    my $src_hdl;
    my $tgt_hdl;
    my $plkSz;
    my $fileSz;
    my $openMode;

    my $fileType;
    my $blkSz;
    my $fileName;
    my $fileGenName;

    my $dbname;
    my $dbid;
    my $tsname;
    my $fno;
    my $plid;
    my $sameen;

    # size of pl/sql NUMBER type 
    my $pl_sql_number = 22;

    # size of pl/sql VARCHAR type 
    my $pl_sql_varchar = 1024;
    # 1Kbytes
    my $Kbytes = 1024;

    # normalize file paths
    # criteria: if it is an OS file, has to be full path, otherwise it will be
    #           treated as a relative path in the ASM context

    $src_path =~ s,\\,/,g; # replace DOS style \\ to UNIX style /
    $src_path =~ s,/+,/,g; # multiple slashes together become a single slash

    # if src_path doesn't start with '/', then it is an ASM relative path
    # every OS path must be absolute.
    if (($src_path !~ /^[a-z]:/i) && ($src_path !~ m'^[/\\\\]'))
    {
      # ASM file case

      # if source file name is not an an absolute OS path nor ASM path,
      # assume it is ASM and  complete it
      %norm = asmcmdshare_normalize_path($src_dbh, $src_path, 0, \$ret);

      if ($ret != 0)
      {
        @eargs = ($src_path);
        asmcmdshare_error_msg(8014, \@eargs);
        next;
      }
      @src_paths = @{$norm{'path'}};
    }
    else
    {
      # OS file(s) case
      $src_path =~ s/ /\\ /g; # escape blank spaces
      @src_paths = glob($src_path);

      if ($src_remote_inst != 1 )
      {
        # on remote host we cannot check for file existence
        foreach $src_path (@src_paths)
        {
          if ( !(-r $src_path) )
          {
            @eargs = ($src_path);
            asmcmdshare_error_msg(8014, \@eargs);
            next;
          }
        }
      }
    }

    # add the normalized file name(s) to the array
    push(@norm_src_fname, @src_paths);

    # Save a list containing only the file names
    @src_names = map { /.*\/(.*)/ ; $1 } @src_paths;

    # if target doesn't start with '/', then it is an ASM relative path
    # every OS path must be absolute
    if (($tgt_path !~ /^[a-z]:/i) && ($tgt_path !~ m'^[/\\\\]'))
    {
      $tgt_path = asmcmdshare_make_absolute($tgt_path);

      # target is ASM
      # if target is a relative path, complete it to be a valid ASM path
      %norm = asmcmdshare_normalize_path($tgt_dbh, $tgt_path, 1, \$ret);
      # path exists, must be a directory, and need to complete it with 
      # the source name
      if ($ret == 0)
      {
        # Since we support wildcards now, make sure there is only one match for
        # target path.  Otherwise, report error.
        if (@{$norm{'path'}} != 1)
        {
          @eargs = ($tgt_path);
          # ASMCMD-8005: "directory '%s' is ambiguous"
          asmcmdshare_error_msg(8005, \@eargs);
          return;
        }

        # complete the path
        $tgt_path = $norm{'path'}->[0];
        $name     = $tgt_path;
        $name     =~ s,.*/(.*)$,$1,;

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

        asmcmdshare_get_subdirs($tgt_dbh, \@entry_list, undef, $dg_nums[0],
                                $ref_ids[0], $par_ids[0], $name, undef, 0, 1);

        # if target path is a disk group or a directory
        if (($ref_ids[0] == $dg_nums[0] << 24 ) ||
            $entry_list[0]->{'alias_directory'} eq 'Y')
        {
          @tgt_names = map { "$tgt_path/$_" } @src_names;
        }
        elsif ( $entry_list[0]->{'alias_directory'} eq 'N' )
        {
          asmcmdshare_assert(@src_names == 1, \@src_names);
          @tgt_names = ($tgt_path);
        }
        else
        {
          @eargs = ($tgt_path);
          asmcmdshare_error_msg(8014, \@eargs);
          next;
        }
      }
      else
      {
        @tgt_names = ($tgt_path);
      }
    }
    else
    {
      $tgt_path =~ s,\\,/,g;  # replace DOS style \\ to UNIX style /
      $tgt_path =~ s,/+,/,g; # multiple / together become a single /
      $tgt_path =~ s/ /\\ /g;  # escape blank spaces
      @tgt_paths = glob($tgt_path);

      # Since we support wildcards now, make sure there is only one match for
      # target path.  Otherwise, report error.
      unless (@tgt_paths == 1)
      {
        @eargs = ($tgt_path);
        # ASMCMD-8005: "directory '%s' is ambiguous"
        asmcmdshare_error_msg(8005, \@eargs);
        return;
      }

      $tgt_path = shift @tgt_paths;

      # target is OS path, if it exists check if it is a directory
      if (-d $tgt_path)
      {
        # if target OS directory does not have write permission, bail out.
        if (!-w $tgt_path)
        {
          asmcmdshare_error_msg(9463, undef);
          return undef;
        }

        @tgt_names = map { "$tgt_path/$_" } @src_names;
      }
      else
      {
        @tgt_names = ($tgt_path);
      }

      # check for target OS file existence, if exist check for write permission
      foreach $tgt_name (@tgt_names)
      {
        if ( -e $tgt_name )
        {
          if ( !-w $tgt_name )
          {
            asmcmdshare_error_msg (9463, undef);
            return undef;
          }
        }
        else
        {
          # target file does not exist, check if the target dir exist
          my $filename ;
          my @eargs;

          # split path to check for existence of dir too.
          ($tgt_path, $filename) = $tgt_name =~ m|^(.*[/\\])([^/\\]+?)$|;

          # check if path is a directory and has write permission
          if (-d $tgt_path && !-w $tgt_path)
          {
            asmcmdshare_error_msg (9463, undef);
            return undef;
          }
        }
      }
    }

    # replace DOS style '\\' to Unix style '\/'
    @tgt_names = map { $_ =~ s,\\,/,g ; $_ } @tgt_names;
    
    push(@norm_tgt_fname, @tgt_names);
  }

  if ($sparse_merge_begin != 0)
  {
    # normalize file paths
    # criteria: if it is an OS file, has to be full path,
    # otherwise it will be treated as a relative path
    # in the ASM context
  
    # replace DOS style \\ to UNIX style /
    $sparse_merge_end =~ s/\\/\//g;
  
    # if sparse_merge_end doesn't start with '/', it is an ASM relative path
    # every OS path must be absolute.
    if (($sparse_merge_end !~ /^[a-z]:/i) && ($sparse_merge_end !~ m'^[/\\\\]'))
    {
      # ASM file case
      # if sparse_merge_end file name is not an an absolute OS path nor ASM
      # path, assume it is ASM and complete it
      %norm = asmcmdshare_normalize_path($src_dbh, $sparse_merge_end, 0, \$ret);

      if ( $ret != 0 )
      {
        @eargs = ($sparse_merge_end);
        asmcmdshare_error_msg(8014, \@eargs);
        return undef;
      }
      $sparse_merge_end = $norm{'path'}->[0];
    }
    else
    {
      # OS file case
      if ( !(-r $sparse_merge_end) )
      {
        @eargs = ($sparse_merge_end);
        asmcmdshare_error_msg(8014, \@eargs);
        return undef;
      }
    }
  }

  # Get file information. Very important to do dbms_diskgroup stuff in the end,
  # since the connection becomes useless after calling it.
  foreach my $src_path (@norm_src_fname)
  {
    my $fileType;
    my $fileSz;
    my $blkSz;
    my %info;
    
    # Get file information, block size, file size, file type
    if (asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                $ASMCMDGLOBAL_VER_11gR1) >= 0)
    {
      $src_sth = $src_dbh->prepare(q{
        begin
          dbms_diskgroup.getfileattr(:fileName, :fileType, :fileSz, :blkSz);
        end;
        });

      # bind input parameters #
      $src_sth->bind_param( ":fileName", $src_path );

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

      # $sth->execute() || die $dbh->errstr;
      $ret = $src_sth->execute();
      if (!defined($ret) && ($asmcmdglobal_hash{'mode'} eq 'n')) 
      {        
        $asmcmdglobal_hash{'e'} = -1; 
      }
      if (!defined $fileType || $fileType < 1 )
      {
        my (@eargs) = ($src_path);
        asmcmdshare_error_msg(8012, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
        if ($asmcmdglobal_hash{'mode'} eq 'n')	
        {	
           $asmcmdglobal_hash{'e'} = -1;	
        }	
        $invalid_files{$src_path} = 1;
        next;
      }
      
      if (!defined $blkSz || $blkSz < 1)
      {
        my (@eargs) = ($src_path);
        asmcmdshare_error_msg(8013, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
        $invalid_files{$src_path} = 1;
        next;
      }
    }
    else
    {
      my $sameen = 1;
      my $dbname;
      my $tsname;
      my $dbid;
      my $fno;
      my $plid;

      # if file path starts with '/' then it is an OS file #
      # find file attributes, type, blkSz, fileSz(numberOfblks) #
      # use dbms_back_restore.readFileHeader package
      # for any instance release older than 11.xx
      $src_sth = $src_dbh->prepare(q{
             begin
               dbms_backup_restore.readFileHeader(:fileName, :dbname,
               :dbid, :tsname, :fno, :fileSz, :blkSz, :plid, :sameen);
             end;
             });

      # bind input params first #
      $src_sth->bind_param( ":fileName", $src_path );
      $src_sth->bind_param( ":sameen", $sameen );

      # bind ouput params #
      $src_sth->bind_param_inout( ":dbname", \$dbname, $PLSQL_VARCHAR );
      $src_sth->bind_param_inout( ":tsname", \$tsname, $PLSQL_VARCHAR );
      $src_sth->bind_param_inout( ":dbid", \$dbid, $PLSQL_NUMBER );
      $src_sth->bind_param_inout( ":fno", \$fno, $PLSQL_NUMBER );

      # fileSZ: total_number_lbk - 1, which is 1 lbk(logical blk)
      #         less of v$asm_files.blocks 
      $src_sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER );
      $src_sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER );
      $src_sth->bind_param_inout( ":plid", \$plid, $PLSQL_NUMBER );

      $ret = $src_sth->execute();
      if (!defined ($ret) && ($asmcmdglobal_hash{'mode'} eq 'n'))
      { 
       asmcmdglobal_hash{'e'} = -1;	
      }    
      if ( !defined $fno || $fno <= 0 )
      {
        my @eargs = ($src_path);
        asmcmdshare_error_msg(8012, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
        $invalid_files{$src_path} = 1;
        next;
      }

      # for values for file type, please refer ASM PL/SQL
      # Fixed Package Quickstart Guide
      $fileType = $PLSQL_DATAFILE;
      #figure out file type from path
    }

    # if file type is == 2, change it to 12 (datafile copy)
    if ($fileType == $PLSQL_DATAFILE)
    {
       $fileType = $PLSQL_DATAFILE_CP;
    }

    $info{'fsize'} = $fileSz;
    $info{'blksz'} = $blkSz;
    $info{'ftyp'}  = $fileType;
    push (@src_finfo, \%info);
  }

  # remove unexisting files
  # remove from @norm_src_fname and also from @norm_tgt_fname
  foreach (keys(%invalid_files))
  {
    for ($i = 0; $i < @norm_src_fname; $i++)
    {
      if ($norm_src_fname[$i] eq $_)
      {
        splice(@norm_src_fname, $i, 1);
        splice(@norm_tgt_fname, $i, 1);
        next;
      }
    }
  }

  if (!$src_remote_inst && !$tgt_remote_inst)  # local to local
  {
    for ($i = 0; $i < @norm_src_fname; $i++)
    {
      if ($sparse_merge_begin != 1)
      {
        # If the src and target files are the same then error out
        if ($norm_src_fname[$i] eq $norm_tgt_fname[$i])
        {
          my (@eargs) = ($norm_src_fname[$i]);
          asmcmdshare_error_msg(9358, \@eargs);
          next;
        }

        $src_sth = $src_dbh->prepare(q{
          begin
          dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz, 
                              :src_fsiz, '','','', :dst_path, 1, 0, 
                              :sparse_option, :sparse_begin, '',
                              :dbuniquename);
          end;
          });
      }
      else
      {
        # If src (sparse_merge_begin) and sparse_merge_end files are 
        # the same then error out
        if ($norm_src_fname[$i] eq $sparse_merge_end)
        {
          my (@eargs) = ($norm_src_fname[$i]);
          asmcmdshare_error_msg(8035, \@eargs);
          return undef;
        }

        $src_sth = $src_dbh->prepare(q{
          begin
          dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz, 
                              :src_fsiz, '','','', :dst_path, 1, 0, 
                              :sparse_option, :sparse_begin, :sparse_end,
                              :dbuniquename);
          end;
          });
        $src_sth->bind_param(":sparse_end", $sparse_merge_end);
      }
      $src_sth->bind_param(":src_path",  $norm_src_fname[$i]);
      $src_sth->bind_param(":src_ftyp",  $src_finfo[$i]->{'ftyp'});
      $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'});
      $src_sth->bind_param(":src_fsiz",  $src_finfo[$i]->{'fsize'});
      $src_sth->bind_param(":dst_path",  $norm_tgt_fname[$i]);
      $src_sth->bind_param(":sparse_option", $sparse);
      $src_sth->bind_param(":sparse_begin", $sparse_merge_begin);
      $src_sth->bind_param(":dbuniquename", $dbuniquename);

      $fromfile = $norm_src_fname[$i];
      $tofile   = $norm_tgt_fname[$i];

      asmcmdbase_print_action($fromfile, $tofile, $sparse_merge_end);
      $ret = $src_sth->execute();

      if (!defined($ret))
      {
        if ($sparse_merge_begin != 1)
        {
          my (@eargs) = ($fromfile, $tofile);
          asmcmdshare_error_msg(8016, \@eargs);
        }
        else
        {
          # failure case for sparse_merge
          my (@eargs) = ($fromfile, $sparse_merge_end, $tofile);
          asmcmdshare_error_msg(8033, \@eargs);
        }
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
        if ($asmcmdglobal_hash{'mode'} eq 'n')
        {
           $asmcmdglobal_hash{'e'} = -1; 
        }
        next;
      }
      else
      {
         $copy_success_files->{$norm_src_fname[$i]} = $norm_tgt_fname[$i];
         $success_count++;
      }
    }
    asmcmdshare_trace(3, "SUCCESS: Completed copying from local to local.",
                      'y', 'n');
  }
  elsif ($src_remote_inst)  # remote to local
  {
    my $host;
    my $port;
    my $sid;
    my $src_str;

    $port = 1521;
    $sid  = '+ASM';
    $port = $rport if(defined $rport);
    $sid  = $rsid if(defined $rsid);
    $host = $rhost if(defined $rhost);
   
    for ($i = 0; $i < @norm_src_fname; $i++)
    {
      $src_sth = $tgt_dbh->prepare(q{
        begin
        dbms_diskgroup.copy(:connect_iden, :usrname, :passwd, 
                            :src_path, :src_ftyp, :src_blksz, 
                            :src_fsiz, '','','', :dst_path, 1, 0,
                            :sparse_option, 0, '', :dbuniquename);
        end;
        });

      $src_str = "(description=(address_list=(ADDRESS=(PROTOCOL=tcp)";
      $src_str .= "(HOST=".$host.")" if defined($host);
      $src_str .= "(privilege=sysdba)(internal_logon=sysdba)";
      $src_str .= "(PORT=".$port.")))"if defined($port);
      $src_str .= "(connect_data=(service_name=".$servicename.")" 
                  if defined($servicename);
      $src_str .= " (instance_name=".$sid.")))" if defined($sid);

      $src_sth->bind_param(":connect_iden", $src_str);
      $src_sth->bind_param(":usrname", $rusr);
      $src_sth->bind_param(":passwd", $rpswd);
      $src_sth->bind_param(":src_path",  $norm_src_fname[$i]);
      $src_sth->bind_param(":src_ftyp",  $src_finfo[$i]->{'ftyp'});
      $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'});
      $src_sth->bind_param(":src_fsiz",  $src_finfo[$i]->{'fsize'});
      $src_sth->bind_param(":dst_path",  $norm_tgt_fname[$i]);
      $src_sth->bind_param(":sparse_option",  $sparse);
      $src_sth->bind_param(":dbuniquename", $dbuniquename);

      $fromfile = $host.":".$norm_src_fname[$i];
      $tofile   = $norm_tgt_fname[$i];

      asmcmdbase_print_action($fromfile, $tofile, '');
      $ret = $src_sth->execute();

      if (!defined($ret))
      {	
        my @eargs = ($fromfile, $tofile);
        my $msg   = "ERROR:asmcmdbase_do_file_copy01:dbms_diskgroup".
                    ".copy failed with ";
        $msg .= "ServiceName=$servicename" if defined($servicename);
        $msg .= " connect_iden: $src_str" if defined($src_str);
        asmcmdshare_trace(1, $msg , 'y', 'n');
        asmcmdshare_error_msg(8016, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
        if ($asmcmdglobal_hash{'mode'} eq 'n')
        {
           $asmcmdglobal_hash{'e'} = -1; 
        }
        next;
      }
      else
      {
         $copy_success_files->{$norm_src_fname[$i]} = $norm_tgt_fname[$i];
         $success_count++;
      }
    }
    asmcmdshare_trace(3, "SUCCESS: Completed copying from remote to local.",
                      'y', 'n');
  }
  elsif ($tgt_remote_inst)   # local to remote
  {
    my $host;
    my $port;
    my $sid;
    my $dst_str;
    my $pblkSz;
    my $comp;
    my $version;
    my $src_pwfile;
    my $pblksize;

    $pblkSz     = 0;
    $src_pwfile = 0;

    for ($i = 0; $i <= $#norm_src_fname; $i++)
    {
      if ($src_finfo[$i]->{'ftyp'} == $PLSQL_PWFILE)
      {
        $src_pwfile = 1;
      }
    }

    # We have to obtain physical block size of the destination
    # which is required for password file copy.
    if ($src_pwfile && $tgt_path !~ /^\+/) 
    {
      # Get the remote asm version 
      $version = asmcmdshare_get_asm_version($tgt_dbh); 
      # Copy for password files is supported for versions greater
      # than 12.1.0.0.0
      $comp    = asmcmdshare_version_cmp($version, $ASMCMDGLOBAL_VER_12gR1);

      if ($comp >= 0)
      {
        $tgt_sth = $tgt_dbh->prepare(q{
        begin
        dbms_diskgroup.getfilephyblksize(:fileName, :flag, :pblksize);
        end;
        });

        # bind input parameters #
        $tgt_sth->bind_param( ":fileName", $tgt_path);
        # set the value to 1 to obtain the values for pwfile
        $tgt_sth->bind_param( ":flag", 1);
        $tgt_sth->bind_param_inout( ":pblksize", \$pblksize, $PLSQL_NUMBER);

        $ret = $tgt_sth->execute();
          
        if (!defined($ret))
        {
          if ($asmcmdglobal_hash{'mode'} eq 'n')
          {
             $asmcmdglobal_hash{'e'} = -1; 
          }
        }  
        if (!defined $pblksize || $pblksize < 1)
        {
          my (@eargs) = ($tgt_path);
          asmcmdshare_error_msg(8013, \@eargs);
        }
        $pblkSz = $pblksize;
      }
      else
      {
         my (@eargs)    = ($ASMCMDGLOBAL_VER_12gR1);
         my (@hostname) = ($rhost);
         asmcmdshare_error_msg(8023, \@eargs, \@hostname);
      }
    }

    $port = 1521; #default port
    $sid  = '+ASM'; #default SID
    $port = $rport if(defined $rport);
    $sid  = $rsid if(defined $rsid);
    $host = $rhost if(defined $rhost);

    for ($i = 0; $i <= $#norm_src_fname; $i++)
    {

       $src_sth = $src_dbh->prepare(q{
       begin
       dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz,
                           :src_fsiz, :connect_iden, :usrname, :passwd,
                           :dst_path, 1, :dst_pblksize, :sparse_option, 0, '',
                           :dbuniquename);
       end;
       });

      $dst_str = "(description=(address_list=(ADDRESS=(PROTOCOL=tcp)";
      $dst_str .= "(HOST=".$host.")" if defined($host);
      $dst_str .= "(privilege=sysdba)(internal_logon=sysdba)";
      $dst_str .= "(PORT=".$port.")))"if defined($port);
      $dst_str .= "(connect_data=(service_name=".$servicename.")" 
                  if defined($servicename);
      $dst_str .= " (instance_name=".$sid.")))" if defined($sid);

      $src_sth->bind_param(":src_path",  $norm_src_fname[$i]);
      $src_sth->bind_param(":src_ftyp",  $src_finfo[$i]->{'ftyp'});
      $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'});
      $src_sth->bind_param(":src_fsiz",  $src_finfo[$i]->{'fsize'});
      $src_sth->bind_param(":connect_iden", $dst_str);
      $src_sth->bind_param(":usrname", $rusr);
      $src_sth->bind_param(":passwd", $rpswd);
      $src_sth->bind_param(":dst_path",  $norm_tgt_fname[$i]);
      $src_sth->bind_param(":dst_pblksize", $pblkSz);
      $src_sth->bind_param(":sparse_option",  $sparse);
      $src_sth->bind_param(":dbuniquename", $dbuniquename);

      $fromfile = $norm_src_fname[$i];
      $tofile = $rhost.":".$norm_tgt_fname[$i];
      asmcmdbase_print_action($fromfile, $tofile, '');
      $ret = $src_sth->execute();

      if ( !defined($ret) )
      {
        my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]);
        my ($msg) = "ERROR:asmcmdbase_do_file_copy02:dbms_diskgroup".
                    ".copy failed with ";
        $msg .= "ServiceName=$servicename" if defined($servicename);
        $msg .= " connect_iden=$dst_str" if defined($dst_str);
        asmcmdshare_trace(1, $msg , 'y', 'n');
        asmcmdshare_error_msg(8016, \@eargs);
        asmcmdshare_trace(1, "$DBI::errstr", 'y', 'y');
        if ($asmcmdglobal_hash{'mode'} eq 'n')
        {
           $asmcmdglobal_hash{'e'} = -1; 
        }
        next;
      }
      else
      {
         $copy_success_files->{$norm_src_fname[$i]} = $norm_tgt_fname[$i];
         $success_count++;
      }
    }
    asmcmdshare_trace(3, "SUCCESS: Completed copying from local to remote.",
                         'y', 'n');
  }

  return $copy_success_files;
}
##############################################################################

########
## NAME
##   asmcmdbase_set_sparse_parent_child
## DESCRIPTION
##   This routine sets parent of the specified child file(s) to a new parent
##   file using dbms_diskgroup PL/SQL pkgs. Child cannot be a directory.
##
## PARAMETERS
##   child_paths  (IN) - the array of child file names.
##   parent_path  (IN) - parent file name or directory.
##
## RETURNS
##   Count of child file(s) whose parent were successfully set; 0 on error.
##
## NOTES
#########
sub asmcmdbase_set_sparse_parent_child
{
  my ($child_dbh, $parent_dbh, $child_paths, $parent_path) = @_;
  
  my (@norm_child_fname, @norm_parent_fname);
  my ($child_sth);
  my ($ret);
  my (%norm);
  my (@eargs);
  my ($i);
  my ($success_count) = 0;

  # to print with correct '/' on windows
  my ($childfile);         # normalized sparse child file name
  my ($parentfile);        # normalized sparse parent file name

  # normalize and validate paths first
  # iterate through each sparse child file
  foreach (@{$child_paths})
  {
    my $child_path = $_;

    # normalize file paths
    # criteria: if it is an OS file, has to be full path,
    #           otherwise it will be treated as a relative path
    #           in the ASM context

    # replace DOS style \\ to UNIX style /
    $child_path =~ s/\\/\//g;

    # if src_path doesn't start with '/', then it is an ASM relative path
    # every OS path must be absolute.
    if (($child_path !~ /^[a-z]:/i) && ($child_path !~ m'^[/\\\\]'))
    {
      # ASM file case
      # if sparse child file name is not an an absolute OS path nor ASM path,
      # assume it is ASM and complete it
      %norm = asmcmdshare_normalize_path($child_dbh, $child_path, 0, \$ret);

      if ($ret != 0)
      {
        @eargs = ($child_path);
        asmcmdshare_error_msg(8014, \@eargs);
        next;
      }
      $child_path = $norm{'path'}->[0];
    }
    else
    {
      # OS file case
      if (!(-r $child_path))
      {
        @eargs = ($child_path);
        asmcmdshare_error_msg(8014, \@eargs);
        next;
      }
    }

    # add the normalized file name to the array
    push(@norm_child_fname, $child_path);

    #replace DOS style '\\' to Unix style '\/'
    $parent_path =~ s/\\/\//g;

    # if parent doesn't start with '/', then it is an ASM relative path
    # every OS path must be absolute
    if (($parent_path !~ /^[a-z]:/i) && ($parent_path !~ m'^[/\\\\]'))
    {
      $parent_path = asmcmdshare_make_absolute($parent_path);
      # parent is in ASM
      # if parent is a relative path, complete it to be a valid ASM path
      %norm = asmcmdshare_normalize_path($parent_dbh, $parent_path, 1, \$ret);
      
      if ($ret != 0)  
      {
        @eargs = ($parent_path);
        asmcmdshare_error_msg(8014, \@eargs);
        return $success_count; 
      }
      $parent_path = $norm{'path'}->[0];
    }
    else
    {
      # OS file case
      if (!(-r $parent_path))
      {
        @eargs = ($parent_path);
        asmcmdshare_error_msg(8014, \@eargs);
        return $success_count;
      }
    }
    push(@norm_parent_fname, $parent_path);
  }
  
  # set child's new parent
  for ($i = 0; $i < @norm_child_fname; $i++)
  {
    # If both child and parent files are the same then error out
    if ($norm_child_fname[$i] eq $norm_parent_fname[$i])
    {
      my (@eargs) = ($norm_child_fname[$i]);
      asmcmdshare_error_msg(9358, \@eargs);
      next;
    }

    $child_sth = $child_dbh->prepare(q{
        begin
        dbms_diskgroup.setsparseparent(:child_path, :parent_path);
        end;
        });

    $child_sth->bind_param(":child_path",  $norm_child_fname[$i]);
    $child_sth->bind_param(":parent_path",  $norm_parent_fname[$i]);

    $childfile = $norm_child_fname[$i];
    $parentfile = $norm_parent_fname[$i];

    asmcmdbase_print_action($childfile, $parentfile, '');

    $ret = $child_sth->execute();

    if (!defined($ret))
    {
      my (@eargs) = ($childfile, $parentfile);
      asmcmdshare_error_msg(8017, \@eargs);
      asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
      if ($asmcmdglobal_hash{'mode'} eq 'n')
      {
        $asmcmdglobal_hash{'e'} = -1;
      }
      next;
    }
    else
    {
      $success_count++;
    }
  }
  asmcmdshare_trace(3, "Completed setsparseparent from local to local.",
                    'y', 'n');
  
  return $success_count;
}

############################# Sort Order Routines ############################
########
# NAME
#   asmcmdbase_name_forward
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Order alphabetically by 1) name from v$asm_alias and 2) the full path to
#   that name.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
########
sub asmcmdbase_name_forward 
{
  $a->{'name'} cmp $b->{'name'}
  or
  $a->{'path'} cmp $b->{'path'};
}

########
# NAME
#   asmcmdbase_name_backward
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Order reverse-alphabetically by 1) name from v$asm_alias and 2) the full 
#   path to that name.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
########
sub asmcmdbase_name_backward 
{
  $b->{'name'} cmp $a->{'name'}
  or
  $b->{'path'} cmp $a->{'path'};
}

########
# NAME
#   asmcmdbase_time_forward
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Order in this way:
#
#     1)  Directory aliases always comes first, then file aliases.
#     2a) If directory, sort in reverse-alphabetical order by name and then
#         by path, just like in asmcmdbase_name_backward().
#     2b) If file, sort by Julian date with most recent first, then reverse-
#         alphabetically by name and then by path.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
#
# NOTES
#   We sort by Julian date in absolute number of days.  As in Unix versions of
#   ls, time forward puts the most recently updated files first, as in ls -r.
########
sub asmcmdbase_time_forward 
{
  # If one alias is a file and another is a directory, then the directory
  # comes first.
  if ($a->{'alias_directory'} ne $b->{'alias_directory'}) 
  {
    $b->{'alias_directory'} cmp $a->{'alias_directory'};
  }

  # If both are directories, sort in same way as in asmcmdbase_name_backward().
  elsif ($a->{'alias_directory'} eq 'Y') 
  {
    $b->{'name'} cmp $a->{'name'}
    or
    $b->{'path'} cmp $a->{'path'};
  }

  # If both are files, sort first by Julian date, most recent first; then 
  # reverse alphabetically as in asmcmdbase_name_backward().
  else 
  {
#   In the (near) future), v$file joined with v$alias will not return volume
#   information. When that happens, we can get rid of the 'volume' code
#   below - leaving only the 'else' clause. 
    my($a_name, $a_unused) = split /\./, $a->{'name'}, 2;
    my($b_name, $b_unused) = split /\./, $b->{'name'}, 2;

    # volumes don't have 'julian_time' or 'path' and so would generate an error
    if (($a_name eq 'volume') || ($b_name eq 'volume') ||
        ($a_name eq 'DRL') || ($b_name eq 'DRL'))
    {
      $b->{'name'} cmp $a->{'name'};
    }
    else
    {
      if (defined($a->{'julian_time'}) && defined($b->{'julian_time'}))
      {
        $b->{'julian_time'} <=> $a->{'julian_time'}
        or
        $b->{'name'} cmp $a->{'name'}
        or
        $b->{'path'} cmp $a->{'path'};
      }
      else
      {
        $b->{'name'} cmp $a->{'name'}
        or
        $b->{'path'} cmp $a->{'path'};
      }
    }
  }
}

########
# NAME
#   asmcmdbase_time_backward
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Order in this way:
#
#     1)  Directory aliases always comes first, then file aliases.
#     2a) If directory, sort in alphabetical order by name and then
#         by path, just like in asmcmdbase_name_forward().
#     2b) If file, sort by Julian date with most recent last, then 
#         alphabetically by name and then by path.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
#
# NOTES
#   We sort by Julian date in absolute number of days.  As in Unix versions of
#   ls, time backward puts the most recently updated files last, as in ls -rt.
########
sub asmcmdbase_time_backward 
{
  # If one alias is a file and another is a directory, then the directory
  # comes first.
  if ($a->{'alias_directory'} ne $b->{'alias_directory'}) 
  {
    $b->{'alias_directory'} cmp $a->{'alias_directory'};
  }

  # If both are directories, sort in same way as in asmcmdbase_name_forward().
  elsif ($a->{'alias_directory'} eq 'Y') 
  {
    $a->{'name'} cmp $b->{'name'}
    or
    $a->{'path'} cmp $b->{'path'};
  }

  # If both are files, sort first by Julian date, most recent last; then 
  # reverse alphabetically as in asmcmdbase_name_forward().
  else 
  {
    if (defined($a->{'julian_time'}) && defined($b->{'julian_time'}))
    {
      $a->{'julian_time'} <=> $b->{'julian_time'}
      or
      $a->{'name'} cmp $b->{'name'}
      or
      $a->{'path'} cmp $b->{'path'};
    }
    else
    {
      $a->{'name'} cmp $b->{'name'}
      or
      $a->{'path'} cmp $b->{'path'};
    }
  }
}

########
# NAME
#   asmcmdbase_rm_recur_order
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Delete in this order: 1) files, 2) dir with deepest number of levels first.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
#
# NOTES
#   Only asmcmdbase_process_rm() uses this routine.
########
sub asmcmdbase_rm_recur_order 
{
  # Delete in this order: 1) files, 2) dir with deepest number of levels first.
  $a->{'alias_directory'} cmp $b->{'alias_directory'}
  or
  asmcmdbase_levels($b->{'full_path'}) <=> asmcmdbase_levels($a->{'full_path'});
}

########
# NAME
#   asmcmdbase_levels
#
# DESCRIPTION
#   This routine returns the number of levels of subdirectries in a path.
#   For instance, +dgroup/ORCL/DATAFILE has three levels.  
#
# PARAMETERS
#   path   (IN) - a full path to an alias.
#
# RETURNS
#   The number of levels of subdirectories in a path.
#
# NOTES
#   The number of levels is always one more than the number of forward slashes
#   in the path.  The assumption is true because $path must be normalized
#   before calling this routine.  Normalized paths cannot have duplicate 
#   slashes, as in '+dgroup//ORCL'.
########
sub asmcmdbase_levels 
{
  my ($path) = shift;

  my (@levels);          # The number of levels of subdirectories in a path. #

  @levels = split (/\//, $path);

  return scalar @levels;
}
##############################################################################




######################### Parameter Parsing Routines #########################
########
# NAME
#   asmcmdbase_parse_remote_conn_str
#
# DESCRIPTION
#   This routine parses the remote connect string into two components & 
#   inquire user-input passwd:
#     1) user
#     2) identifier string
#
# PARAMETERS
#   cstr   (IN) - the connect string to be parsed.
#
# RETURNS
#   Zero on success; undefined on error.
#
# NOTES
#   The connect string should be in the format user/password@identifier.
########
sub asmcmdbase_parse_remote_conn_str 
{
  my ($cstr) = shift;

  my ($ident);     # The identifier portion of the connect string. #
  my ($usr);       # The username portion of $usrpswd. #
  my ($pswd);
  my (@eargs);

  if ($cstr !~ m'@') # usr name must be specified followed by '@' #
  {
    @eargs = ($cstr);
    asmcmdshare_error_msg(8010, \@eargs);
    return undef;
  }

  ($usr, $ident) = split (/\@/, $cstr);    # Split by '@'. #
  if ($usr eq '')
  {
    @eargs = ($cstr);
    asmcmdshare_error_msg(8010, \@eargs);
    return undef;
  }
  if ($ident eq '')
  {
    @eargs = ($cstr);
    asmcmdshare_error_msg(8011, \@eargs);
    return undef; 
  }

  # store parsed usr name & identifier into gobal variables #
  $rusr = $usr;
  ($rusr, $rpswd) = split(/\//, $rusr);
  $rident = $ident;
  # Prompt user for password if not already provided#
  $rpswd = asmcmdshare_getpswd() unless defined($rpswd);
  # 20721939: quote password to prevent SQL injection risks
  $rpswd = qq("$rpswd");
  return 0;
}


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

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

########
# NAME
#   asmcmdbase_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
#   True if $arg is a command that can take wildcards as part of its argument, 
#   false otherwise.
#
# NOTES
#   Currently, only cd, du, find, ls, and rm can take wildcard as part of
#   their arguments.
########
sub asmcmdbase_is_wildcard_cmd 
{
  my ($arg) = shift;

  return defined ($asmcmdbase_cmds{ $arg }) &&
         (asmcmdshare_get_cmd_wildcard($arg) eq "true") ;
}

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

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

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

  return 0;
}

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

  #include deprecated options if any
  if($asmcmdglobal_deprecated_options{ $cmd })
  {
    foreach my $key(keys %{$asmcmdglobal_deprecated_options{ $cmd }})
    {
      #include only if the option is changed and not discontinued
      push(@string, $asmcmdglobal_deprecated_options{$cmd}{$key}[0]);
    }
  }

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

  return 0;
}

########
# NAME
#   asmcmdbase_parse_int_cmd_line
#
# DESCRIPTION
#   This routine parses a line of command and divides it up into tokens of 
#   arguments, delimited by spaces.
#
# PARAMETERS
#   cmd_line  (IN)   - user-entered line of command, including the command
#                      name and its arguments.
#   argv_ref  (OUT)  - Reference to an array of arguments to return, with the 
#                      command name stored as element zero of the array, and 
#                      its arguments stored as the subsequent elements; much 
#                      like the array 'argv' in C.  Should be passed in as 
#                      an empty array.
#
# RETURNS
#   0 on success, -1 on error.
#
# NOTES
#   Arguments are delimited by whitespace, unless that whitespace is enclosed
#   within single quotes, in which case they are considered as part of one
#   argument.
#
#   Valid states for the state transition:
#     NO QUOTE - parsing a portion of $cmd_line that's *not* in quotes.
#     IN QUOTE - parsing a portion of $cmd_line that's in quotes.
#     SPACES   - same condition for NO QUOTE is true; also true: currently
#                parsing the delimiter $ASMCMDBASE_SPACE before tokens, or 
#                arguments.
#
#   State transition diagram:
#
#    Input -> 
#   ----------------------------------------------------
#   |State    | quote    | space    | other    | NULL  |
#   |---------+----------+----------+----------+-------|
#   |NO QUOTE | IN QUOTE | SPACES*  | NO QUOTE | DONE* |
#   |---------+---------------------+----------+-------|
#   |IN QUOTE | NO QUOTE | IN QUOTE | IN QUOTE | ERR   |
#   |---------+----------+----------+----------+-------|
#   |SPACES   | IN QUOTE | SPACES   | NO QUOTE | DONE* |
#   |--------------------------------------------------|
#
#   * In these cases, $token must have one complete argument, so add $token
#     to the output parameter array.
########
sub asmcmdbase_parse_int_cmd_line
{
  my ($cmd_line, $argv_ref) = @_;

  my ($char);                                # One character from $cmd_line. #
  my ($state) = 'NO QUOTE';
  my ($token) = '';                           # One argument from $cmd_line. #
  my ($offset);       # Offset to interate through $cmd_line using substr(). #
  my (@eargs);                                   # Array of error arguments. #

  # Iterate through $cmd_line character by character using substr().
  for ($offset = 0; $offset < length($cmd_line); $offset++) 
  {
    $char = substr ($cmd_line, $offset, 1);

    if ($state eq 'NO QUOTE')
    {
      if ($char eq "'")
      {
        $state = 'IN QUOTE';
      }
      elsif ($char eq $ASMCMDBASE_SPACE)
      {
        $state = 'SPACES';
        push (@{ $argv_ref }, $token);
        $token = '';
      }
      else
      {                # $char is any non-space, non-single quote character. #
        $token .= $char;
      }
    }
    elsif ($state eq 'IN QUOTE')
    {
      if ($char eq "'")
      {
        $state = 'NO QUOTE';
      }
      else
      {                           # $char is any non-single quote character. #
        $token .= $char;
      }
    }
    elsif ($state eq 'SPACES')
    {
      if ($char eq "'")
      {
        $state = 'IN QUOTE';
      }
      elsif ($char ne $ASMCMDBASE_SPACE)
      {                                  # $char is any non-space character. #
        $token .= $char;
        $state = 'NO QUOTE';
      }
      else
      {                                             # $char must be a space. #
        # Multiplie consecutive spaces encountered; do nothing.
      }
    }
    else
    {
      # Should never get here.  Signal internal error.
      @eargs = ("asmcmdbase_parse_int_cmd_line_05");
      asmcmdshare_signal_exception (8202, \@eargs);
    }
  }

  push (@{ $argv_ref }, $token);

  if ($state eq 'IN QUOTE')
  {             # Error: somebody forgot to close the quote; parsing failed. #
    return -1;
  }

  return 0;
}
##############################################################################





############################# Error Routines #################################
########
# NAME
#   asmcmdbase_syntax_error
#
# DESCRIPTION
#   This routine prints the correct syntax for a command to STDERR, used 
#   when there is a syntax error.  If the command with bad syntax is asmcmd 
#   itself, then asmcmdbase_syntax_error lso calls exit() to quit out.
#
# PARAMETERS
#   cmd   (IN) - user-entered command name string.
#
# RETURNS
#   1 if the command belongs to this module; 0 if command not found.
#
# NOTES
#   These errors are user-errors and not internal errors.  They are of type
#   record, not signal.  Thus, even if exit() is called, the exit value is
#   zero.
########
sub asmcmdbase_syntax_error 
{
  my ($cmd) = shift;
  my ($cmd_syntax);                               # Correct syntax for $cmd. #
  my ($cmd_print_name) = '';
  my ($succ) = 0;

  $cmd_syntax = asmcmdshare_get_help_syntax($cmd);    # Get syntax for $cmd. #
  $cmd_syntax = asmcmdshare_trim_str ($cmd_syntax);   # Trim blank spaces #

  if (defined ($cmd_syntax))
  {
    $cmd_print_name = $cmd if ($cmd ne 'asmcmd');

    asmcmdshare_printstderr 'usage: ' . $cmd_syntax . "\n";
    asmcmdshare_printstderr 'help:  help ' . $cmd_print_name . "\n";

    # Can't proceed if asmcmd has bad syntax. #
    exit -1 if (($cmd eq 'asmcmd'));
    $succ = 1;
  }

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

  return $succ;
}


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






########################## Initialization Routines ###########################
########
# NAME
#   asmcmdbase_check_insttype
#
# DESCRIPTION
#   This routine checks the instance type of the connected instance to see
#   if it is an ASM instance.  If not, it prints error and exits 0.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null; may not return if exit 0.
#
# NOTES
#   This routine should be called before executing any asmcmd commands.
########
sub asmcmdbase_check_insttype 
{
  my ($dbh) = shift;
  my ($insttype);                 # Instance type of the connected instance. #

  $insttype = asmcmdshare_get_insttype ($dbh);# Get instance type using SQL. #

  # It's 'ASM' in 10gR2 and 'asm' in 10gR1.
  if ( ($insttype ne 'ASM') && ($insttype ne 'asm') ) 
  {
    asmcmdshare_error_msg(8003, undef);
    exit 0;
  }
  asmcmdshare_trace(3, "NOTE: Instance type: $insttype", 'n', 'n');
  return;
}

########
# NAME
#   asmcmdbase_init_global
#
# DESCRIPTION
#   This routine initializes the global variables in the hash 
#   %asmcmdglobal_hash.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   
########
sub asmcmdbase_init_global 
{
  my ($dbh) = shift;

  # Default initial directory is '+'.
  $asmcmdglobal_hash{'cwdnm'}  = '+';

  # Default group is first the first group.
  $asmcmdglobal_hash{'cwdref'} = 1 << 24;
  $asmcmdglobal_hash{'gnum'}   = 1;
}
##############################################################################




############################## SQL Routines ##################################
########
# NAME
#   asmcmdbase_mkdir_sql
#
# DESCRIPTION
#   This routine constructs the SQL used to create a new directory and calls
#   asmcmdshare_do_stmt() to execute it.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gname  (IN) - name for diskgroup in which to create the directory.
#   dir    (IN) - full path to the directory to be created.
#
# RETURNS
#   Null.
########
sub asmcmdbase_mkdir_sql 
{
  my ($dbh, $gname, $dir) = @_;
  my ($ret, $qry);

  $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" add directory '$dir'";
  $ret = asmcmdshare_do_stmt($dbh, $qry);

  return;
}

########
# NAME
#   asmcmdbase_rm_sql
#
# DESCRIPTION
#   This routine constructs the SQL used to drop a file or directory from
#   a diskgroup.  It calls asmcmdshare_do_stmt() to execute it.
#
# PARAMETERS
#   dbh          (IN) - database handle.
#   gname        (IN) - name of diskgroup where to drop the file or directory.
#   alias        (IN) - full path to the alias to be dropped.
#   is_dir       (IN) - 'Y' if $alias is a directory, 'N' otherwise.
#
# RETURNS
#   Null.
########
sub asmcmdbase_rm_sql
{
  my ($dbh, $gname, $alias, $is_dir) = @_;
  my $ret;
  my $qry;

  # Construct the correct SQL for either a directory or a file.
  if ($is_dir eq 'Y') 
  {                                                        # Drop directory! #
    $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop directory '$alias'";
  }
  else 
  {                                                             # Drop file! #
    $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop file '$alias'";
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);

  return;
}

########
# NAME
#   asmcmdbase_mkalias_sql
#
# DESCRIPTION
#   This routine constructs the SQL used to create a user alias. It calls 
#   asmcmdshare_do_stmt() to execute it.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gname  (IN) - name of diskgroup where to create the user alias.
#   sys_a  (IN) - full path to the system alias to which the new user alias 
#                 will point.
#   usr_a  (IN) - full path specifying the location for the new user alias.
#
# RETURNS
#   Null.
########
sub asmcmdbase_mkalias_sql 
{
  my ($dbh, $gname, $sys_a, $usr_a) = @_;
  my ($ret, $qry);

  $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" add alias '$usr_a' for '$sys_a'";

  $ret = asmcmdshare_do_stmt($dbh, $qry);

  return;
}

########
# NAME
#   asmcmdbase_rmalias_sql
#
# DESCRIPTION
#   This routine constructs the SQL used to drop a user alias or to drop
#   recursively a directory tree that contains only user aliases.  It 
#   calls asmcmdshare_do_stmt() to execute it.
#
# PARAMETERS
#   dbh      (IN) - initialized database handle, must be non-null.
#   gname    (IN) - name of diskgroup where to drop $alias.
#   alias    (IN) - path specifying the location of the alias to be dropped.
#   recurse  (IN) - 1 iff $alias points to a directory to be dropped 
#                   recursively.
#
# RETURNS
#   Null.
########
sub asmcmdbase_rmalias_sql 
{
  my ($dbh, $gname, $alias, $recurse) = @_;
  my ($ret, $qry);

  if ($recurse == 1) 
  {                                                  # Drop directory force! #
    $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop directory '$alias' force";
  }
  else 
  {                                                       # Drop user alias! #
    $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop alias '$alias'";
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
}

########
# NAME
#   asmcmdbase_is_bal
#
# DESCRIPTION
#   This routine constructs the SQL used to determine whether diskgroup number
#   $gnum is currently rebalancing.  It calls asmcmdshare_do_select() to 
#   execute it.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#   gnum  (IN) - group number of the diskgroup in question.
#
# RETURNS
#   String: 'Y' if diskgroup $gnum is currently rebalancing; 'N' otherwise.
########
sub asmcmdbase_is_rbal 
{
  my ($dbh, $gnum) = @_;

  my ($sth);                                        # SQL statement handler. #
  my ($qry);                                             # SQL query string. #
  my ($row);                                     # One row of query results. #
  my ($rebal) = 'N';                      # Return string; see RETURN above. #

  $qry = 'select operation from v$asm_operation where group_number=?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $rebal = 'Y' if (defined ($row) && ($row->{'OPERATION'} eq 'REBAL'));
  asmcmdshare_finish ($sth);

  return $rebal;
}

########
# NAME
#   asmcmdbase_get_alias_path
#
# DESCRIPTION
#   This routine constructs the SQL used to retrieve either a system or a
#   user alias name when given a group number and a file number.  It calls
#   asmcmdshare_do_select() to execute it.  Finally, it calls 
#   asmcmdbase_find_sql() to retrieve the full path to the user or system 
#   alias in question.
#
# 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.
#   sys_cre  (IN) - 'Y' if we query for system alias; 'N' if we query for user
#                   alias.
#
# RETURNS
#   Full path to the request alias; 'none' if not found (only when sys_cre
#   is 'N'.
#
# NOTES
#   
########
sub asmcmdbase_get_alias_path 
{
  my ($dbh, $gnum, $fnum, $sys_cre) = @_;

  my ($sth, $qry, $row);
  my ($name);                                 # Name of the retrieved alias. #
  my ($par_id);                       # Parent index of the retrieved alias. #
  my ($path);                            # Full path to the retrieved alias. #
  my (@results); # Array of hashes: return-format of asmcmdshare_find_int(). #

  $qry = 'select name, parent_index from v$asm_alias' .
         ' where group_number = ?' .
         ' and file_number = ? and system_created = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  $sth->bind_param(2,$fnum);
  $sth->bind_param(3,$sys_cre,SQL_VARCHAR);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $name = $row->{'NAME'};
  $par_id = $row->{'PARENT_INDEX'};
  asmcmdshare_finish($sth);

  # If sys_cre is 'N', then the alias may not exist.  Return 'none'.
  return 'none' unless (defined ($name));

  # Retrieve the full path to the alias.
  @results = asmcmdbase_find_sql($dbh, '+', $name, undef, $par_id, 0, 0);
  $path = $results[0]->{'full_path'};

  return defined $path ? $path : 'none';
}

########
# NAME
#   asmcmdbase_get_ct
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch a list of row(s) from
#   [g]v$[asm|ios]_client
#
# PARAMETERS
#   dbh    (IN) - database handle.
#   gname  (IN) - optional: limit select by this group number.
#   global (IN) - flag to query global view
#   inst   (IN) - type of instance where the data is taken from.
#
# RETURNS
#   An array containing zero or one or more rows from v$asm_client.  The column
#   values for each row are stored in a hash, the reference to which
#   is indexed in the array.
#   If inst is undefined, then asm instance is assumed.
########
sub asmcmdbase_get_ct
{
  my ($dbh, $gname, $global, $inst) = @_;
  my $gnum;        # The group number for the diskgroup specified by $gname. #
  my $qry;
  my $row;
  my $sth;
  my @ct_list;              # The return array of hashes; see RETURNS above. #

  $inst = 'asm' unless(defined($inst));

  if ($global)
  {
    $qry = 'select * from gv$'.$inst.'_client';
  }
  else
  {
    $qry = 'select * from v$'.$inst.'_client';
  }

  # Narrow select if $gname is specified.
  if (defined ($gname))
  {
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);  # Get group num. #
    if (!defined($gnum))
    {
      my (@eargs) = ($gname);
      # ASMCMD-8001 "diskgroup '%s' does not exist or is not mounted"
      asmcmdshare_error_msg(8001, \@eargs);
      return @ct_list;
    }

    $qry = $qry . ' where group_number = ?';
  }

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  if (defined ($gnum))
  {
    $sth->bind_param(1,$gnum,SQL_INTEGER);
  }
  asmcmdshare_do_execute($sth);

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

    if ($inst eq 'ios')
    {
      $ct_info{'group_number'} = '';
      $ct_info{'status'}       = '';
    }
    else
    {
      $ct_info{'group_number'} = $row->{'GROUP_NUMBER'};
      $ct_info{'status'}       = $row->{'STATUS'};
      $ct_info{'group_name'}   =
        asmcmdshare_get_gname_from_gnum($dbh, $ct_info{'group_number'});
    }
    $ct_info{'instance_name'} = $row->{'INSTANCE_NAME'};
    $ct_info{'db_name'}       = $row->{'DB_NAME'};

    if (!defined($ct_info{'group_name'}))
    {
      $ct_info{'group_name'} = '';
    }

    # New columns for 10gR2, so check for forward compatiblity.
    if (asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                $ASMCMDGLOBAL_VER_10gR2) >= 0)
    {
      if ($inst eq 'ios')
      {
        $ct_info{'software_version'}   = '';
        $ct_info{'compatible_version'} = '';
      }
      else
      {
        $ct_info{'software_version'}   = $row->{'SOFTWARE_VERSION'};
        $ct_info{'compatible_version'} = $row->{'COMPATIBLE_VERSION'};
      }
    }

    # If we want to see global information
    if ($global)
    {
      $ct_info{'inst_id'} = $row->{'INST_ID'};
    }

    push (@ct_list, \%ct_info);
  }

  asmcmdshare_finish($sth);

  return (@ct_list);
}

########
# NAME
#   asmcmdbase_connect
#
# DESCRIPTION
#   This routine initializes the database handle by establishing a connection
#   to an ASM instance, either with a bequeath connection or by the listener.
#
# PARAMETERS
#   usr    (IN) - username of the connect string.
#   pswd   (IN) - password for the username.
#   ident  (IN) - the identifier part of the connect string.
#
# RETURNS
#   An initialized database handle; undefined if connect fails.
#
# NOTES
#   The connect string is in the form of <user>[/password][@connect_identifier].
#   The identifier is optional.  If no identifier is in the connect string,
#   then we use the value of the ORACLE_SID environment variable as the
#   identifier.
#
#   In Flex ASM, ASMCMD will not necessarily connect to the local ASM instance
#   if there is one running on the node nor does it depend on the value of
#   ORACLE_SID environment variable. If Flex ASM is enabled, the connect string
#   and credentials are queried using ASMCMDGetConnDetails and these are used
#   to establish connection to any one of the ASM instances running in the
#   cluster.
#
########
sub asmcmdbase_connect
{
  my ($ident, $constr) = @_;
  my ($usr, $pswd, $contype);
  my ($dbh);              # Database handle, to be initialized and returned. #
  my ($driver);              # Driver parameter for the DBI->connect() call. #
  my ($host);              # The hostname of the host where listener is run. #
  my ($port);                          # The port where the listener is run. #
  my ($sid);              # The SID of the ASM instance we're connecting to. #

  my (%session_mode);        # bug-5402303, session attr for DBI->connect(). #
  my (@eargs);                                 # Error arguments for assert. #
  my ($clus_mode);                                       # The Cluster mode. #
  my ($stor_access);                                  # Storage Access Mode. #
  my ($rc, $str);                         # Return value and Connect string. #
  my ($creds);                   # Credentials which is an array of array's. #
  my ($instance_name);       # ASM instance to which ASMCMD is connected to. #
  my (@result);
  my ($discover) = $asmcmdglobal_hash{'discover'};

  # Environment variable 'ORACLE_HOME' will be set, otherwise
  # ASMCMD init code will bail out.

  # directories in which kfod can be found.
  my (@patharr) =("$ENV{'ORACLE_HOME'}/bin/",
                  "$ENV{'ORACLE_HOME'}/rdbms/bin/");

  @result = asmcmdshare_execute_tool ("kfod", 
                                      ".exe", 
                                      "op=getclstype", 
                                      \@patharr);
  chomp($result[0]); # Remove the newline character at the end of the string #

  # split the "[cluster mode] - [storage access mode]" information
  ($clus_mode, $stor_access) = split / - /, $result[0];

  chomp($clus_mode);
  asmcmdshare_trace(3, "cluster mode - $clus_mode", 'y', 'n');

  my $logstr = "Printing the connection string \n";
  # 25514391: If connecting via a member cluster, use sysdba privileges   #
  # instead of defaulting to sysasm.                                      #
  if ($clus_mode eq 'Client cluster')
  {
    $contype = 'sysdba';
  }
  else
  {
    $contype = $asmcmdglobal_hash{'contyp'};
  }

  # Verify connection admin type, sysdba or sysasm, and assign proper ora #
  # session mode. (sysasm = 32768 or sysdba = 2)                          #
  if (($contype =~ /^sysdba$/i))
  {
    $session_mode{'ora_session_mode'} = 2; 
  }
  else
  {
    $session_mode{'ora_session_mode'} = 32768;
  }

  $session_mode{'PrintError'} = 0;

  $driver = 'dbi:Oracle:';

  # Use PERL2C interface to obtain connection string to guarantee we connect
  # only to ASM instance.  ORACLE_SID set to DBSID was allowing connection to
  # DB Instance and operations were failing non-user-friendly.

  # clus_mod - Flex mode disabled(or legacy), Flex mod enabled, Client 
  #            cluster - in all these cases if --discover option specified
  #            obtain connection string using PERL2C.

  # 23721855: When global_hash{target} is set to other value than 'ASM' means
  # that the caller wants to retrieve information for the instance of the
  # specified type. It is the caller's responsibility to set back this value
  # to 'ASM' when it reconnects to the ASM instance.
  
  if ($asmcmdglobal_hash{'target'} ne 'ASM' ||
      $clus_mode eq 'ASM cluster : Flex mode disabled' && ( $discover == 0 ))
  {
    asmcmdshare_trace('3', "in flex mode disabled", 'y', 'n');
    $logstr .="ENV{ORACLE_HOME} = <" . $ENV{'ORACLE_HOME'} . ">\n" 
      if defined($ENV{'ORACLE_HOME'});
    $logstr .= "ENV{ORACLE_SID} = <" . "$ENV{'ORACLE_SID'}" . ">\n" 
      if defined($ENV{'ORACLE_SID'});

    # ident will be valid only in case of remote connect
    if (defined($ident) && $ident ne '')
    {
      my $i = rindex($ident , '.');

      if($i ge 0)
      {
        $sid = substr($ident, $i+1);
        $ident =~ s/\Q.$sid\E//g;
        $host = $ident;
      }
      else
      {
        $sid = $ident;
      }

      $port = $asmcmdglobal_hash{'port'} 
                      if (defined ($asmcmdglobal_hash{'port'}));

      # Now we have the values for $host, $port, and $sid, finish constructing
      # the $driver string.
      if (defined ($host)) 
      {
        $driver .= 'host=' . $host;
        $rhost = $host;
      }
    
      $rsid = $sid;
      $rport = $port if (defined ($port)) ;
   
      if (defined ($rport)) 
      {
        $driver .= ';port=' . $rport;
      }
      # No port specification needed by default; defaults to 1521. #

      $driver .= ';sid=' . $rsid;                      # SID always required. #
      $usr = $rusr;
      $pswd = $rpswd;
    }
  }
  elsif ((($clus_mode eq 'ASM cluster : Flex mode disabled') &&
          ($discover == 1)) ||
          ($clus_mode eq 'ASM cluster : Flex mode enabled') ||
         ($clus_mode eq 'Client cluster'))
  {
    undef &ASMCMDGetConnDetails;
    my ($conn) = DynaLoader::dl_find_symbol($asmcmdglobal_hash{'asmperl'},
                                          "XS_ASMCMDCLNT_kgfnGetConnDetails");
    DynaLoader::dl_install_xsub("ASMCMDGetConnDetails", $conn);

    # Get connection string and credentials from kgfnGetConnDetails using 
    # PerlToC API
    ($rc, $str, $creds) = ASMCMDGetConnDetails(0,
                                               $asmcmdglobal_hash{'service'},
                                               $asmcmdglobal_hash{'inst'},
                                               '_asmcmd');
    # incase of error, record the $rc & $str.
    if ($rc != 0 )
    {
      asmcmdshare_trace (1, 
                         "Error while fetching connection string for FlexASM".
                         " Error :$rc Errstr : $str\n",
                         'y',
                         'n');
      return;
    }
    # KGFNGETCONNDETAILS_OVERFLOW - overflow of connection strings.
    if ($rc == 1)
    {
      asmcmdshare_trace_msg(3, 9480);
      return;
    }
    # KGFNGETCONNDETAILS_NONE - no connection string
    if ($rc == 2)
    {
      asmcmdshare_trace_msg (3, 9481);
      return;
    }
    # KGFNGETCONNDETAILS_OTHER - unknown issue.
    if ($rc == -1)
    {
      asmcmdshare_trace_msg (3, 9482);
    }

    # 20402145: Parse the rhost, rport & rsid for remote copy
    # operation - local->remote,  remote->local.
    if (defined($ident) && $ident ne '')
    {
      my $i = rindex($ident , '.');

      asmcmdshare_trace(3, "rindex - $i", 'y', 'n');
      if($i ge 0)
      {
        $rsid = substr($ident, $i+1);
        $ident =~ s/\Q.$rsid\E//g;
        $rhost = $ident;
        asmcmdshare_trace(3, "rsid $rsid", 'y', 'n');
        asmcmdshare_trace(3, "rhost $rhost", 'y', 'n');
      }
      else
      {
        $rsid = $ident;
        asmcmdshare_trace(3, "rsid  $rsid", 'y', 'n');
      }
      $rport = $asmcmdglobal_hash{'port'}
                      if (defined ($asmcmdglobal_hash{'port'}));
      asmcmdshare_trace(3, "rport  $rport", 'y', 'n');
    }

    $driver .= $str;
    $usr = @{$$creds[0]}[1];
    $pswd = @{$$creds[0]}[0];

    asmcmdshare_trace(3, "driver  $driver", 'y', 'n');
    asmcmdshare_trace(3, "user $usr ", 'y', 'n') if defined ($usr);
  }
  elsif($clus_mode eq 'Unknown')
  {
    @eargs = ("Cluster Type is Unknown");
    asmcmdshare_assert(0, \@eargs);
  }
  
  # Now try to connect!  ora_session_mode => 2 means to connect as sysdba
  $dbh = DBI->connect($driver, $usr, $pswd, \%session_mode);

  $logstr .= "contype = <" . $contype . ">\n"
    if defined($contype);
  $logstr .= "driver = <" . $driver . ">\n" if defined $driver;
  $logstr .= "instanceName = <" . $asmcmdglobal_hash{'inst'} . ">\n" 
             if defined $asmcmdglobal_hash{'inst'};
  $logstr .= "usr = <" . $usr . ">\n" if defined $usr;
  $logstr .= "port = <" . $port . ">\n" if defined $port;
  $logstr .= "ServiceName = <" . $asmcmdglobal_hash{'service'} . ">\n" 
             if defined $asmcmdglobal_hash{'service'};
  chomp $logstr;
  asmcmdshare_trace(3, "$logstr", 'y', 'n');

  if(!defined($dbh))
  { 
     $$constr = " Driver="."$driver";
     $$constr .=" User=".$usr if defined($usr);
     $$constr .= " Session_mode=".$contype if defined($contype);
     $$constr .= " ServiceName=" .$asmcmdglobal_hash{'service'} 
                   if defined($asmcmdglobal_hash{'service'});
     $$constr .= " port=$port" if defined($port);
     asmcmdshare_trace(3, "Failed to connect to ASM instance", 'y', 'n');
     asmcmdshare_trace(3, $DBI::errstr, 'y', 'n');


     # If Flex ASM, try connecting using another set of credentials. 
     if (($clus_mode eq 'ASM cluster : Flex mode enabled') || 
         ($clus_mode eq 'Client cluster'))
     {
        my $i;
        for ( $i = 1; (!defined($dbh) && ($i < @{$creds})); $i++)
        {
           $usr = @{$$creds[$i]}[1];
           $pswd = @{$$creds[$i]}[0];
           asmcmdshare_trace('3', "Connecting to ASM instance as User:"
                                  ." $usr", 'y', 'n');
           $dbh = DBI->connect($driver, $usr, $pswd, \%session_mode);
           asmcmdshare_trace(3, $DBI::errstr, 'y', 'n') if (!defined($dbh));
        }

        if(defined($dbh))
        {
           $instance_name = asmcmdshare_get_instance_name($dbh);
           asmcmdshare_trace(3, "Successfully connected to Flex-ASM instance "
                             ."$instance_name", 'y', 'n');
        }
     }
  }
  else
  {
     $instance_name = asmcmdshare_get_instance_name($dbh);
     asmcmdshare_trace(3, "Successfully connected to " .
                       "$asmcmdglobal_hash{'target'} instance $instance_name",
                       'y', 'n');
  }

  return $dbh;
}

########
# NAME
#   asmcmdbase_disconnect
#
# DESCRIPTION
#   This routine disconnects from the ASM instance, given an initialized
#   database handle.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle.
#
# RETURNS
#   Undefined if $dbh is undefined; the return value of $dbh->disconnect()
#   upon success.
########
sub asmcmdbase_disconnect
{
  my $dbh = shift;
  return undef unless(defined $dbh);
  return $dbh->disconnect;
}
##############################################################################

############################## Help Routines #################################
########
# NAME
#   asmcmdbase_get_asmcmd_cmds
#
# DESCRIPTION
#   This routine constructs a string that contains a list of the names of all
#   ASMCMD internal commands and returns this string.
#
# PARAMETERS
#   None.
#
# RETURNS
#   A string contain a list of the names of all ASMCMD internal commands.
#
# NOTES
#   Used by the help command and by the error command when the user enters
#   an invalid internal command.
########
sub asmcmdbase_get_asmcmd_cmds
{
  return asmcmdshare_filter_invisible_cmds(%asmcmdbase_cmds);
}

##############################################################################
1;

OHA YOOOO