# Copyright (c) 2004, 2014, 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)
#    manuegar   08/19/13 - Backport manuegar_bug-13951456 from main
#    apfwkr     08/01/12 - Backport pvenkatr_bug-12631186 from
#    pvenkatr   02/14/12 - Backport pvenkatr_bug-13590569 from main
#    apfwkr     01/04/12 - Backport moreddy_bug-12818129 from main
#    shmubeen   06/12/11 - backport of 12538042
#    moreddy    01/13/11 - Backport moreddy_bug-8667038 from main
#    moreddy    01/12/11 - Backport moreddy_bug-6969662 from main
#    pvenkatr   11/01/10 - Backport pvenkatr_bug-9778018 from main
#    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_lsof
#
# 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_int
#   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
#
# 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
                );

use strict;
use DBI;
use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat);
use Math::BigInt;
use asmcmdglobal;
use asmcmdshare;

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

use POSIX qw(:termios_h);

############################ Global Constants ###############################
my  (%asmcmdbase_cmds) = (cd      => {wildcard     => 'True',
                                      no_instance  => 'True',
                                                      },
                          cp      => {wildcard     => '',
                                      no_instance  => 'True',
                                      flags        =>  {
						        'r'=>'recursive',
                                                        'service=s'=>
                                                       'servicename',
                                                       'port=s'=>'portnum'}
                                                      },
                          du      => {wildcard     => 'True',
                                      no_instance  => 'True',
                                      flags        =>  {'suppressheader'=>
                                                       'suppressHeaders'}
                                                      },
                          find    => {wildcard     => 'True',
                                      no_instance  => 'True',
                                      flags        =>  {'type=s'=>'fileType'}
                                                      },
                          help    => {
                                     },

                          ls      => {wildcard     => 'True',
                                      no_instance  => 'True',
                                      flags        =>  {'l'=>'longListing',
                                                        'i'=>'interactive',
                                                        's'=>'size',
                                                        'd'=>'directory',
                                                        'reverse'=>
                                                       'reverseOrder',
                                                        't'=>'timeStamp',
                                                        'L'=>'fileReference',
                                                        'absolutepath'=>
                                                       'absolutepath',
                                                        'g'=>'global',
                                                        'suppressheader'=>
                                                       'suppressHeaders',
                                                        'permission'=>
                                                       'showPermission'}
                                                      },
                          lsct    => {no_instance  => 'True',
                                      flags        =>  {'g'=>'global',
                                                        'suppressheader'=>
                                                       'suppressHeaders'}
                                                      },
                          lsdg    => {no_instance  => 'True',
                                      flags        =>  {'discovery'=>'noCache',
                                                        'g'=>'global',
                                                        'suppressheader'=>
                                                       'suppressHeaders'}
                                                      },
                          mkalias => {no_instance  => 'True',
                                                      },
                          mkdir   => {no_instance  => 'True',
                                                      },
                          pwd     => {no_instance  => 'True',
                                                      },
                          rmalias => {no_instance  => 'True',
                                      flags        =>  {'r'=>'recursive'}
                                                      },
                          rm      => {wildcard     => 'True',
                                      no_instance  => 'True',
                                      flags        =>  {'r'=>'recursive',
                                                        'f'=>'force'}
                                                      },
                          lsof    => {no_instance  => 'True',
                                      flags        =>  {'dbname=s'=>'database',
                                                        'G=s'=>'diskGroup',
                                                        'C=s'=>
                                                       'clientInstanceName',
                                                        'suppressheader'=>
                                                       'suppressHeaders'}
                                                      }
                          );

my ($ASMCMDBASE_SPACE) = ' ';           # Constant string for a space.      #
my ($ASMCMDBASE_SIXMONTH) = 183;        # Number of days in six months.     #
my ($ASMCMDBASE_DATELEN) = 15;          # 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' keyward 
# 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_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;

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

  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);
  my (@lsof_list);
  my ($k, $v, $h);
  my (%min_col_wid, $print_format, $printf_code, @what_print);

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

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

  #filter disk group
  if (defined($args{'G'}))
  {
    #get disk group name
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $args{'G'});
    if (!defined($gnum))
    {
      return;
    }
    push (@where, "group_kffof = ". $gnum);
  }

  #filter database
  if (defined($args{'dbname'}))
  {
    push (@where, "dbname_kffof = '".$args{'dbname'}."'");
  }

  #filter instance
  if (defined($args{'C'}))
  {
    push (@where, "instancename_kffof = '".$args{'C'}."'");
  }

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

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where,
                                        \@order);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($sth);
 
  #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]/;
      $file_info{$k}    = $v;
      $min_col_wid{$k} = max($min_col_wid{$k}, length($v));
    }
    push (@lsof_list, \%file_info);
  }

  asmcmdshare_finish($sth);

  #create print format
  $print_format = '';

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

  #print header
  if (!defined ($args{'suppressheader'}) )
  {
    $printf_code = "printf \"$print_format\", ";
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, "\'" . $asmcmdbase_lsof_header{$_} . "\'");
    }
    $printf_code .= "(" . join (", ", @what_print) . ")";

    eval $printf_code;
  }

  #print rows
  foreach $h (@lsof_list)
  {
    $printf_code = "printf \"$print_format\", ";
    @what_print = ();
    foreach (@what)
    {
      push (@what_print, "\'" . $h->{$_} . "\'");
    }
    $printf_code .= "(" . join (", ", @what_print) . ")";
    eval $printf_code;
  }

}


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

  # Check if number of non-option parameters are correct.
  if (@ARGV < 1) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # Process creation one path at a time.
  while (defined ($dir = shift (@ARGV))) 
  {
    $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. #

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

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

  # Check if number of non-option parameters are correct.
  if (@ARGV == 0) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # 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 < @ARGV; $i++) 
      {
        if ($ARGV[$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.
  while (defined ($alias_path = shift (@ARGV))) 
  {
    # 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, $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');
            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.
    $spprserr = 1 if (defined ($args{'r'}));
    %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, $gnum, undef, $par_id, 
                               $alias_name, undef, 0, 0);
        $is_dir = $entries2[$i]->{'alias_directory'};
        $gname = asmcmdshare_get_gname_from_gnum ($dbh, $gnum);
        
        # Run SQL to remove an entry.     
        asmcmdbase_rm_sql($dbh, $gname, $alias_path, $is_dir);
      }
    }
  }
  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);

  # Check if number of non-option parameters are correct.
  if (@ARGV != 2) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $sys_a = shift (@ARGV);
  $usr_a = shift (@ARGV);

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

  # Check if number of non-option parameters are correct.
  if (@ARGV == 0) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # Process each alias entry one at a time. (rmalias can take multiple alias
  # arguments.
  while (defined ($alias_path = shift (@ARGV))) 
  {
    # 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);

  # Check if number of non-option parameters are correct.
  if (@ARGV != 0) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

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

  # Check if number of non-option parameters are correct.
  if (@ARGV != 1) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $dir = shift (@ARGV);

  # 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 ($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 = shift(@ARGV) if (@ARGV > 0);
  $alias = '' unless defined ($alias);     # Defaults to '', or current dir. #

  # 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.
      asmcmdshare_get_subdirs($dbh, \@entry_list, $entry_info{'group_number'}, 
                              $entry_info{'reference_index'}, 
                              $entry_info{'parent_index'}, 
                              $entry_info{'name'}, undef, 0, $get_file_info);

      $entry_list[$i]->{'path'} = $entry_info{'path'};
      $entry_list[$i]->{'name_print'} = $entry_info{'name'};

      # If the entry is a file, process the file column values from v$asm_file.
      if ($entry_list[$i]->{'alias_directory'} eq 'N') 
      {
        asmcmdbase_ls_process_file ($dbh, $entry_list[$i], \%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 '+'.
      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'},
                                       \%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) 
      {
        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);

 # Check if number of non-option parameters are correct.
  if(@ARGV != 2)
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

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

  # Run the internal find routine.
  @match = asmcmdbase_find_int($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');
    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);                     # asmcmdbase_parse_int_args() return value. #
  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. #

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

  # Check if number of non-option parameters are correct.
  if (@ARGV > 1) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $dir = shift (@ARGV);
  $dir = '' unless (defined ($dir));

  # Find all system alias only under $dir.
  @match = asmcmdbase_find_int ($dbh, $dir, '*', undef, undef, 1, 0);
  return if (@match == 0);

  # 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'}, \%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;
  }

  # 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'})) 
  {
    printf ("%7s%20s\n", 'Used_MB', 'Mirror_used_MB');
  }

  printf ("%7s%20s\n", $uMB, $mirUMB);

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

  # Check if number of non-option parameters are correct.
  if (@ARGV > 1) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $gname = shift (@ARGV);

  # 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 (%args);                             # Argument hash used by getopts(). #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my (@client);                # Array of hashes of clients in v$asm_client. #
  my ($gname);                             # Group name, as entered by user. #
  my ($row) = '';                     # One row of one client, for printing. #
  my (@header);
  my (@rowval);
  my ($iter);                      # Iteration variable for foreach() loops. #
  my ($global) = 0;                            # True iff query global view. #
  my (%min_col_wid);                       # Minimum column width to disply. #
  # 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);

  # Check if number of non-option parameters are correct.
  if (@ARGV > 1) 
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

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

  $gname = shift (@ARGV);
  @client = asmcmdbase_get_ct($dbh, $gname, $global);
  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');

    printf $row, @header
           if (!defined ($args{'suppressheader'}));
  }
  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');

    printf $row, @header
           if (!defined ($args{'suppressheader'}));
  }

  # 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'});
      printf $row, @rowval;
    }
    else 
    {                                                       # 10gR1 version. #
      push @rowval, ($iter->{'db_name'}, $iter->{'status'}, 
                     $iter->{'instance_name'},
                     $iter->{'group_name'});
      printf $row, @rowval;
    }
  }

  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);
    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 
{
  use Text::ParseWords;

  my ($dbh) = shift;                                # local auto-variables #
  my ($ret);
  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_str);                                    # target file/dir args #
  my ($tgt_path);

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

  my ($src_dbh) = $dbh;
  my ($tgt_dbh) = $dbh;

  my ($src_rem_ct) = 0;   #number of source remote connection parameters
  my ($driver);

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

  # if optional --serivce parameter is provide, 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" ;
  }

  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');
  
  # check if src & tgt parameters are correct #
  if (@ARGV < 2)
  {
    asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # last element is the target
  $tgt_str = pop(@ARGV);

  # remaining is the source
  @src_strs = @ARGV;

  # 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)
  {
    $src_remote_inst = 1;
    @arg_tokens = &quotewords(':', 0, $src_strs[0]);
    $remote_conn_str = $arg_tokens[0];
    push (@src_paths, $arg_tokens[1]);
    #get and parse connection data
    return 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 ) ;

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

  if ( $tgt_remote_inst  == 1 )
  {
    @arg_tokens = &quotewords(':', 0, $tgt_str);
    $remote_conn_str = $arg_tokens[0];
    $tgt_path = $arg_tokens[1];
    #get and parse connection data
    return 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 #
    $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;
    }
    asmcmdbase_check_insttype($remote_dbh);

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

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

}

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



################### 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 format.  It 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.
#   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, $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, $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 ($mon, $day, $time);
  my ($diff);           # Difference of    $min_col_wid_ref->{'date_print'}  #
                        #                - $ASMCMDBASE_DATELEN 
                        #                + 1                                .#
  my (@print_args);

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

  # In case the header length is longer than $ASMCMDBASE_DATELEN, 
  # calculate difference and use it as minimum length of time or year.
  # Add 1 because default is at least one extra space, compensated by
  # one less space between the fields 'Time' and 'Sys'.
  $diff = $min_col_wid_ref->{'date_print'} - $ASMCMDBASE_DATELEN + 1;

  #           MON DD HH:MI:SS or
  #           MON DD  YYYY
  $row_l .= "%-3s %2s %8s" . "%$diff" . "s ";   # Month, day, and time/year. #

  $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'} .= '/';
  }
  elsif (defined ($args_ref->{'l'}))
  {                                     # Date values exists for files only. #
    ($mon, $day, $time) = split (/\s+/, $entry_ref->{'date_print'});
  }



  if (($entry_ref->{'alias_directory'} eq 'Y') ||
      (($entry_ref->{'system_created'} eq 'N') &&
       (!defined ($args_ref->{'L'}))))
  {
    $entry_ref->{'type'}       = $ASMCMDBASE_SPACE;
    $entry_ref->{'redundancy'} = $ASMCMDBASE_SPACE;
    $entry_ref->{'striped'}    = $ASMCMDBASE_SPACE;
    $mon                       = $ASMCMDBASE_SPACE;
    $day                       = $ASMCMDBASE_SPACE;
    $time                      = $ASMCMDBASE_SPACE;
    $entry_ref->{'space'}      = $ASMCMDBASE_SPACE;

    $entry_ref->{'block_size'} = $ASMCMDBASE_SPACE;
    $entry_ref->{'blocks'}     = $ASMCMDBASE_SPACE;
    $entry_ref->{'bytes'}      = $ASMCMDBASE_SPACE;

    $entry_ref->{'user'}       = $ASMCMDBASE_SPACE;
    $entry_ref->{'group'}      = $ASMCMDBASE_SPACE;
    $entry_ref->{'perm'}       = $ASMCMDBASE_SPACE;
  }

  @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, $mon);
    push (@print_args, $day);
    push (@print_args, $time);
    push (@print_args, $ASMCMDBASE_SPACE);
    push (@print_args, $entry_ref->{'system_created'});
  }

  if (defined($args_ref->{'s'}))                                    # ls -s. #
  {
    push (@print_args, $entry_ref->{'block_size'});
    push (@print_args, $entry_ref->{'blocks'});
    push (@print_args, $entry_ref->{'bytes'});
    push (@print_args, $entry_ref->{'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'});

  printf ($row, @print_args);

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

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

  printf $hdr, @print_args;

  # 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->{'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 ($printf_code);       # dynamically generated printf() code for eval(). #

  # 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{'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 printf() code based on user-specified flags.
  $printf_code = 'printf $row, (';

  if (defined ($args_ref->{'g'}))
  {
    $printf_code .= '$ASMCMDBASE_LSDG_HDR_INSTID, ';
  }

  if (defined ($args_ref->{'l'}))
  {
    $printf_code .= '$ASMCMDBASE_LSDG_HDR_STATE, 
                     $ASMCMDBASE_LSDG_HDR_TYPE,
                     $ASMCMDBASE_LSDG_HDR_REBAL, ';
    }

  if (defined ($args_ref->{'s'}))
  {
    $printf_code .= '$ASMCMDBASE_LSDG_HDR_SECTOR,
                     $ASMCMDBASE_LSDG_HDR_BLOCK,
                     $ASMCMDBASE_LSDG_HDR_AU,
                     $ASMCMDBASE_LSDG_HDR_TMB,
                     $ASMCMDBASE_LSDG_HDR_FMB, ';

    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_10gR2) >= 0 )
    {                                           # Version is 10gR2 or later. #
      $printf_code .= '$ASMCMDBASE_LSDG_HDR_RMFMB,
                       $ASMCMDBASE_LSDG_HDR_UFMB,
                       $ASMCMDBASE_LSDG_HDR_ODISK, ';
    }

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

  # Always include the name.
  $printf_code .= '$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))
  {
    eval($printf_code);
    asmcmdshare_trace(1, $@, 'y', 'y') if $@;
  }

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

    # Construct the printf() statement to eval() later.
    $printf_code = 'printf $row, (';

    if (defined ($args_ref->{'g'}))
    {
      $printf_code .= q|$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'});

      $printf_code .= q|$dgroups[$i]->{'state'}, 
                        $dgroups[$i]->{'type'}, 
                        $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;
      }

      $printf_code .= q|$dgroups[$i]->{'sector_size'}, 
                        $dgroups[$i]->{'block_size'}, 
                        $dgroups[$i]->{'allocation_unit_size'}, 
                        $dgroups[$i]->{'total_mb'}, 
                        $dgroups[$i]->{'free_mb'}, |;

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

      if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                   $ASMCMDGLOBAL_VER_11gR2) >= 0 )
      {
        $printf_code .= q|$dgroups[$i]->{'voting_files'}, |;
      }
    }

    # Always include the disk group name.
    $printf_code .= q|$dgroups[$i]->{'name'}) |;

    # Now evaluate the printf() expression.
    eval($printf_code);
    asmcmdshare_trace(1, $@, 'y', 'y') if $@;
  }

  return;
}

########
# NAME
#   asmcmdbase_find_int
#
# 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_int() returns at most one
#   match.  Use this feature when you have only an alias name by want the 
#   full path to it.
#
#   asmcmdbase_process_du() is the only routine that calls asmcmdbase_find_int()
#   with sys_non_dir_only set to true.
########
sub asmcmdbase_find_int 
{
  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_int() 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. #

  print "You may delete multiple files and/or directories. \n" .
    'Are you sure? (y/n) ';
  $response = <STDIN>;
  chomp ($response);

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




########
# 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
#   Zero on success; undefined 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, @norm_tgt_fname, @src_finfo);
  my ($src_fil, $tgt_fil);
  my ($src_sth, $tgt_sth);
  my ($ret);
  my (%norm);
  my (@eargs);
  my (@paths, @ref_ids, @par_ids, @dg_nums, @entry_list, $name);
  my ($i);
  my ($tgt_name);
  my (%invalid_files);
  my ($servicename) = $asmcmdglobal_hash {'service'};    #opt param for cp
          ## $asmcmdglobal_hash{'service'} is always set in 
          ## asmcmdbase_process_cp.  If no value speficified, the +ASM
          ## is assumed.

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

    #my $file_to_copy_name;
    my $tgt_fname = $tgt_path;
    my @tmp;

    my ( $src_hdl, $tgt_hdl, $plkSz, $fileSz, $openMode );
    my ( $fileType, $blkSz, $fileName, $fileGenName );
    my ( $dbname, $dbid, $tsname, $fno, $plid, $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

    # 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_path = $norm{'path'}->[0];
    }
    else
    {
      # OS file case
      if ($src_remote_inst != 1 )
      {
        # on remote host we cannot check for file existence
        if ( !(-r $src_path) )
        {
          @eargs = ($src_path);
          asmcmdshare_error_msg(8014, \@eargs);
          next;
        }

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

    # add the normalized file name to the array
    push(@norm_src_fname, $src_path);


    #my ($fileType, $fileSz, $blkSz);
    $src_name = $src_path;
    $src_name =~ s,.*/(.*)$,$1,;

    # 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 )
      {
        # complete the path
        $tgt_path = $norm{'path'}->[0];

        @paths   = @{ $norm{'path'} };
        @ref_ids = @{ $norm{'ref_id'} };
        @par_ids = @{ $norm{'par_id'} };
        @dg_nums = @{ $norm{'gnum'} };
        $name = $paths[0];
        $name =~ s,.*/(.*)$,$1,;

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

        # if the directory is a diskgroup or a directory
        if ( ($ref_ids[0] == $dg_nums[0] << 24 ) ||
             $entry_list[0]->{'alias_directory'} eq 'Y' )
        {
          $tgt_name = $tgt_path . '/' . $src_name;

        }
	elsif ( $entry_list[0]->{'alias_directory'} eq 'N' )
        {
            $tgt_name = $tgt_path;
        }
        else
        {
          @eargs = ($tgt_name);
          asmcmdshare_error_msg(8014, \@eargs);
          next;
        }
      }
      else
      {
        $tgt_name = $tgt_path;
      }
    }
    else
    {
      # target is OS
      # if path exists it must be a directory
      if ( -d $tgt_path )
      {
        $tgt_name = $tgt_path . '/' . $src_name;
      }
      else
      {
        $tgt_name = $tgt_path;
      }
    }
    push(@norm_tgt_fname, $tgt_name);
  }

  #get file information
  #very important to do dbms_diskgroup stuff in the end, since the connection
  #becomes useless after calling it


  foreach (@norm_src_fname)
  {
    my ($src_path) = $_;
    my ($fileType, $fileSz, $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 $fileType || $fileType < 1 )
      {
        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;
      }

      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, $tsname, $dbid, $fno, $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 $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++)
    {
      $src_sth = $src_dbh->prepare(q{
        begin
        dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz, 
                            :src_fsiz, '','','', :dst_path, 1);
        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]);

      print STDOUT "copying $norm_src_fname[$i] -> $norm_tgt_fname[$i]\n";
      $ret = $src_sth->execute();

      if ( !defined($ret) )
      {
        my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]);
        asmcmdshare_error_msg(8016, \@eargs);
        asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
        next;
      }
    }
     asmcmdshare_trace(3, "SUCCESS: Completed copying from local to local.",
                                       'y', 'n');
  }
  elsif ($src_remote_inst)  # remote to local
  {
    my ($host, $port, $sid, $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);
        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]);

      print STDOUT "copying $rhost:$norm_src_fname[$i] -> $norm_tgt_fname[$i]\n";
      $ret = $src_sth->execute();

      if ( !defined($ret) )
      {	
        my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]);
        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');
        next;
      }
    }
    asmcmdshare_trace(3, "SUCCESS: Completed copying from remote to local.",
                                       'y', 'n');
  }
  elsif ($tgt_remote_inst)   # local to remote
  {
    my ($host, $port, $sid, $dst_str);
    $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);
        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]);

      print STDOUT "copying $norm_src_fname[$i] -> $rhost:$norm_tgt_fname[$i]\n";
      $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');
        next;
      }
    }
    asmcmdshare_trace(3, "SUCCESS: Completed copying from local to remote.",
                         'y', 'n');
  }

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



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

  return 0;
}


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

  my $echo   = ECHO | ECHOK | ICANON;
  my $noecho = $echo & ~$echo;

  $fd_stdin = fileno(STDIN);
  $term     = POSIX::Termios->new();

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

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

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

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

  return $inputchr;
}


########
# NAME
#   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 }) &&
         defined ($asmcmdbase_cmds{ $arg }{ wildcard });
}

########
# 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
#   True if $arg is a command that can run without an ASM instance 
#   or does not exist, false otherwise.
#
# 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;

  return !defined ($asmcmdbase_cmds{ $arg }) ||
         !defined ($asmcmdbase_cmds{ $arg }{ no_instance });
}

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

  #build the list of options to parse using GetOptions
  if($asmcmdbase_cmds{ $cmd }{ flags })
  { 
    foreach $key(keys %{$asmcmdbase_cmds{ $cmd }{ flags }})
    { 
      push(@string, $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 GetOptions() from the Getopt::Long package to parse arguments for 
  # internal commands.  These arguments are stored in @ARGV.
  if (!GetOptions($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
#   Exits if cmd is "asmcmd" or "asmcmd_no_conn_str"; 1 if another command
#   that 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;

  # Get the syntax for non -c version of asmcmd if -c is deprecated.
  $cmd = 'asmcmd_no_conn_str' if (! $ASMCMDGLOBAL_USE_CONN_STR && 
                                   ($cmd eq 'asmcmd'));
  $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_no_conn_str' &&
                               $cmd ne 'asmcmd');

    print STDERR 'usage: ' .  $cmd_syntax . "\n";

    print STDERR 'help:  help ' . $cmd_print_name . "\n";

    # Can't proceed if asmcmd has bad syntax. #
    exit -1 if (($cmd eq 'asmcmd') ||
                ($cmd eq 'asmcmd_no_conn_str'));
    $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);

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

  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, $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);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
  if ($asmcmdglobal_hash{'mode'} eq 'n')
  {
    $asmcmdglobal_hash{'e'} = -1;
  }

  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);
  if (!defined ($ret))
  {
    asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
    if ($asmcmdglobal_hash{'mode'} eq 'n')
    {
      $asmcmdglobal_hash{'e'} = -1;
    }
  }

  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);
  if (!defined ($ret))
  {
    asmcmdshare_trace(1, $DBI::errstr, 'y', 'y');
    if ($asmcmdglobal_hash{'mode'} eq 'n')
    {
      $asmcmdglobal_hash{'e'} = -1;
    }
  }
}

########
# 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_int() 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=\'' .
          $sys_cre . '\'';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  $sth->bind_param(2,$fnum);
  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_int ($dbh, '+', $name, undef, $par_id, 0, 0);
  $path = $results[0]->{'full_path'};

  return $path;
}

########
# NAME
#   asmcmdbase_get_ct
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch a list of row(s) from  
#   v$asm_client or gv$asm_client.
#
# PARAMETERS
#   dbh    (IN) - database handle.
#   gname  (IN) - optional: limit select by this group number.
#
# 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.  
########
sub asmcmdbase_get_ct 
{
  my ($dbh, $gname, $global) = @_;

  my ($sth, $qry, $row);
  my (@ct_list);            # The return array of hashes; see RETURNS above. #
  my ($gnum);      # The group number for the diskgroup specified by $gname. #
  
  if ($global)
  {
    $qry = 'select * from gv$asm_client';
  }
  else
  {
    $qry = 'select * from v$asm_client';
  }

  # Narrow select if $gname is specified. 
  if (defined ($gname)) 
  {
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);  # Get group num. #
    return @ct_list unless (defined ($gnum));            # Group must exist. #
    $qry = $qry . ' where group_number=' . $gnum;
  }

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

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

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

    # New columns for 10gR2, so check for forward compatiblity.
    if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                                 $ASMCMDGLOBAL_VER_10gR2) >= 0 )
    {
      $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.  
#
########
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. #

  $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:';

  #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'}) );
    $port = 1521 if( !defined ($port));

    $rsid = $sid;
    $rport = $port if (defined ($port));

    # Now we have the values for $host, $port, and $sid, finish contructing
    # the $driver string.
    if (defined ($host)) 
    {
      $driver .= 'host=' . $host;
      $rhost = $host;
    }

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

  # Now try to connect!  ora_session_mode => 2 means to connect as sysdba
  $dbh = DBI->connect($driver, $usr, $pswd, \%session_mode);
  if(!defined($dbh))
  { 
     $$constr = " Driver="."$driver";
     $$constr .=" User=".$usr if defined($usr);
     $$constr .= " Password=". $pswd if defined($pswd);
     $$constr .= " Session_mode=".$contype if defined($contype);
     $$constr .= " ServiceName=" .$asmcmdglobal_hash{'service'} 
                   if defined($asmcmdglobal_hash{'service'});
     $$constr .= " port=$port" if defined($port);
  }
  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_print_cmds(sort(keys %asmcmdbase_cmds));
}

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