# Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      asmcmddisk - ASM CoMmanD line interface DISK operations
#
#    DESCRIPTION
#      This module contains the code for ASMCMD/ASM disk-related
#      operations, such as bad block remap and listing the
#      contents of v$asm_disk.
#
#    NOTES
#      usage: asmcmdcore [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    manuegar   08/19/13 - Backport manuegar_bug-13951456 from main
#    adileepk   05/04/12 - Backport adileepk_bug_unvar from main
#    apfwkr     01/04/12 - Backport moreddy_bug-12818129 from main
#    adileepk   04/12/11 - Backport adileepk_lsdsk_failgroup_bug from main
#    moreddy    01/13/11 - Backport moreddy_bug-8667038 from main
#    amitroy    04/21/10 - BUG 8933243 - USE DIFFERENT CHARACTER IN ASMCMD TO
#                          "HIDE COLUMN DETAILS" INSTEAD OF -H
#    pvenkatr   03/31/10 - Syntax, description, example - all from XML
#    moreddy    03/22/10 - Adding more tracing
#    mchimang   02/22/10 - Fixed the bug 9364411
#    moreddy    02/04/10 - bug9262634 fixed printing of error messages
#    moreddy    01/19/10 - Adding tracing messages
#    moreddy    12/14/09 - bug-9135531 broken xml files should not succeed
#    mproddut   10/09/09 - #8753738 mount does not mount more than one diskgroup
#    amitroy    10/09/09 - #BUG 8829999:iostat returning uninitialized value
#                          into console with offline disk
#    pvenkatr   09/03/09 - Help message from xml file
#    danagara   08/26/09 - bug-8554208 Added success message for few asm cmds
#    amitroy    09/22/09 - modifying 'offline' time arguement; bug #8711671.
#    sanselva   07/30/09 - #8707883 lsod disk pattern was not parsed
#    sanselva   06/24/09 - fix help msgs for iostat
#    soye       06/10/09 - move mapau and mapextent to diagnostic package
#    pbagal     05/13/09 - Add OS pid to lsod, and remove type
#    sanselva   04/24/09 - remove invalid -a option from 'help offline'
#    heyuen     04/17/09 - fix lsdsk -M
#    sanselva   04/06/09 - ASMCMD long options and consistency
#    heyuen     03/23/09 - rename dgdrop to dropdg/ add iostat
#    gmengel    02/13/09 - add option to list missing disks. 
#    heyuen     01/08/09 - change secsize to get from x$kfkid, x$kfdsk
#    heyuen     01/05/09 - lsod
#    heyuen     12/05/08 - add checks for sector_size
#    heyuen     10/14/08 - use dynamic modules
#    heyuen     10/02/08 - enable variable block size in disks
#    heyuen     08/26/08 - show voting file, fix mkdg/chdg to accept inline
#                          args
#    heyuen     07/28/08 - use command properties array
#    heyuen     07/08/08 - mkdg better error handling, msgs
#    heyuen     06/17/08 - fix chdg help
#    heyuen     06/16/08 - update help
#    heyuen     05/22/08 - fix parser_state
#    heyuen     04/30/08 - fix mount restricted
#    soye       04/24/08 - add missing diskgroup in rebal's help
#    heyuen     04/15/08 - add membership to lsdsk, reorder help messages
#    heyuen     03/14/08 - fix chdg parser
#    heyuen     03/31/08 - mkdg does not error propperly
#    heyuen     03/20/08 - fix disk name during diskgroup creation
#    heyuen     03/14/08 - fix create diskgroup syntax
#    soye       12/17/07 - add ASM file mapping support
#    heyuen     02/12/08 - change help messages
#    ykatada    01/28/08 - #6769870 correct the wrong word 'BYTES_WRITEEN'
#    heyuen     12/04/07 - use kfod instead of regular ls for listing disks
#    heyuen     08/08/07 - do not scan for disk groups in lsdsk offline mode
#    hqian      06/24/07 - #6137843: reimplement lsdsk using kfed
#    hqian      05/31/07 - Fix uninitialized value in lsdsk
#    heyuen     05/25/07 - add return codes for errors
#    hqian      05/24/07 - remap: error out for external redundancy disk group
#    heyuen     05/03/07 - fix asmcmd lsdsk return codes
#    hqian      03/06/07 - Rename command "repair" to "remap"
#    hqian      02/23/07 - #5893911: use new dbms_diskgroup.blockrepair
#                          interface for repair; 
#    hqian      01/02/07 - Align IO buffer for disk header read
#    hqian      11/30/06 - asmcmd lsdsk -I supports only UNIX and no character
#                          devices
#    heyuen     10/18/06 - Add return codes for lsdsk
#    hqian      07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks 
#    hqian      06/19/06 - 11gR1 proj-18435: implement asmcmd repair 
#    hqian      06/14/06 - 11gR1 proj-18435: implement asmcmd lsdsk
#    hqian      06/14/06 - Creation
#
#############################################################################
#
############################ Functions List #################################
# asmcmddisk_init
# asmcmddisk_process_cmd
# asmcmddisk_process_lsdsk
# asmcmddisk_scan_dsk
# asmcmddisk_convert_date
# asmcmddisk_validate_header
# asmcmddisk_verify_endian
# asmcmddisk_verify_hard
# asmcmddisk_verify_btype
# asmcmddisk_verify_dformat
# asmcmddisk_verify_checksum
# asmcmddisk_init_disk_attr
# asmcmddisk_path_forward
# asmcmddisk_lsdsk_init_col_wid
# asmcmddisk_process_remap
# asmcmddisk_get_dnum_from_dname
# asmcmddisk_get_dsk
# asmcmddisk_process_help
# asmcmddisk_is_cmd
# asmcmddisk_is_wildcard_cmd
# asmcmddisk_is_no_instance_cmd
# asmcmddisk_parse_int_args
# asmcmddisk_syntax_error
# asmcmddisk_get_cmd_syntax
# asmcmddisk_get_asmcmd_cmds
# asmcmddisk_process_mkdg
# asmcmddisk_process_dropdg
# asmcmddisk_process_chkdg
# asmcmddisk_process_chdg
# asmcmddisk_process_mount
# asmcmddisk_process_umount
# asmcmddisk_process_online
# asmcmddisk_process_offline
# asmcmddisk_process_rebal
#
#############################################################################

package asmcmddisk;
require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(asmcmddisk_init
                 );

use strict;
use Getopt::Long qw(:config no_ignore_case bundling);
use Data::Dumper;
use asmcmdglobal;
use asmcmdshare;
use XML::Parser;
use Data::Dumper;
use List::Util qw[min max];

####################### ASMCMDDISK Global Constants ######################
# ASMCMD Column Header Names:
# Below are the names of the column headers for lsdsk.  These headers
# are the ASMCMD equivalent of the columns of v$asm_disk.
our ($ASMCMDDISK_LSDSK_INST_ID)       = 'Inst_ID';
our ($ASMCMDDISK_LSDSK_GNUM)          = 'Group_Num';
our ($ASMCMDDISK_LSDSK_DNUM)          = 'Disk_Num';
our ($ASMCMDDISK_LSDSK_INCARN)        = 'Incarn';
our ($ASMCMDDISK_LSDSK_DISKGROUP)     = 'Disk_group';
our ($ASMCMDDISK_LSDSK_MOUNTSTAT)     = 'Mount_Stat';
our ($ASMCMDDISK_LSDSK_HEADERSTAT)    = 'Header_Stat';
our ($ASMCMDDISK_LSDSK_MODESTAT)      = 'Mode_Stat';
our ($ASMCMDDISK_LSDSK_STATE)         = 'State';
our ($ASMCMDDISK_LSDSK_REDUND)        = 'Redund';
our ($ASMCMDDISK_LSDSK_LIBRARY)       = 'Library';
our ($ASMCMDDISK_LSDSK_OS_MB)         = 'OS_MB';
our ($ASMCMDDISK_LSDSK_TOTAL_MB)      = 'Total_MB';
our ($ASMCMDDISK_LSDSK_FREE_MB)       = 'Free_MB';
our ($ASMCMDDISK_LSDSK_NAME)          = 'Name';
our ($ASMCMDDISK_LSDSK_FGROUP)        = 'Failgroup';
our ($ASMCMDDISK_LSDSK_FGROUP_TYPE)   = 'Failgroup_Type';
our ($ASMCMDDISK_LSDSK_LABEL)         = 'Label';
our ($ASMCMDDISK_LSDSK_PATH)          = 'Path';
our ($ASMCMDDISK_LSDSK_UDID)          = 'UDID';
our ($ASMCMDDISK_LSDSK_PRODUCT)       = 'Product';
our ($ASMCMDDISK_LSDSK_CREATE_DATE)   = 'Create_Date';
our ($ASMCMDDISK_LSDSK_MOUNT_DATE)    = 'Mount_Date';
our ($ASMCMDDISK_LSDSK_REPAIR_TIMER)  = 'Repair_Timer';
our ($ASMCMDDISK_LSDSK_READS)         = 'Reads';
our ($ASMCMDDISK_LSDSK_WRITES)        = 'Write';
our ($ASMCMDDISK_LSDSK_READ_ERRS)     = 'Read_Errs';
our ($ASMCMDDISK_LSDSK_WRITE_ERRS)    = 'Write_Errs';
our ($ASMCMDDISK_LSDSK_READ_TIME)     = 'Read_time';
our ($ASMCMDDISK_LSDSK_WRITE_TIME)    = 'Write_Time';
our ($ASMCMDDISK_LSDSK_BYTES_READ)    = 'Bytes_Read';
our ($ASMCMDDISK_LSDSK_BYTES_WRITTEN) = 'Bytes_Written';
our ($ASMCMDDISK_LSDSK_VOTING_FILE)   = 'Voting_File';


my ($ASMCMDDISK_METADATA_BLOCKSIZE) = 4096;
my ($ASMCMDDISK_1MB) = 1048576;                                   # 1MB. #

# Other constants
# Discovery string for Linux.
my ($ASMCMDDISK_LINUX_DISCOVER) = '/dev/raw/raw*';
# Header constants
my ($ASMCMDDISK_HARD_4K) = 130;
my ($ASMCMDDISK_BTYPE_DISKHEAD) = 1;

our (@asmcmddisk_parser_state) = ('dummy');
our ($dgstmt)                  =  'dummy';
our (@asmcmddisk_parser_disks) = ('dummy');
our (@asmcmddisk_parser_attrs) = ('dummy');
our (%asmcmddisk_parser_fg)    = ('dummy','dummy');
our ($xml_error)               =  'dummy';
our ($asmcmddisk_parser_text) = ('dummy') ;     # to parse for invalid text

####################### ASMCMDDISK Global Variables ######################
my (%asmcmddisk_cmds) = (lsdsk     => {wildcard    => 'True',
                                       flags       =>  {'k'=>'diskGrpInfo',
                                                        'statistics'=>
                                                       'statistics', 
                                                        'p'=>'diskGrpDetails',
                                                        't'=>'timeStamp',
                                                        'discovery'=>'noCache',
                                                        'g'=>'global',
                                                        'suppressheader'=>
                                                       'suppressHeaders',
                                                        'I'=>'scanDiskHeaders',
                                                        'G=s'=>'diskGroup',
                                                        'member'=>
                                                       'statusMember',
                                                        'candidate'=>
                                                       'statusCandidate',
                                                        'M'=>'missingDisks'}
                                                       },
                         lsod      => {wildcard    =>  'True',
                                       flags       =>   {'suppressheader'=>
                                                        'suppressHeaders',
                                                         'G=s'=>'diskGroup',
                                                         'process=s'=>
                                                       'filterProcesses'},
                                       no_instance => 'True'},
                         remap     => {no_instance => 'True'},
                         dropdg    => {flags       =>  {'r'=>'recursive',
                                                        'f'=>'force'}, 
                                       no_instance => 'True'},
                         mkdg      => {no_instance => 'True'},
                         chkdg     => {flags       =>  {'repair'=>
                                                       'repairDiskgroup'},
                                       no_instance => 'True'
                                                      },
                         chdg      => {no_instance => 'True'},
                         mount     => {flags       =>  {'a'=>'all',
                                                        'restrict'=>
                                                       'restrictedMode',
                                                        'f'=>'force'},
                                       no_instance => 'True'},
                         umount    => {flags       =>  {'a'=>'all',
                                                        'f'=>'force'},
                                       no_instance => 'True'
                                                      },
                         online    => {flags       =>  {'a'=>'all',
                                                        'F=s'=>'failureGroup',
                                                        'G=s'=>'diskGroup',
                                                        'w'=>'wait',
                                                        'D=s'=>'disk'},
                                       no_instance => 'True'
                                                      },
                         offline   => {flags       =>  {'G=s'=>'diskGroup',
                                                        'F=s'=>'failureGroup',
                                                        'D=s'=>'disk',
                                                        't=s'=>'timeStamp'},
                                       no_instance => 'True'
                                                      },
                         rebal     => {flags       =>  {'power=s'=>'powerLevel',
                                                        'w'=>'wait'},
                                       no_instance => 'True'
                                                      },
			 iostat    => {no_instance => 'True',
				       flags       =>  {'G=s'=>'diskGroup',
                                                        'suppressheader'=>
                                                       'suppressHeaders',
                                                        'io'=>'inputOutput',
                                                        'region'=>'regionInfo',
                                                        'e'=>'errorStatistics',
                                                        't'=>'timeStamp'}
                                                      }
                        );

my (%asmcmddisk_lsod_header) = ('program' , 'Process',
                         'status'  , 'Status',
                         'inst_id' , 'Instance',
                         'type'    , 'Type',
                         'ospid'   , 'OSPID',
                         'path'    , 'Path');

# Below are the names of the column headers for iostat.
our (%asmcmddisk_iostat_header) = ('group_number'      ,'Group_Num',
				   'group_name'        ,'Group_Name',
				   'disk_number'       ,'Dsk_Num',
				   'name'              ,'Dsk_Name',
				   'reads'             ,'Reads',
				   'writes'            ,'Writes',
				   'read_errs'         ,'Read_Err',
				   'write_errs'        ,'Write_Err',
				   'read_time'         ,'Read_Time',
				   'write_time'        ,'Write_Time',
				   'bytes_read'        ,'Reads',
				   'bytes_written'     ,'Writes',
				   'cold_reads'        ,'Cold_Reads',
				   'cold_writes'       ,'Cold_Writes',
				   'cold_bytes_read'   ,'Cold_Reads',
				   'cold_bytes_written','Cold_Writes',
				   'hot_reads'         ,'Hot_Reads',
				   'hot_writes'        ,'Hot_Writes',
				   'hot_bytes_read'    ,'Hot_Reads',
				   'hot_bytes_written' ,'Hot_Writes',
				   );


sub is_asmcmd
{
  return 1;
}

########
# NAME
#   asmcmddisk_init
#
# DESCRIPTION
#   This function initializes the asmcmddisk 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, \&asmcmddisk_process_cmd);
  push (@asmcmdglobal_help_callbacks, \&asmcmddisk_process_help);
  push (@asmcmdglobal_command_list_callbacks, \&asmcmddisk_get_asmcmd_cmds);
  push (@asmcmdglobal_is_command_callbacks, \&asmcmddisk_is_cmd);
  push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmddisk_is_wildcard_cmd);
  push (@asmcmdglobal_syntax_error_callbacks, \&asmcmddisk_syntax_error);
  push (@asmcmdglobal_no_instance_callbacks, \&asmcmddisk_is_no_instance_cmd);
  %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmddisk_cmds);
 
  #Perform ASMCMD consistency check if enabled
  if($asmcmdglobal_hash{'consistchk'} eq 'y')
  {
     if(!asmcmdshare_check_option_consistency(%asmcmddisk_cmds))
     {
       exit 1;
     }
  }
}

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

  # Get current command from global value, which is set by 
  # asmcmddisk_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 ASMCMDDISK command.
  my (%cmdhash) = ( lsdsk         => \&asmcmddisk_process_lsdsk,
                    lsod          => \&asmcmddisk_process_lsod,
                    remap         => \&asmcmddisk_process_remap,
                    dropdg        => \&asmcmddisk_process_dropdg,
                    mkdg          => \&asmcmddisk_process_mkdg,
                    chkdg         => \&asmcmddisk_process_chkdg,
                    chdg          => \&asmcmddisk_process_chdg,
                    mount         => \&asmcmddisk_process_mount,
                    umount        => \&asmcmddisk_process_umount,
                    online        => \&asmcmddisk_process_online,
                    offline       => \&asmcmddisk_process_offline,
                    rebal         => \&asmcmddisk_process_rebal,
		    iostat        => \&asmcmddisk_process_iostat);

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

  return $succ;
}


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


  my (%args);                             # Argument hash used by getopts(). #
  my ($ret);
  my ($qry);
  my ($gnum, $row);
  my (@what , @from, $sth, @where, @order);
  my (%min_col_wid, $print_format, $printf_code, @what_print);
  my (%what_delta);
  my ($k, $v, @io_list, @io_list_prev, @delta, $h, $p);
  my ($dt);
  my (@eargs);

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

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

  if (defined($args{'G'}))
  {
    my ($gname) = $args{'G'};   
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);
    if (!defined($gnum))
    {  
      @eargs = ($gname);
      asmcmdshare_error_msg(8001, \@eargs);
      return;
    }

    $gname =~ tr/a-z/A-Z/;
    push (@where, 'v$asm_diskgroup_stat.name = ' . "'$gname'");
  }

  push (@what, 'v$asm_diskgroup_stat.name as group_name');
  push (@what, 'v$asm_disk_stat.name');

  if (defined($args{'io'}))   #display IOs instead of bytes
  {
    push (@what, 'v$asm_disk_stat.reads');
    push (@what, 'v$asm_disk_stat.writes');
    $what_delta{'reads'} = 1;
    $what_delta{'writes'} = 1;
  }
  else
  {
    push (@what, 'v$asm_disk_stat.bytes_read');
    push (@what, 'v$asm_disk_stat.bytes_written');
    $what_delta{'bytes_read'} = 1;
    $what_delta{'bytes_written'} = 1;
  }

  if (defined($args{'region'}))
  {
    if (defined($args{'io'}))
    {
      push (@what, 'v$asm_disk_stat.cold_reads');
      push (@what, 'v$asm_disk_stat.cold_writes');
      push (@what, 'v$asm_disk_stat.hot_reads');
      push (@what, 'v$asm_disk_stat.hot_writes');
      $what_delta{'cold_reads'} = 1;
      $what_delta{'cold_writes'} = 1;
      $what_delta{'hot_reads'} = 1;
      $what_delta{'hot_writes'} = 1;
    }
    else
    {
      push (@what, 'v$asm_disk_stat.cold_bytes_read');
      push (@what, 'v$asm_disk_stat.cold_bytes_written');
      push (@what, 'v$asm_disk_stat.hot_bytes_read');
      push (@what, 'v$asm_disk_stat.hot_bytes_written');
      $what_delta{'cold_bytes_reads'} = 1;
      $what_delta{'cold_bytes_writes'} = 1;
      $what_delta{'hot_bytes_reads'} = 1;
      $what_delta{'hot_bytes_writes'} = 1;
    }
  }

  if (defined($args{'e'}))
  {
    push (@what, 'v$asm_disk_stat.read_errs');
    push (@what, 'v$asm_disk_stat.write_errs');
    $what_delta{'read_errs'} = 1;
    $what_delta{'write_errs'} = 1;
  }

  if (defined($args{'t'}))
  {
    push (@what, 'v$asm_disk_stat.read_time');
    push (@what, 'v$asm_disk_stat.write_time');
    $what_delta{'read_time'} = 1;
    $what_delta{'write_time'} = 1;
  }

  if (defined($ARGV[0]))
  {
    $dt = $ARGV[0];
    if ($dt !~ m/^\d+$/ or $dt eq 0)
    {
      @eargs = ($dt);
      asmcmdshare_error_msg(9399, \@eargs);
      return;
    }
  }

  push (@from, 'v$asm_disk_stat');
  push (@from, 'v$asm_diskgroup_stat');

  push (@where, 'v$asm_disk_stat.group_number > 0');
  push (@where, 'v$asm_disk_stat.group_number = v$asm_diskgroup_stat.group_number');

  push (@order, 'v$asm_disk_stat.group_number, v$asm_disk_stat.name');

  @io_list_prev = ();

  $asmcmdglobal_hash{'running'} = 1;
 do
  {
    $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where, 
                                           \@order);

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

    @io_list = ();

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

      while (($k, $v) = each (%{$row}))
      {
        $k =~ tr/[A-Z]/[a-z]/;
        $io_info{$k} = $v;
      }
      push (@io_list, \%io_info);
    }
    asmcmdshare_finish($sth);

    #if there is a previous scan, get the delta
    if (@io_list_prev)
    {
      @delta = ();
      # entries are uniquely identified by (dgroup, dsk)
      foreach $h(@io_list)
      {
        my ($gnam, $dnam);
        $gnam = $h->{'group_name'};
        $dnam = $h->{'name'};

        foreach $p(@io_list_prev)
        {
          if ($p->{'group_name'} eq $gnam && $p->{'name'}  eq $dnam)
          {
            my (%io_delta_info) = ();
            while (($k, $v) = each (%{$p}))
            {
              $k =~ tr/[A-Z]/[a-z]/;
              if (defined($what_delta{$k}))
              {
                # Fix for Bug-9873674.
                # Set the value of the variable to 0. The variable becomes uninitialized
                # if a disk is offlined while iostat is in running mode.
                if (defined($h->{$k}) && defined $v)
                {
                  $io_delta_info{$k} = ($h->{$k} - $v) /$dt;
                }
                else
                {
                  $io_delta_info{$k} = 0;
                }
                $io_delta_info{$k} =~ m/(.*)/;
                $io_delta_info{$k} = sprintf("%.2f", $1);
              }
              else
              {
                $io_delta_info{$k} = $v;
              }
            }
            push (@delta, \%io_delta_info);
            last;
          }
        }
      }
    }
    else
    {
      @delta = @io_list;
    }

    # initialize the min_col_wid array
    foreach (@what)
    {
      my (@a) = split(/[\.\ ]/, $_);
      my ($colnam) = $a[$#a];
      $min_col_wid{$colnam} = length($asmcmddisk_iostat_header{$colnam});
    }
    # initialize width with the deltas
    foreach $h(@delta)
    {
      while(($k, $v) = each(%{$h}))
      {        
       if(( defined($k) && defined($v) ))
       {
          $min_col_wid{$k} = max($min_col_wid{$k}, length($v));
       }
      }
    }

    $print_format = '';

	    foreach (@what)
	    {
	      my (@a) = split(/[\.\ ]/, $_);
	      my ($colnam) = $a[$#a];
      $print_format .= "%-$min_col_wid{$colnam}s  ";
    }
    $print_format .= "\\n";

    #print header
    if (!defined ($args{'suppressheader'}) )
    {
      $printf_code = "printf \"$print_format\", ";
      @what_print = ();
      foreach (@what)
      {
        my (@a) = split(/[\.\ ]/, $_);
        my ($colnam) = $a[$#a];
        push (@what_print, "\'" . $asmcmddisk_iostat_header{$colnam} . "\'");
      }
      $printf_code .= "(" . join (", ", @what_print) . ")";

      eval $printf_code;
    }
    #print rows
    foreach $h (@delta)
    {
      $printf_code = "printf \"$print_format\", ";
      @what_print = ();
     
      foreach (@what)
      { 
        my (@a) = split(/[\.\ ]/, $_);
        my ($colnam) = $a[$#a];
        if(defined ($h->{$colnam})) 
        {
               push (@what_print, "\'" . $h->{$colnam} . "\'");
        }
        else
        {
                push (@what_print, "\'\'");
        }
      }
      $printf_code .= "(" . join (", ", @what_print) . ")";
      eval $printf_code;

    }

    # if a loop is specified, sleep the specified time
    if (defined($dt))
    {
      print "\n";
      @io_list_prev = @io_list;
      eval{
        sleep $dt;
      }
    }
  }while (defined($dt) and $asmcmdglobal_hash{'running'} == 1);

  return;
}


########
# NAME
#   asmcmddisk_process_lsod
#
# 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
#
# NOTES
#   Only asmcmdcore_shell() calls this routine.
########
sub asmcmddisk_process_lsod
{
  my ($dbh) = shift;
  my (%args);

  my ($disk_pattern, $process_pattern);
  my ($headers);
  my (@what , @from, $sth, $qry, @where, @order, @tmp_cols);
  my ($ret);
  my (@dsk_list);
  my ($row, $k, $v, $h);
  my (%min_col_wid, $print_format, $printf_code, @what_print);
  my ($gname, $gnum);

  # Get option parameters, if any.
  $ret = asmcmddisk_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);

  # Process the disk pattern.
  if (@ARGV > 0)
  {
    $disk_pattern = shift(@ARGV);
    $disk_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g
  }

  if (defined($args{'process'}))
  {
    $process_pattern = $args{'process'};
    $process_pattern =~ tr/[a-z]/[A-Z]/;
    $process_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g
  }
  
  if (defined($args{'G'}))
  {
    my $gname = $args{'G'};
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);
    if (!defined ($gnum))
    {
        my (@eargs) = ($gname);
        asmcmdshare_error_msg(8001, \@eargs);
	return;
    }
  }
      
  # print headers?
  $headers = defined($args{'suppressheader'});

  push(@what, 'x$kfklsod.inst_id as inst_id');
  push(@what, 'v$process.program as program');
  push(@what, 'v$process.spid as ospid');
  push(@what, 'x$kfklsod.path_kfklsod as path');


  # if group number is provided, filter it out
  if (defined($gnum))
  {
    push(@where, 'v$asm_disk.group_number =' . $gnum);
    push(@where, 'v$asm_disk.path = x$kfklsod.path_kfklsod');
    
    push(@from, 'v$asm_disk');
  }

  push(@from, 'x$kfklsod');
  push(@from, 'v$process');
  
  push(@where, 'v$process.pid = x$kfklsod.process_kfklsod');

  if (defined($process_pattern))
  {
    push(@where, "v\$process.program like '" . $process_pattern . "'");
  }

  if (defined($disk_pattern))
  {
    push(@where, "x\$kfklsod.path_kfklsod like '" . $disk_pattern . "'");
  }

  push(@order, 'program');
  push(@order, 'path_kfklsod');
  push(@order, 'ospid');

  #$$$ need to implement -g

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where,
                                         \@order);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($sth);
  
  @tmp_cols = @{$sth->{NAME}};
  @what = ();
  foreach (@tmp_cols)
  {
    push (@what, "\L$_");
  }

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

  #get the rows
  while (defined($row = asmcmdshare_fetch($sth)))
  {
    my(%dsk_info) = ();
    
    while(($k,$v) = each(%{$row}))
    {
      $k =~ tr/[A-Z]/[a-z]/;
      $dsk_info{$k}    = $v;
      $min_col_wid{$k} = max($min_col_wid{$k}, length($v));
    }

    push (@dsk_list, \%dsk_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, "\'" . $asmcmddisk_lsod_header{$_} . "\'");
    }
    $printf_code .= "(" . join (", ", @what_print) . ")";

    eval $printf_code;
  }

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


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

  my (%args);                             # Argument hash used by getopts(). #
  my ($ret);                     # asmcmddisk_parse_int_args() return value. #
  my ($discovery) = 0;                     # flag: 1 for -c and 0 otherwise. #
  my ($global) = 0;                        # flag: 1 for -g and 0 otherwise. #
  my ($missing) = 0;                       # flag: 1 for -M and 0 otherwise. #
  our (@disk_list);                     # list of disks returned from query. #
  my ($disk_pattern, $gnum);                # disk pattern and group number. #
  my (%min_col_wid);                         # hash of minimum column width. #
  my ($row, $row_g, $row_k, $row_s, $row_p, $row_t, $row_n);  # row formats. #
  my ($printf_code);       # dynamically generated printf() code for eval(). #
  my ($inst);                       # use ASM instance, 1 for yes, 0 for no. #
  my (%disk_types, $header_status);          # list of available disk types. #
  my ($mode);                    # mode of the command (interactive or not). #
  my ($i);
  my ($membership_str);
  my (%found_hash) = ();

  $disk_types{'MEMBER'}=0;
  $disk_types{'CANDIDATE'}=0;
  $disk_types{'FORMER'}=0;
  $disk_types{'INVALID'}=0;
  $disk_types{'UNKNOWN'}=0;
  $disk_types{'CONFLICT'}=0;
  $disk_types{'INCOMPATIBLE'}=0;
  $disk_types{'PROVISIONED'}=0;
  $disk_types{'FOREIGN'}=0;
  $disk_types{'MISSING'}=0;

  # Get option parameters, if any.
  $ret = asmcmddisk_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);

  # Process the --discovery and -g flags.
  if (defined($args{'discovery'}))
  {
    $discovery = 1;
  }
  if (defined($args{'g'}))
  {
    $global = 1;
  }
  if (defined ($args{'M'}))
  {
    $missing = 1;
  }
  # Process -d group name.
  if (defined ($args{'G'}) && defined($dbh))
  {
    $gnum = asmcmdshare_get_gnum_from_gname($dbh, $args{'G'});
    if (!defined($gnum))
    {
        my (@eargs) = ($args{'G'});
        asmcmdshare_error_msg(8001, \@eargs);
	return;
    }
  }

  # Process the disk pattern.
  if (@ARGV > 0)
  {
    $disk_pattern = shift(@ARGV);
  }

  # --member and --candidate are mutually exclusive options
  if(defined ($args{'member'}) && defined ($args{'candidate'}))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  elsif(defined ($args{'member'}))
  {
    $membership_str = 'MEMBER';
  }
  elsif(defined ($args{'candidate'}))
  {
    $membership_str = 'CANDIDATE FORMER PROVISIONED' ;
    $discovery = 1; 
  }

  # If we have a connection to an ASM instance and the -I option is not set. #
  if (defined ($dbh) && !defined ($args{'I'}))
  {
    $inst = 1;
  }
  else
  {
    $inst = 0;
  }

  if ($inst)
  {
    asmcmdshare_trace(3, "NOTE: Querying ASM instance to get list of disks",
                          'y', 'n');
    # Run the query to get a list of disks.
    @disk_list = asmcmddisk_get_dsk($dbh, $gnum, $disk_pattern,
                                    $discovery, $global, $missing,
                                    \%found_hash);
  }
  else                                   # If no connection to ASM instance. #
  {
    @disk_list = asmcmddisk_scan_dsk($args{'G'}, $disk_pattern);
  }

  # Sort the results.
  @disk_list = sort asmcmddisk_path_forward @disk_list;
  
  if (defined ($args{'M'}))
  {
    return unless ($inst);
    asmcmddisk_missing($dbh, \@disk_list, \%found_hash);
  }

  # Calculate column width.
  asmcmddisk_lsdsk_init_col_wid (\%min_col_wid);
  asmcmdshare_ls_calc_min_col_wid (\@disk_list, \%min_col_wid);

  # Set up width values for printf().
  $row_g = "%$min_col_wid{'inst_id'}" . "s  ";

  if ($inst)
  {
    $row_k = "%$min_col_wid{'total_mb'}" . "s  " .
             "%$min_col_wid{'free_mb'}" . "s  " .
             "%$min_col_wid{'os_mb'}" . "s  " .
             "%-$min_col_wid{'name'}" . "s  " .
             "%-$min_col_wid{'failgroup'}" . "s  " .
             "%-$min_col_wid{'failgroup_type'}" . "s  " .
             "%-$min_col_wid{'library'}" . "s  " .
             "%-$min_col_wid{'label'}" . "s  " .
             "%-$min_col_wid{'udid'}" . "s  " .
             "%-$min_col_wid{'product'}" . "s  " .
             "%-$min_col_wid{'redundancy'}" . "s  ";

    $row_s = "%$min_col_wid{'reads'}" . "s  " .
             "%$min_col_wid{'writes'}" . "s  " .
             "%$min_col_wid{'read_errs'}" . "s  " .
             "%$min_col_wid{'write_errs'}" . "s  " .
             "%$min_col_wid{'read_time'}" . "s  " .
             "%$min_col_wid{'write_time'}" . "s  " .
             "%$min_col_wid{'bytes_read'}" . "s  " .
             "%$min_col_wid{'bytes_written'}" . "s  " .
             "%$min_col_wid{'voting_file'}" . "s  ";

    $row_p = "%$min_col_wid{'group_number'}" . "s  " .
             "%$min_col_wid{'disk_number'}" . "s  " .
             "%$min_col_wid{'incarnation'}" . "s  " .
             "%-$min_col_wid{'mount_status'}" . "s  " .
             "%-$min_col_wid{'header_status'}" . "s  " .
             "%-$min_col_wid{'mode_status'}" . "s  " .
             "%-$min_col_wid{'state'}" . "s  ";

    $row_t = "%-$min_col_wid{'create_date'}" . "s  " .
             "%-$min_col_wid{'mount_date'}" . "s  " .
             "%-$min_col_wid{'repair_timer'}" . "s  ";
  }
  else
  {
    $row_k = "%$min_col_wid{'total_mb'}" . "s  " .
             "%-$min_col_wid{'name'}" . "s  " .
             "%-$min_col_wid{'failgroup'}" . "s  ";

    $row_s = "";

    $row_p = "%$min_col_wid{'disk_number'}" . "s  " .
             "%-$min_col_wid{'group_name'}" . "s  " .
             "%-$min_col_wid{'header_status'}" . "s  ";

    $row_t = "%-$min_col_wid{'create_date'}" . "s  " .
             "%-$min_col_wid{'mount_date'}" . "s  ";
  }

  $row_n = "%-s\n";

  $row .= $row_g if (defined ($args{'g'}) || 
                     defined ($args{'M'}));                      # lsdsk -g. #
  $row .= $row_k if (defined ($args{'k'}));                      # lsdsk -k. #
  $row .= $row_s if (defined ($args{'statistics'}));    # lsdsk --statistics.# 
  $row .= $row_p if (defined ($args{'p'}));                      # lsdsk -p. #
  $row .= $row_t if (defined ($args{'t'}));                      # lsdsk -t. #
  $row .= $row_n;                                # Always display disk path. #

  # Now generate the printf() code based on user-specified flags.
  $printf_code = 'printf $row, (';

  if (defined ($args{'g'}) || defined ($args{'M'}))
  {
    $printf_code .= '$ASMCMDDISK_LSDSK_INST_ID, ';
  }

  if (defined ($args{'k'}))
  {
    if ($inst)
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_TOTAL_MB, 
                       $ASMCMDDISK_LSDSK_FREE_MB, 
                       $ASMCMDDISK_LSDSK_OS_MB, 
                       $ASMCMDDISK_LSDSK_NAME, 
                       $ASMCMDDISK_LSDSK_FGROUP, 
                       $ASMCMDDISK_LSDSK_FGROUP_TYPE, 
                       $ASMCMDDISK_LSDSK_LIBRARY, 
                       $ASMCMDDISK_LSDSK_LABEL, 
                       $ASMCMDDISK_LSDSK_UDID, 
                       $ASMCMDDISK_LSDSK_PRODUCT, 
                       $ASMCMDDISK_LSDSK_REDUND, ';
    }
    else
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_TOTAL_MB, 
                       $ASMCMDDISK_LSDSK_NAME, 
                       $ASMCMDDISK_LSDSK_FGROUP, ';
    }
  }

  if (defined ($args{'statistics'}))
  {
    if ($inst)
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_READS, 
                       $ASMCMDDISK_LSDSK_WRITES, 
                       $ASMCMDDISK_LSDSK_READ_ERRS, 
                       $ASMCMDDISK_LSDSK_WRITE_ERRS, 
                       $ASMCMDDISK_LSDSK_READ_TIME, 
                       $ASMCMDDISK_LSDSK_WRITE_TIME, 
                       $ASMCMDDISK_LSDSK_BYTES_READ, 
                       $ASMCMDDISK_LSDSK_BYTES_WRITTEN, 
                       $ASMCMDDISK_LSDSK_VOTING_FILE, ';
    }
    # Nothing to print if not $inst.
  }

  if (defined ($args{'p'}))
  {
    if ($inst)
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_GNUM, 
                       $ASMCMDDISK_LSDSK_DNUM, 
                       $ASMCMDDISK_LSDSK_INCARN, 
                       $ASMCMDDISK_LSDSK_MOUNTSTAT, 
                       $ASMCMDDISK_LSDSK_HEADERSTAT, 
                       $ASMCMDDISK_LSDSK_MODESTAT, 
                       $ASMCMDDISK_LSDSK_STATE, ';
    }
    else
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_DNUM, 
                       $ASMCMDDISK_LSDSK_DISKGROUP, 
                       $ASMCMDDISK_LSDSK_HEADERSTAT, ';
    }
  }

  if (defined ($args{'t'}))
  {
    if ($inst)
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_CREATE_DATE, 
                       $ASMCMDDISK_LSDSK_MOUNT_DATE, 
                       $ASMCMDDISK_LSDSK_REPAIR_TIMER, ';
    }
    else
    {
      $printf_code .= '$ASMCMDDISK_LSDSK_CREATE_DATE, 
                       $ASMCMDDISK_LSDSK_MOUNT_DATE, ';
    }
  }

  # Always display the path.
  $printf_code .= '$ASMCMDDISK_LSDSK_PATH) ';

  # Now print the header if not --suppressheader and if we have at least one 
  # row of results.
  if (!defined ($args{'suppressheader'}) && (@disk_list > 0))
  {
    eval($printf_code);
    asmcmdshare_trace(1, $@, 'y', 'y') if $@;
  }

  # Print the actual rows of results.
  for ($i = 0; $i < @disk_list; $i++)
  {
    next if (defined ($args{'M'}) &&
             $disk_list[$i]->{'header_status'} ne "MISSING");

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

    if (defined ($args{'g'}) ||defined ($args{'M'}))
    {
      $printf_code .= q|$disk_list[$i]->{'inst_id'}, |;
    }

    if (defined ($args{'k'}))
    {
      if ($inst)
      {
        $printf_code .= q|$disk_list[$i]->{'total_mb'}, 
                          $disk_list[$i]->{'free_mb'}, 
                          $disk_list[$i]->{'os_mb'}, 
                          $disk_list[$i]->{'name'}, 
                          $disk_list[$i]->{'failgroup'}, 
                          $disk_list[$i]->{'failgroup_type'}, 
                          $disk_list[$i]->{'library'}, 
                          $disk_list[$i]->{'label'}, 
                          $disk_list[$i]->{'udid'}, 
                          $disk_list[$i]->{'product'}, 
                          $disk_list[$i]->{'redundancy'}, |;
      }
      else
      {
        $printf_code .= q|$disk_list[$i]->{'total_mb'}, 
                          $disk_list[$i]->{'name'}, 
                          $disk_list[$i]->{'failgroup'}, |;
      }
    }

    if (defined ($args{'statistics'}))
    {
      if ($inst)
      {
        $printf_code .= q|$disk_list[$i]->{'reads'}, 
                          $disk_list[$i]->{'writes'}, 
                          $disk_list[$i]->{'read_errs'}, 
                          $disk_list[$i]->{'write_errs'}, 
                          $disk_list[$i]->{'read_time'}, 
                          $disk_list[$i]->{'write_time'}, 
                          $disk_list[$i]->{'bytes_read'}, 
                          $disk_list[$i]->{'bytes_written'},
                          $disk_list[$i]->{'voting_file'}, |;
      }
      # Nothing to print if not $inst.
    }

    if (defined ($args{'p'}))
    {
      if ($inst)
      {
        $printf_code .= q|$disk_list[$i]->{'group_number'}, 
                          $disk_list[$i]->{'disk_number'}, 
                          $disk_list[$i]->{'incarnation'}, 
                          $disk_list[$i]->{'mount_status'}, 
                          $disk_list[$i]->{'header_status'}, 
                          $disk_list[$i]->{'mode_status'}, 
                          $disk_list[$i]->{'state'}, |;
      }
      else
      {
        $printf_code .= q|$disk_list[$i]->{'disk_number'}, 
                          $disk_list[$i]->{'group_name'}, 
                          $disk_list[$i]->{'header_status'}, |;
      }
    }

    if (defined ($args{'t'}))
    {
      if ($inst)
      {
        $printf_code .= q|$disk_list[$i]->{'create_date'}, 
                          $disk_list[$i]->{'mount_date'}, 
                          $disk_list[$i]->{'repair_timer'}, |;
      }
      else
      {
        $printf_code .= q|$disk_list[$i]->{'create_date'}, 
                          $disk_list[$i]->{'mount_date'}, |;
      }
    }

    # Always show path.
    $printf_code .= q|$disk_list[$i]->{'path'})|;

    # Now evaluate the printf() expression.
    next if (defined ($membership_str) &&
             $membership_str !~ $disk_list[$i]->{'header_status'});

    eval($printf_code);
    asmcmdshare_trace(1, $@, 'y', 'y') if $@;

    # Collect what kinds of disks do we have
    $header_status = $disk_list[$i]->{'header_status'}; 
    $disk_types{$header_status}=$disk_types{$header_status}+1;
  }

  $mode = $asmcmdglobal_hash{'mode'};
  if ( $mode eq 'n' )
  {
    if ( $disk_types{'MEMBER'} > 0 && $disk_types{'CANDIDATE'} > 0 ){
      exit 2;
    }
    if ( $disk_types{'MEMBER'} == 0 && $disk_types{'CANDIDATE'} > 0 ){
      exit 1;
    }
    if ( $disk_types{'MEMBER'} > 0 && $disk_types{'CANDIDATE'} == 0 ){
      exit 0;
    }
    exit -1;
  }
}


########
# NAME
#   asmcmddisk_scan_dsk
#
# DESCRIPTION
#   This function scans the header information on a set of disks
#   identified by <disk_pattern> and returns that information.
#
# PARAMETERS
#   gname        (IN) - string name of the disk group by which to filter
#                       results.
#   disk_pattern (IN) - discovery string that identifies the set of
#                       disks to scan.
#
# RETURNS
#   An array of hashes of disk header fields.
#
########
sub asmcmddisk_scan_dsk
{
  my ($gname, $disk_pattern) = @_;
  my (@disk_list);        # List of hashes of disk information to be printed. #
  my (@path_list);   # List of path strings returned from the discovery glob. #
  my ($path);              # Iterator through the list of paths (@path_list). #
  my ($has_header);        # Boolean: whether a given disk has an ASM header. #
  my (@eargs);                       # Error arguments used for error output. #
  my ($kfed);                                # Path to kfed stand-alone tool. #
  my ($kfod, $kfod_exec);
  my ($buf, @lines);

  if (!defined($asmcmdshare_unix_os{$^O}))
  {
    # Error out if the OS is not UNIX. We currently support only UNIX OSes.
    asmcmdshare_error_msg(9378, undef);
    return;
  }
  
  # Form path to kfed executable.
  $kfed = "$ENV{'ORACLE_HOME'}/bin/kfed";

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

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

  # Untaint $kfed. Match everything except newlines, carriage returns,
  # and tabs
  $kfed =~ /([^\n^\r^\t]+)/;
  $kfed = $1;


  # Form path to kfod executable.
  $kfod = "$ENV{'ORACLE_HOME'}/bin/kfod";

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

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

  # Untaint $kfed. Match everything except newlines, carriage returns,
  # and tabs
  $kfod =~ /([^\n^\r^\t]+)/;
  $kfod = $1;

  # Use default discovery string if one is not specified.
  if (!defined ($disk_pattern) || $disk_pattern eq '')
  {
    $disk_pattern = $ASMCMDDISK_LINUX_DISCOVER;
  }

  # Substitute any acceptable wild card character with '*', which
  # is known to be accepted. This way, any asmcmd wild card
  # character can be used.
  $disk_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\*,g;


  # discover the disks with kfod
  $kfod_exec = "$kfod a='$disk_pattern' di=all _asm_a=FALSE n=TRUE op=DISKS";

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

   asmcmdshare_trace(3, "NOTE: Discovering disks using kfod..", 'n', 'n');
  eval
  {
    $buf = `$kfod_exec`;
  };
  if ($@ ne '')
  {
    # The require statement failed. Quit lsdsk with error.
    @eargs = ($kfod_exec, $@);
    asmcmdshare_error_msg(9375, \@eargs);
    last;
  }

  @path_list=();
  @lines = split (/\n/, $buf);
  foreach (@lines)
  {
    my (@line);
    $_ =~ s/^\s+//;
    @line = split(' ', $_);
    push (@path_list, $line[1]);
  }

  # Open each path one at a time.
  foreach $path (@path_list)
  {
    # Skip directories. 
    if (-d $path)
    {
      next;
    }

    my (%disk_attr);         # One row of disk record representing one disk. #
    my ($attr);                                # A single disk header field. #
    my ($kfed_exec);                                  # kfed execution line. #
    my (@lines);                       # Array of lines of output from kfed. #
    my ($au_size);                              # AU size of the disk group. #
    my ($num_aus);                    # The number of AUs in the disk group. #
    my ($month, $day, $hour, $minute, $second, $year);

    # Untaint $path. Match everything except newlines, carriage returns,
    # and tabs
    $path =~ /([^\n^\r^\t]+)/;
    $path = $1;

    # Form the kfed execution line to read the disk header.
    $kfed_exec = "$kfed op=read aunum=0 blknum=0 dev=\'$path\'";

    # Execute the kfed command and capture the results.
    asmcmdshare_trace(4, "asmcmddisk_scan_dsk(): Reading disk header of $path"
                   ." using kfed", 'n', 'n');
    eval
    {
      $buf = `$kfed_exec`;
    };
    if ($@ ne '')
    {
      # The require statement failed. Quit lsdsk with error.
      @eargs = ($kfed_exec, $@);
      asmcmdshare_error_msg(9375, \@eargs);
      last;
    }

    # If we get "KFED-00303: unable to open file," skip the disk.
    # There is no need to print the error, as we're doing discovery,
    # essentially.
    if ($buf =~ /^KFED-00303/)
    {
      next;
    }

    # Split output into an array of lines of output.
    @lines = split (/\n/, $buf);

    # Initialize all fields of the hash to avoid Perl errors.
    %disk_attr = asmcmddisk_init_disk_attr();

    # We already know the path.
    $disk_attr{'path'} = $path;

    # Validate disk header. 
    $has_header = asmcmddisk_validate_header(\@lines);

    # If no header, then mark as candidate. 
    if (!$has_header)
    {
      $disk_attr{'header_status'} = 'CANDIDATE';
    }
    else
    {
      # If there is a header, retrieve the header information.

      # Get the header status.
      $disk_attr{'header_status'} = $lines[21];
      $disk_attr{'header_status'} =~ /KFDHDR_(\w+)$/;
      $disk_attr{'header_status'} = $1;

      # Get the disk group name.
      $disk_attr{'group_name'} = $lines[23];
      $disk_attr{'group_name'} =~ s/^kfdhdb.grpname\:\s+(\w+?)\s\;.+/$1/;
      $disk_attr{'group_name'} = $1;

      # Get the disk number.
      $disk_attr{'disk_number'} = $lines[19];
      $disk_attr{'disk_number'} =~ s/^kfdhdb.dsknum\:\s+(\d+?)\s\;.+/$1/;
      $disk_attr{'disk_number'} = $1;

      # Get the fail group name.
      $disk_attr{'failgroup'} = $lines[24];
      $disk_attr{'failgroup'} =~ s/^kfdhdb.fgname\:\s+(\w+?)\s\;.+/$1/;
      $disk_attr{'failgroup'} = $1;

      # Get the disk name.
      $disk_attr{'name'} = $lines[22];
      $disk_attr{'name'} =~ s/^kfdhdb.dskname\:\s+(\w+?)\s\;.+/$1/;
      $disk_attr{'name'} = $1;

      # Get the total MB of the disk.
      $au_size = $lines[32];
      $au_size =~ s/^kfdhdb.ausize\:\s+(\d+?)\s\;.+/$1/;
      $au_size = $1;

      $num_aus = $lines[34];
      $num_aus =~ s/^kfdhdb.dsksize\:\s+(\d+?)\s\;.+/$1/;
      $num_aus = $1;

      # Calcute total MB. Be careful not to overflow 4-byte integers.
      if ($au_size < $ASMCMDDISK_1MB)
      {
        $disk_attr{'total_mb'} = $au_size * $num_aus / $ASMCMDDISK_1MB;
      }
      else
      {
        $disk_attr{'total_mb'} = $au_size / $ASMCMDDISK_1MB * $num_aus;
      }

      # Get the creation time stamp.
      $disk_attr{'create_date'} =
        asmcmddisk_convert_date($lines[26], $lines[27]);

      # Get the month time stamp.
      $disk_attr{'mount_date'} =
        asmcmddisk_convert_date($lines[28], $lines[29]);
    }

    # Add to disk results list only if 1) we are not restricting by
    # disk group or 2) this disk belongs to the specified disk group.
    if (!defined($gname) || ($disk_attr{'group_name'} eq uc($gname)))
    {
      # Reference the disk hash from the disk_list array.
      push (@disk_list, \%disk_attr);
    }
  }

  return @disk_list;
}


########
# NAME
#   asmcmddisk_convert_date
#
# DESCRIPTION
#   This function converts a date that is in kfed output format 
#   to a string format. 
#
# PARAMETERS
#   hi         (IN) - the high 4-bytes of the date from kfed
#   lo         (IN) - the low  4-bytes of the date from kfed
#
# RETURNS
#   Date string in "MON DAY HH24:MM:SS YEAR" format.
#
########
sub asmcmddisk_convert_date
{
  my ($hi, $lo) = @_;
  my ($year, $month, $day, $hour, $minute, $second);
  
  my ($date);
  my (%months) = ( 1    => 'Jan',
                   2    => 'Feb',
                   3    => 'Mar',
                   4    => 'Apr',
                   5    => 'May',
                   6    => 'Jun',
                   7    => 'Jul',
                   8    => 'Aug',
                   9    => 'Sep',
                  10    => 'Oct',
                  11    => 'Nov',
                  12    => 'Dec',
                 );

  # Month, day, hour, and year are kept in the high bits.
  $month = $day = $hour = $year = $hi;

  # Minute, second, and smaller units are kept in the low bits.
  $minute = $second = $lo;

  # Use regex to extract the month.
  $month =~ /MNTH=0x([0-9a-f]+)/;
  $month = $1;

  # Use regex to extract the day.
  $day =~ /DAYS=0x([0-9a-f]+)/;
  $day = $1;

  # Use regex to extract the hour.
  $hour =~ /HOUR=0x([0-9a-f]+)/;
  $hour = $1;

  # Use regex to extract the year.
  $year =~ /YEAR=0x([0-9a-f]+)/;
  $year = $1;

  # Use regex to extract the minute.
  $minute =~ /MINS=0x([0-9a-f]+)/;
  $minute = $1;

  # Use regex to extract the second.
  $second =~ /SECS=0x([0-9a-f]+)/;
  $second = $1;

  # Add preceding zeros to values. For the year, the value should be
  # 4 digits long, so add a preceding zero if it's only three digits
  # long. For all other values, they should be 2 digits long, so add
  # preceding zeros if they are each only 1 digit long.
  $month  = '0' . $month  if (length($month)  < 2);
  $day    = '0' . $day    if (length($day)    < 2);
  $hour   = '0' . $hour   if (length($hour)   < 2);
  $minute = '0' . $minute if (length($minute) < 2);
  $second = '0' . $second if (length($second) < 2);
  $year   = '0' . $year   if (length($year)   < 4);

  # Convert from hex to decimal. Note that H denotes one nybble, or 
  # 4-bits, or one hex digit. These five values are at most two
  # nybbles, or one byte long. C denotes a byte and is portable.
  $month  = unpack ("C", pack ("H2", $month));
  $day    = unpack ("C", pack ("H2", $day));
  $hour   = unpack ("C", pack ("H2", $hour));
  $minute = unpack ("C", pack ("H2", $minute));
  $second = unpack ("C", pack ("H2", $second));

  # The year requires 4 nybbles, or 2 bytes. H4 always formats in 
  # 2-byte big-endian format, and n always reads in 2-byte big-endian
  # format, so there is no portability issue here, either.
  $year = unpack ("n", pack ("H4", "07d7"));

  # Add preceding zeros for formatting purposes.
  $day    = '0' . $day    if ($day    < 10);
  $hour   = '0' . $hour   if ($hour   < 10);
  $minute = '0' . $minute if ($minute < 10);
  $second = '0' . $second if ($second < 10);

  $month = $months{$month};

  $date = "$month $day $hour:$minute:$second $year";

  return $date;
}


########
# NAME
#   asmcmddisk_validate_header
#
# DESCRIPTION
#   Given the results from kfed, this function determines if
#   this block is a valid ASM disk header block.
#
# PARAMETERS
#   lines_ref           (IN) - reference to array of lines of kfed results
#   
# RETURNS
#   TRUE if <block> is a valid ASM disk header and FALSE otherwise.
#
# NOTES
#   This function uses a similar algorithm as that of kfbtValid(),
#   with the exception of the checksum, which is skipped. The reason
#   is that the results kfed returns have already been checksummed.
########
sub asmcmddisk_validate_header
{
  my ($lines_ref) = shift;
  my ($has_header) = 1;
  my ($disk_endian);
  my ($disk_hard);
  my ($disk_btype);
  my ($disk_dformat);

  if (@{$lines_ref} < 16)
  {
    return 0;
  }

  # Retrieve the four elements to check: endianness, H.A.R.D., block
  # type, and data format.

  # Endianness
  $disk_endian = $lines_ref->[0];
  $disk_endian =~ s/^kfbh.endian\:\s+(\d+?)\s\;.+/$1/;

  # H.A.R.D.
  $disk_hard = $lines_ref->[1];
  $disk_hard =~ s/^kfbh.hard\:\s+(\d+?)\s\;.+/$1/;

  # Block type
  $disk_btype = $lines_ref->[2];
  $disk_btype =~ s/^kfbh.type\:\s+(\d+?)\s\;.+/$1/;

  # Data format
  $disk_dformat = $lines_ref->[3];
  $disk_dformat =~ s/^kfbh.datfmt\:\s+(\d+?)\s\;.+/$1/;

  # Do header verification.
  if (!asmcmddisk_verify_endian($disk_endian))
  {
    $has_header = 0;
  }
  elsif (!asmcmddisk_verify_hard($disk_hard))
  {
    $has_header = 0;
  }
  elsif (!asmcmddisk_verify_btype($disk_btype))
  {
    $has_header = 0;
  }
  elsif (!asmcmddisk_verify_dformat($disk_dformat))
  {
    $has_header = 0;
  }

  return $has_header;
}


########
# NAME
#   asmcmddisk_verify_endian
#
# DESCRIPTION
#   Given a string of binary digits <disk_endian>, this function 
#   verifies if the endianness of this disk endian string is
#   the same as the endianness of this machine.
#
# PARAMETERS
#   disk_endian     (IN) - binary string of disk endianness
#  
# RETURNS
#   TRUE if the disk endianness matches the machine endianness;
#   FALSE otherwise.
#
########
sub asmcmddisk_verify_endian
{
  my ($disk_endian) = shift;
  my ($ret) = 1;

  # If disk endian is not the same as system endian, then check fails.
  if ($disk_endian != $asmcmdglobal_hash{'endn'})
  {
    $ret = 0;
  }

  return $ret;
}


########
# NAME
#   asmcmddisk_verify_hard
#
# DESCRIPTION
#   Given an integer <disk_hard>, check to see if this value is
#   the ASM HARD 4K constant.
#
# PARAMETERS
#   disk_hard          (IN) - integer of the disk HARD value
#  
# RETURNS
#   TRUE if <disk_hard> equals <ASMCMDDISK_HARD_4K>; FALSE otherwise.
#
########
sub asmcmddisk_verify_hard
{
  my ($disk_hard) = shift;
  my ($ret) = 1;

  if ($disk_hard != $ASMCMDDISK_HARD_4K)
  {
    $ret = 0;
  } 

  return $ret;
}


########
# NAME
#   asmcmddisk_verify_btype
#
# DESCRIPTION
#   Given an integer <disk_btype>, determine if the disk block type
#   is the disk header block type.
#
# PARAMETERS
#   disk_btype          (IN) - integer value for the disk block type
#  
# RETURNS
#   TRUE if <disk_btype> is the disk header block type; FALSE otherwise
#
########
sub asmcmddisk_verify_btype
{
  my ($disk_btype) = shift;
  my ($ret) = 1;

  if ($disk_btype != $ASMCMDDISK_BTYPE_DISKHEAD)
  {
    $ret = 0;
  }

  return $ret;
}


########
# NAME
#   asmcmddisk_verify_dformat
#
# DESCRIPTION
#   Determine if the disk format in <disk_dformat> is correct.
#
# PARAMETERS
#   disk_dformat       (IN) - disk format
#  
# RETURNS
#   TRUE if <disk_dformat> is correct; FALSE otherwise.
#
########
sub asmcmddisk_verify_dformat
{
  my ($disk_dformat) = shift;
  my ($ret) = 1;

  if ($disk_dformat == 0)
  {
    $ret = 0;
  }

  return $ret;
}


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

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

  return %dsk_info;
}


########
# NAME
#   asmcmddisk_path_forward
#
# DESCRIPTION
#   Routine for ordering a sort, used by Perl's built-in function sort().
#   Order alphabetically by path from v$asm_disk.
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
########
sub asmcmddisk_path_forward
{
  $a->{'path'} cmp $b->{'path'};
}

########
# NAME
#   asmcmddisk_missing
#
# DESCRIPTION
#   Routine for adding missing disks to disk list. They are added with
#   header_status set to "MISSING" 
#
# PARAMETERS
#   None.
#
# RETURNS
#   N/A.
########
sub asmcmddisk_missing
{
  my ($dbh, $list_ref, $found_ref) = @_;
  my ($i);
  my ($inst);
  my ($key);
  my (%inst_info) = asmcmddisk_get_inst($dbh);

  # Go through the disk list and cross check angainst the instance list
  # looking for path/inst_id combos that have not been found. 
  for ($i = 0; $i <  @{$list_ref}; $i++)
  {
    foreach $inst (keys(%inst_info))
    { 
      $key = $list_ref->[$i]->{'path'} . $inst;
 
      # Was a disk found for this path/instance? If not add it it as missing
      if (!defined($found_ref->{$key})) 
      {
        my (%new_disk);
        %new_disk = asmcmddisk_init_disk_attr();
        $new_disk{'path'} =  $list_ref->[$i]->{'path'};
        $new_disk{'inst_id'} = $inst_info{$inst};
        $new_disk{'header_status'} = 'MISSING';
        $found_ref->{$key} = 1;
        push(@{$list_ref}, \%new_disk);
      }
    }
  }

}

########
# NAME
#   asmcmddisk_lsdsk_init_col_wid
#
# DESCRIPTION
#   This routine initializes the minimum column width hash with default values
#   for lsdsk. 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_disk but look similar.
#
# PARAMETERS
#   min_col_wid_ref (OUT) - reference to hash of minimum column width.
#
# RETURNS
#   Null.
#
# NOTES
#   Must call this routine or asmcmddisk_lsdsk_init_col_wid() before calling 
#   asmcmdbase_ls_calc_min_col_wid().
########
sub asmcmddisk_lsdsk_init_col_wid
{
  my ($min_col_wid_ref) = shift;

  $min_col_wid_ref->{'inst_id'}       = length($ASMCMDDISK_LSDSK_INST_ID);
  $min_col_wid_ref->{'group_number'}  = length($ASMCMDDISK_LSDSK_GNUM);
  $min_col_wid_ref->{'disk_number'}   = length($ASMCMDDISK_LSDSK_DNUM);
  $min_col_wid_ref->{'incarnation'}   = length($ASMCMDDISK_LSDSK_INCARN);
  $min_col_wid_ref->{'group_name'}    = length($ASMCMDDISK_LSDSK_DISKGROUP);
  $min_col_wid_ref->{'mount_status'}  = length($ASMCMDDISK_LSDSK_MOUNTSTAT);
  $min_col_wid_ref->{'header_status'} = length($ASMCMDDISK_LSDSK_HEADERSTAT);
  $min_col_wid_ref->{'mode_status'}   = length($ASMCMDDISK_LSDSK_MODESTAT);
  $min_col_wid_ref->{'state'}         = length($ASMCMDDISK_LSDSK_STATE);
  $min_col_wid_ref->{'redundancy'}    = length($ASMCMDDISK_LSDSK_REDUND);
  $min_col_wid_ref->{'library'}       = length($ASMCMDDISK_LSDSK_LIBRARY);
  $min_col_wid_ref->{'os_mb'}         = length($ASMCMDDISK_LSDSK_OS_MB);
  $min_col_wid_ref->{'total_mb'}      = length($ASMCMDDISK_LSDSK_TOTAL_MB);
  $min_col_wid_ref->{'free_mb'}       = length($ASMCMDDISK_LSDSK_FREE_MB);
  $min_col_wid_ref->{'name'}          = length($ASMCMDDISK_LSDSK_NAME);
  $min_col_wid_ref->{'failgroup'}     = length($ASMCMDDISK_LSDSK_FGROUP);
  $min_col_wid_ref->{'failgroup_type'}= length($ASMCMDDISK_LSDSK_FGROUP_TYPE);
  $min_col_wid_ref->{'label'}         = length($ASMCMDDISK_LSDSK_LABEL);
  $min_col_wid_ref->{'path'}          = length($ASMCMDDISK_LSDSK_PATH);
  $min_col_wid_ref->{'udid'}          = length($ASMCMDDISK_LSDSK_UDID);
  $min_col_wid_ref->{'product'}       = length($ASMCMDDISK_LSDSK_PRODUCT);
  $min_col_wid_ref->{'create_date'}   = length($ASMCMDDISK_LSDSK_CREATE_DATE);
  $min_col_wid_ref->{'mount_date'}    = length($ASMCMDDISK_LSDSK_MOUNT_DATE);
  $min_col_wid_ref->{'repair_timer'}  = length($ASMCMDDISK_LSDSK_REPAIR_TIMER);
  $min_col_wid_ref->{'reads'}         = length($ASMCMDDISK_LSDSK_READS);
  $min_col_wid_ref->{'writes'}        = length($ASMCMDDISK_LSDSK_WRITES);
  $min_col_wid_ref->{'read_errs'}     = length($ASMCMDDISK_LSDSK_READ_ERRS);
  $min_col_wid_ref->{'write_errs'}    = length($ASMCMDDISK_LSDSK_WRITE_ERRS);
  $min_col_wid_ref->{'read_time'}     = length($ASMCMDDISK_LSDSK_READ_TIME);
  $min_col_wid_ref->{'write_time'}    = length($ASMCMDDISK_LSDSK_WRITE_TIME);
  $min_col_wid_ref->{'bytes_read'}    = length($ASMCMDDISK_LSDSK_BYTES_READ);
  $min_col_wid_ref->{'bytes_written'} = length($ASMCMDDISK_LSDSK_BYTES_WRITTEN);
  $min_col_wid_ref->{'voting_file'}   = length($ASMCMDDISK_LSDSK_VOTING_FILE);

  return;
}


########
# NAME
#   asmcmddisk_process_remap
#
# DESCRIPTION
#   This function implements the asmcmd remap functionality.  Given
#   a disk group name, a disk name, and a range of physical blocks,
#   this function translates this information into file virtual extents
#   in ASM. The dbms_diskgroup.remap() PL/SQL function is called 
#   to remap the identified virtual extent, if any block within are
#   unreadable.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_process_cmd() can call this routine.
#
#   Physical blocks are essentially disk sectors.
#
#   The AU physical block size is retrieved from the disk
#   x@kfkid.blksz_kfkid
#
########
sub asmcmddisk_process_remap 
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my ($qry);                                          # SQL query statement. #
  my ($sth);                                            # SQL query handler. #
  my ($row);                                                # SQL query row. #
  my ($gname);                                                 # Group name. #
  my ($gnum);                                                # Group number. #
  my ($dname);                                                  # Disk name. #
  my ($dnum);                                                 # Disk number. #
  my ($range);                                            # Range of blocks. #
  my ($start, $end);                     # Start and end of range of blocks. #
  my ($ret);                     # Return value for SQL execution statement. #
  my ($plsql_stmt);                       # ASM PL/SQL statement to execute. #
  my (@eargs);                                            # Error arguments. #
  my ($pblocksize);                       # Physical block size of the disk. #
  my ($ausize);                                                   # AU size. #
  my ($pblocks_per_au);                  # Number of physical blocks per AU. #
  my ($aunum);                              # AU number to fetch in x$kfdat. #
  my ($fnum);                                                 # File number. #
  my ($pxn);                                       # Physical extent number. #
  my ($bnum);                                       # Physical block number. #
  my ($pblock_offset_in_au);             # Physical block offset into an AU. #
  my ($file_redund);                     # File redundancy from redun_kffil. #
  my ($redund_factor);      # Redundancy factor: 3=high, 2=mirror, 1=unprot. #
  my ($vxn);                  # Virtual extent number, or extent set number. #
  my ($au_block_start);    # Start of a physical block range on a single AU. #
  my ($au_block_end);        # End of a physical block range on a single AU. #
                                                                # an extent. #
  my ($filename); # Filename, in the form +diskgroup.filenumber.incarnation. #
  my ($map_failed) = 0;        # Whether an AU failed to map to an ASM file. #

  # Integer division only, not floating point, which is default.
  use integer;

  # Check if number of non-option parameters are correct: must be exactly 3.
  if (@ARGV != 3) 
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # The remap command requires at least ASM version 11gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_11gR1) < 0 )
  {
    asmcmdshare_error_msg(9381, undef);
    return;
  }

  $gname = shift (@ARGV);                        # Get group name parameter. #
  $dname = shift (@ARGV);                         # Get disk name parameter. #
  $range = shift (@ARGV);                       # Get block range parameter. #

  $range =~ s/\s//g;                                    # Remove all spaces. #
  ($start, $end) = split ('-', $range, 2);

  # Make sure that the range is in the format of [0-9]+-[0-9]+, and that
  # the $end is >= $start.
  if (!defined ($start) || !defined ($end) ||
      ($start !~ /\d/) || ($start =~ /\D/) ||
      ($end !~ /\d/) || ($end =~ /\D/) || ($end < $start))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  # Validate and get group number. 
  $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);
  if (!defined ($gnum))
  {
    @eargs = ($gname);
    asmcmdshare_error_msg(8001, \@eargs);
    return;
  }

  # Validate and get disk number.
  $dnum = asmcmddisk_get_dnum_from_dname($dbh, $gnum, $dname);
  if (!defined ($dnum))
  { 
    @eargs = ($dname, $gname);
    asmcmdshare_error_msg(9371, \@eargs);
    return;
  }

  # Get the sector size for this disk
  # All the disks of a diskgroup have the same sector size
  # If migration is happening, the value will be the target sector size
  # even if migration is not complete
  $pblocksize = asmcmddisk_get_dsk_secsize($dbh, $gnum, $dnum);

  # Get the ausize for the disk group.
  $ausize = asmcmddisk_get_dg_ausize($dbh, $gnum);

  $pblocks_per_au = $ausize / $pblocksize;
  $bnum = $start;

  # Walk through all the physical blocks one AU at a time.
  while ($bnum <= $end)
  {
    # Mark the starting physical block of a new AU.
    $au_block_start = $bnum;

    # Calculate the AU we want.
    $aunum  = $bnum / $pblocks_per_au;

    # Calculate the physical block offset into the last AU represented
    # by the physical block range.
    $pblock_offset_in_au = $bnum % $pblocks_per_au;

    # Query for the file number and physical extent number.
    $qry = 'select fnum_kfdat, xnum_kfdat from x$kfdat where ' .
           'group_kfdat=? and number_kfdat=?' .
           ' and aunum_kfdat=?';
    $sth = asmcmdshare_do_prepare($dbh, $qry);
    $sth->bind_param(1,$gnum);
    $sth->bind_param(2,$dnum);
    $sth->bind_param(3,$aunum);
    asmcmdshare_do_execute($sth);
    $row = asmcmdshare_fetch($sth);
    $fnum = $row->{'FNUM_KFDAT'};
    $pxn = $row->{'XNUM_KFDAT'};
    asmcmdshare_finish($sth);

    # Find ending physical block of the AU, which is the last block
    # in this AU.
    $au_block_end = $bnum - ($pblock_offset_in_au + 1) + $pblocks_per_au;

    # Advances $bnum to the block past the last block on the AU.
    $bnum = $au_block_end + 1;

    # Error arguments should use $au_block_end as the range ending
    # unless $au_block_end exceeds $end, in which case $end
    # should be used. The reason we use $end in the latter case
    # is that we don't want the error message to include a range
    # that is outside of the range originally specified by the user.
    if ($au_block_end <= $end)
    {
      @eargs = ($au_block_start, $au_block_end);
    }
    else
    {
      @eargs = ($au_block_start, $end);
    }

    # If no file is allocated on this AU, then return error.
    if (!defined ($fnum) || ($fnum == 0))
    {
      asmcmdshare_error_msg(9372, \@eargs);
      $map_failed = 1;
      next;
    }

    # Get redundancy information on the file from x$kffil.
    $qry = 'select redun_kffil from x$kffil ' .
           'where group_kffil=? and number_kffil=?';
    $sth = asmcmdshare_do_prepare($dbh, $qry);
    $sth->bind_param(1,$gnum);
    $sth->bind_param(2,$fnum);
    asmcmdshare_do_execute($sth);
    $row = asmcmdshare_fetch($sth);
    $file_redund = $row->{'REDUN_KFFIL'};
    asmcmdshare_finish($sth);

    if (!defined ($file_redund))
    {
      # Assume external redundancy by default if unknown file, which
      # is usually when $fnum==0. Having File 0 will error out correctly
      # in PL/SQL, so no need to error out here.
      $redund_factor = 1;
    }
    elsif ($file_redund eq "UNPROT")
    {
      $redund_factor = 1;
    }
    elsif ($file_redund eq "MIRROR")
    {
      $redund_factor = 2;
    }
    else 
    {
      $redund_factor = 3;
    }

    if ($redund_factor == 1)
    {
      asmcmdshare_error_msg(9382, \@eargs);
      $map_failed = 1;
      next;
    }

    # Calculate the virtual extent number of the file.
    $vxn = $pxn / $redund_factor;

    # Construct the PL/SQL statement to remap virtual extent $vxn of
    # file $fnum or disk group $gnum.
    $plsql_stmt = qq|
        begin
          dbms_diskgroup.remap($gnum, $fnum, $vxn);

        exception when others then
         raise;
        end; |;

    # Run SQL. #
    $ret = asmcmdshare_do_stmt($dbh, $plsql_stmt);

    # Display $DBI::errstr if do_stmt failed.  Recording ASMCMD error 8101
    # simply prints $DBI::errstr.  Use 8101 for non-select SQL statements
    # that fail.
    asmcmdshare_error_msg(8101, undef) unless (defined ($ret));
  }

  # Print error 9373 if any physical blocks failed to map to an ASM file. */
  if ($map_failed)
  {
    asmcmdshare_error_msg(9373, undef);
  }
  else
  {
    print STDOUT "Remap requests submitted\n";
  }

  return;
}


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

  my (%args);
  my ($force);
  my ($mode);
  my ($contents);
  my (@dgroup_list);
  my ($dgname);
  my ($dgroup_pattern, $global);
  my ($qry, $ret);

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

  $force    = 0;
  $contents = 0;

  # check for the force option
  $force = 1 if (defined($args{'f'}));

  # check for the including contents option
  $contents = 1 if (defined($args{'r'}));

  if ($force == 1 && $contents == 0)
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  if (@ARGV != 1)
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $dgname = $ARGV[0];

  $qry = 'DROP DISKGROUP ' . $dgname . ' ';

  if ($contents){
    if ($force){
      $qry .= 'FORCE ';
    }

    $qry .= 'INCLUDING CONTENTS ';
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);

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

  return;
}


########
# NAME
#   asmcmddisk_mkdg_start
#
# DESCRIPTION
#   This function processes the start of xml tags in mkdg.
#
# PARAMETERS
#   expat   (IN) - expat parser object
#   element (IN) - tag element name
#   attrs   (IN) - tag element attributes
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_process_mkdg() calls this function.
########
#
#   transition map
#
#   Tags on the xml elements
#   dg  => disk group
#   fg  => failure group
#   dsk => disk
#   a   => attribute
#                       TO
#           |dg     |fg     |dsk    |a      |
#   +-------+-------+-------+-------+-------+
#    dg     |err    |       |       |       |
# F +-------+-------+-------+-------+-------+
# R  fg     |err    |err    |       |err    |
# O +-------+-------+-------+-------+-------+
# M  dsk    |err    |err    |err    |err    |
#   +-------+-------+-------+-------+-------+
#    a      |err    |err    |err    |err    |
#   +-------+-------+-------+-------+-------+
#
########
sub asmcmddisk_mkdg_start
{
  my ($expat, $element, %attrs) = @_;
  my (@eargs);

  if ($element eq 'dg')
  {
    my ($name, $redun);
    if (@asmcmddisk_parser_state != 0)
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }
    push (@asmcmddisk_parser_state, $element);

    if (defined($attrs{'name'}))
    {
      $name = $attrs{'name'};
    }
    else
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }
    $dgstmt = "create diskgroup " . $name . " ";

    if (defined($attrs{'redundancy'}))
    {
      $redun = $attrs{'redundancy'};
      $dgstmt .= $redun . " redundancy ";
    }
    @asmcmddisk_parser_disks = ();
    @asmcmddisk_parser_attrs = ();
  }
  elsif ($element eq 'fg')
  {
    my ($name);

    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'dg')
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    push (@asmcmddisk_parser_state, $element);

    if (defined ($attrs{'name'}))
    {
      $name = $attrs{'name'};
      $dgstmt .= " failgroup " . $name . " ";
    }

    @asmcmddisk_parser_disks = ();
  }
  elsif ($element eq 'dsk')
  {
    my ($string, $name, $size, $force, $clause);

    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        ($asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'dg' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'fg')
       )
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }
    push (@asmcmddisk_parser_state, $element);

    $clause = '';

    if (defined ($attrs{'string'}))
    {
      $string = $attrs{'string'};
      $clause .= " \'$string\'";
    }

    if (defined ($attrs{'name'}))
    {
      $name = $attrs{'name'};
      $clause .= " name $name";
    }

    if (defined ($attrs{'size'}))
    {
      $size = $attrs{'size'};
      $clause .= " size $size";
    }

    if (defined ($attrs{'force'}))
    {
      $force = $attrs{'force'};
      $clause .= " force";
    }

    push (@asmcmddisk_parser_disks, $clause);
  }
  elsif ($element eq 'a')
  {
    my ($string, $name, $value, $clause);
    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'dg')
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }
    push (@asmcmddisk_parser_state, $element);
    
    if (defined ($attrs{'name'}))
    {
      $name = $attrs{'name'};
    }
    if (defined ($attrs{'value'}))
    {
      $value = $attrs{'value'};
    }
    $clause = "\'$name\' = \'$value\'";
    push (@asmcmddisk_parser_attrs, $clause);
  }
  else
  {
    @eargs = ($element);
    asmcmdshare_error_msg(9390, \@eargs);
    $xml_error = 1;
    return;
  }
}


########
# NAME
#   asmcmddisk_mkdg_end
#
# DESCRIPTION
#   This function processes the end of xml tags in mkdg.
#
# PARAMETERS
#   expat   (IN) - expat parser object
#   element (IN) - tag element name
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_process_mkdg() calls this function.
########
sub asmcmddisk_mkdg_end
{
  my ($expat, $element) = @_;

  pop @asmcmddisk_parser_state;
 
  if ($element eq 'fg' || $element eq 'dg')
  {
    if ($element eq 'fg' && $#asmcmddisk_parser_disks == -1)
    { 
      my @eargs = ($element);
      asmcmdshare_error_msg(9398, \@eargs);
      $xml_error = 1;
      return; 
    }

    if (@asmcmddisk_parser_disks > 0)
    {
      $dgstmt .= " disk " . join(', ', @asmcmddisk_parser_disks);
      @asmcmddisk_parser_disks = ();
    }
  }

  if ($element eq 'dg')
  {
    if (@asmcmddisk_parser_attrs > 0)
    {
      $dgstmt .= " attribute " . join(', ', @asmcmddisk_parser_attrs);
      @asmcmddisk_parser_attrs = ();
    }
  }
}


########
# NAME
#   asmcmddisk_mkdg_char
#
# DESCRIPTION
#   This function processes the text value fo xml node in mkdg.
#
# PARAMETERS
#   expat   (IN)  - expat parser object
#   string  (IN)  - one line of text from text node
#
# RETURNS
#  Null.
#
# NOTES
#   Only asmcmddisk_process_mkdg() calls this function
########
sub asmcmddisk_mkdg_char
{
  my ($expat, $string) = @_ ;

  # in case of multi-line, this callback is called for each line,
  #append and collect all lines together
  if ( length (chomp($string)) > 0 )
  {
    $asmcmddisk_parser_text .= $string ;
  }
}

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

  my (%args);
  my ($qry, $ret);
  my ($parser, $file);
  my ($string_args) = '';
  my (@eargs);

  $xml_error = 0;

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

  # check for a configuration file
  if (@ARGV < 1)
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  if (-r $ARGV[0])
  {
    $file = $ARGV[0];
  }
  else
  {
    $string_args = join (" ", @ARGV);
  }

  @asmcmddisk_parser_state = ();
  $dgstmt = "";

  # We do not expect any text values for any nodes.
  $asmcmddisk_parser_text = "" ;

  # specify the handler callbacks
  $parser = XML::Parser->new(Handlers =>{Start => \&asmcmddisk_mkdg_start,
                                         End   => \&asmcmddisk_mkdg_end,
                                         Char  => \&asmcmddisk_mkdg_char,
                                        },
                             ErrorContext => 5
                            );

  eval {
    if (defined($file))
    {
      $parser->parsefile($file);
    }
    else
    {
      $parser->parse($string_args);
    }
  };
  if ($@)
  { 
    my (@msg, $err);
    $err = $@;
    @msg = split(/\n/, $err);
    if ($msg[$#msg] =~ m/Parser.pm/)
    {
      pop @msg;
    }

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

  # error out, if any of the nodes has text values.  simply print the text.
#  $asmcmddisk_parser_text = trim($asmcmddisk_parser_text);
  $asmcmddisk_parser_text =~ s/\s+//;

  if(length($asmcmddisk_parser_text) > 0 )
  {
    $xml_error = 1 ;
    @eargs = ($asmcmddisk_parser_text);

    asmcmdshare_error_msg(9395, \@eargs);
    return ;
  }

  if ($xml_error == 1)
  {
    return;
  }

  # Run SQL. #
  $ret = asmcmdshare_do_stmt($dbh, $dgstmt);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
}


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

  my (%args);
  my ($qry, $ret);
  my ($dgname);

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

  # get the diskgroup name
  if (!defined($ARGV[0]))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  $dgname = $ARGV[0];

  $qry = "ALTER DISKGROUP " . $dgname . " CHECK ";

  # repair the disk group
  if (defined($args{'repair'}))
  {
    $qry .= " REPAIR";
  }
  else
  {
    $qry .= " NOREPAIR";
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);

  if (defined ($ret))
  {
    print "Diskgroup altered.\n" ;
  }
  
}


########
# NAME
#   asmcmddisk_chdg_start
#
# DESCRIPTION
#   This function processes the start of xml tags in chdg.
#
# PARAMETERS
#   expat   (IN) - expat parser object
#   element (IN) - tag element name
#   attrs   (IN) - tag element attributes
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_process_chdg() calls this function.
########
#
#   transition map
#
#               TO
#           |chdg   |add    |drop   |rsz    |fg     |dsk    |
#   +-------+-------+-------+-------+-------+-------+-------+
#    chdg   |err    |       |       |       |err    |err    |
#   +-------+-------+-------+-------+-------+-------+-------+
#    add    |err    |err    |err    |err    |       |       |
# F +-------+-------+-------+-------+-------+-------+-------+
# R  drop   |err    |err    |err    |err    |       |       |
# O +-------+-------+-------+-------+-------+-------+-------+
# M  rsz    |err    |err    |err    |err    |       |       |
#   +-------+-------+-------+-------+-------+-------+-------+
#    fg     |err    |err    |err    |err    |err    |       |
#   +-------+-------+-------+-------+-------+-------+-------+
#    dsk    |err    |err    |err    |err    |err    |err    |
#   +-------+-------+-------+-------+-------+-------+-------+
#
########
sub asmcmddisk_chdg_start
{
  my ($expat, $element, %attrs) = @_;
  my (@eargs);

  if ($element eq 'chdg')
  {
    my ($dgname, $power);

    if ($#asmcmddisk_parser_state != -1)
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    if (!defined($attrs{'name'}))
    {
      @eargs = ('name');
      asmcmdshare_error_msg(9391, \@eargs);
      $xml_error = 1;
      return;
    }

    push (@asmcmddisk_parser_state, $element);

    $dgname  = $attrs{'name'};
    $dgstmt = 'alter diskgroup ' . $dgname . ' ';

    if (defined($attrs{'power'}))
    {
      $power = $attrs{'power'};
      $dgstmt .= "rebalance power " . $power . " ";
    }
  }
  elsif ($element eq 'add' || $element eq 'drop')
  {
    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'chdg')
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    $dgstmt .= ' ' . $element . ' ';

    push (@asmcmddisk_parser_state, $element);

    @asmcmddisk_parser_disks = ();
  }
  elsif ($element eq 'resize')
  {
    my ($size);

    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'chdg')
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    $dgstmt .= ' ' . $element . ' ';

    if (defined($attrs{'size'}))
    {
      $size = $attrs{'size'};
      $dgstmt .= " all size " . $size;
    }

    push (@asmcmddisk_parser_state, $element);

    @asmcmddisk_parser_disks = ();
  }
  elsif ($element eq 'fg')
  {
    # get failure group attributes
    %asmcmddisk_parser_fg=();

    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        ($asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'add' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'drop' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'resize')) 
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    if (!defined($attrs{'name'}))
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    $asmcmddisk_parser_fg{'name'}= $attrs{'name'};

    if (defined($attrs{'size'}))
    {
      $asmcmddisk_parser_fg{'size'}=$attrs{'size'};
    }

    if (defined($attrs{'force'}))
    {
      $asmcmddisk_parser_fg{'force'}=$attrs{'force'};
    }

    push (@asmcmddisk_parser_state, $element);
  }
  elsif ($element eq 'dsk')
  {
    # get attributes for a disk

    my (%dsk_attr);
    if (!defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state]) ||
        ($asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'add' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'drop' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'resize' &&
         $asmcmddisk_parser_state[$#asmcmddisk_parser_state] ne 'fg')
       )
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    if (defined($attrs{'string'}))
    {
      $dsk_attr{'string'} = $attrs{'string'};
    }

    if (defined($attrs{'name'}))
    {
      $dsk_attr{'name'} = $attrs{'name'};
    }

    if (defined($attrs{'size'}))
    {
      $dsk_attr{'size'} = $attrs{'size'};
    }

    if (defined($attrs{'force'}))
    {
      $dsk_attr{'force'} = $attrs{'force'};
    }
    push (@asmcmddisk_parser_state, $element);
    push (@asmcmddisk_parser_disks, \%dsk_attr);
  }
  else
  {
    @eargs = ($element);
    asmcmdshare_error_msg(9390, \@eargs);
    $xml_error = 1;
    return;
  }
}


########
# NAME
#   asmcmddisk_chdg_end
#
# DESCRIPTION
#   This function processes the end of xml tags in chdg.
#
# PARAMETERS
#   expat   (IN) - expat parser object
#   element (IN) - tag element name
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_process_chdg() calls this function.
########
sub asmcmddisk_chdg_end
{
  my ($expat, $element) = @_;
  my ($oper, $fgname);
  my (@eargs);

  # if element == dsk and father != fg, operate
  if ( $element eq 'dsk' && 
       defined($asmcmddisk_parser_state[$#asmcmddisk_parser_state - 1]) &&
       $asmcmddisk_parser_state[$#asmcmddisk_parser_state - 1] ne 'fg')
  {
    my ($dsk);

    $oper = $asmcmddisk_parser_state[$#asmcmddisk_parser_state - 1];

    # there is just one disk in the array
    $dsk = $asmcmddisk_parser_disks[0];

    # add operation
    if ($oper eq 'add')
    {
      $dgstmt .= " disk " . "\'" . $dsk->{'string'} . "\' ";

      if (defined($dsk->{'name'}))
      {
        $dgstmt .= " name " . $dsk->{'name'} . " ";
      }

      if (defined($dsk->{'size'}))
      {
        $dgstmt .= " size " . $dsk->{'size'} . " ";
      }

      if (defined($dsk->{'force'})&& $dsk->{'force'} eq 'true')
      {
        $dgstmt .= " force ";
      }
    }
    # drop operation
    elsif ($oper eq 'drop')
    {
      $dgstmt .= " disk " . $dsk->{'name'} . " ";

      if (defined($dsk->{'force'}))
      {
        if ($dsk->{'force'} eq 'true')
        {
          $dgstmt .= "force ";
        }
      }
    }
    # resize operation
    elsif ($oper eq 'resize')
    {
      $dgstmt .= " disk " . $dsk->{'name'} . " ";
      $dgstmt .= "size " . $dsk->{'size'} . " ";
    }

    @asmcmddisk_parser_disks = ();
  }

  # if element == fg, operate
  if ( $element eq 'fg')
  {
    my (@dsks) = ();
    my ($disk_string);

    $oper = $asmcmddisk_parser_state[$#asmcmddisk_parser_state - 1];
    $fgname = $asmcmddisk_parser_fg{'name'};
    if (!defined($oper) || !defined($fgname))
    {
      asmcmdshare_error_msg(9391, undef);
      $xml_error = 1;
      return;
    }

    # add operation
    if ($oper eq 'add')
    {
      $dgstmt .= " failgroup " . $fgname . " disk ";

      if($#asmcmddisk_parser_disks == -1)
      {
        @eargs = ($element);
	asmcmdshare_error_msg(9398, \@eargs);
        $xml_error = 1;
	return;
      }

      foreach (@asmcmddisk_parser_disks)
      {
        $disk_string = "\'" . $_->{'string'} . "\'";

        if (defined($_->{'name'}))
        {
          $disk_string .= " name " . $_->{'name'} . " ";
        }

        if (defined($_->{'size'}))
        {
          $disk_string .= " size " . $_->{'size'} . " ";
        }

        if (defined($_->{'force'}))
        {
          if ($_->{'force'} eq 'true')
          {
            $disk_string .= " force ";
          }
        }

        push (@dsks, $disk_string);
      }
      $dgstmt .= join (", ", @dsks) . " ";
    }
    # drop operation
    elsif($oper eq 'drop')
    {
      $dgstmt .= " disks in failgroup " . $fgname . " ";
      if (defined($asmcmddisk_parser_fg{'force'}))
      {
        if ( $asmcmddisk_parser_fg{'force'} eq 'true' )
        {
          $dgstmt .= "force ";
        }
      }
    }
    # resize operation
    elsif($oper eq 'resize')
    {
      $dgstmt .= " disks in failgroup " . $fgname;
      if (defined($asmcmddisk_parser_fg{'size'}))
      {
        $dgstmt .= " size " . $asmcmddisk_parser_fg{'size'} . " ";
      }
    }

    @asmcmddisk_parser_disks = ();
  }

  pop @asmcmddisk_parser_state;
}


########
# NAME
#   asmcmddisk_chdg_char
#
# DESCRIPTION
#   This function processes the text values
#
# PARAMETERS
#   expat    (IN)  - expat parser object
#   string   (IN)  - one line of the text node
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmddisk_prcess_chdg() calls this function.
########
sub asmcmddisk_chdg_char
{
  my  ($expat, $string) = @_ ;

  # This function is called for each line found the text mode.
  # append all the text
  $asmcmddisk_parser_text .= $string ;
}

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

  my (%args);
  my ($qry, $ret);
  my ($parser, $file);
  my ($string_args) = '';
  my (@eargs);
  $xml_error = 0;

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

  # check for a configuration file
  if (@ARGV < 1)
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  if (-r $ARGV[0])
  {
    $file = $ARGV[0];
  }
  else
  {
    $string_args = join (" ", @ARGV);
  }

  @asmcmddisk_parser_state = ();
  $dgstmt = "";
  $asmcmddisk_parser_text = "" ;

  # specify the handler callbacks
  $parser = XML::Parser->new(Handlers =>{Start => \&asmcmddisk_chdg_start,
                                         End   => \&asmcmddisk_chdg_end,
                                         Char  => \&asmcmddisk_chdg_char
                                        });

  eval{
    if (defined($file))
    {
      $parser->parsefile($file);
    }
    else
    {
      $parser->parse($string_args);
    }
  };
  if ($@)
  {
    @eargs = ($@);
    asmcmdshare_error_msg(9395, \@eargs);
    return;
  }
 
  # error out, if any of the nodes has text values.  Simply print the text.
  $asmcmddisk_parser_text =~ s/\s+// ;

  if(length($asmcmddisk_parser_text) > 0 )
  {
    @eargs = ($asmcmddisk_parser_text);
    asmcmdshare_error_msg(9395, \@eargs);
    $xml_error = 1 ;
  }

  if ($xml_error == 1)
  {
    return;
  }

  if (!defined($dgstmt) || $dgstmt eq '')
  {
    asmcmdshare_error_msg(9391, undef);
    return;
  }

  # Run SQL. #
  $ret = asmcmdshare_do_stmt($dbh, $dgstmt);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
 
  if (defined ($ret))
  {
    print "Diskgroup altered.\n" ;
  }
}


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

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

  # all disk groups?
  if (defined($args{'a'}))
  {
    push(@dgnames, "ALL");
  }
  else
  {
    # check for a disk group
    if (!defined($ARGV[0]))
    {
      asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
      return;
    }
    foreach (@ARGV)
    { 
      push (@dgnames, $_);
    }
  }
  $qry = "ALTER DISKGROUP " .  join(',', @dgnames) . " MOUNT ";

  # restricted option
  if (defined($args{'restrict'}))
  { 
    $qry .= " RESTRICTED";
  }

  # force option
  if (defined($args{'f'}))
  {
    $qry .= " FORCE";
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
}


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

  my (%args);
  my ($qry, $ret);
  my (@dgnames);

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

  # all disk groups?
  if (defined($args{'a'}))
  {
    push(@dgnames, "ALL");   
  }
  else
  {
    # check for a disk group
    if (!defined($ARGV[0]))
    {
      asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
      return;
    }
    foreach (@ARGV)
    { 
      push (@dgnames, $_);
    }
  }

  $qry = "ALTER DISKGROUP " . join(',', @dgnames) . " DISMOUNT ";

  # force option
  if (defined($args{'f'}))
  {
    $qry .= " FORCE";
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
}


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

  my (%args);
  my ($qry, $ret);
  my ($dgname, $fgname, $dname);
  my (@error);
  my ($sth, $row);

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

  # get disk group
  if (!defined($args{'G'}))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  $dgname = $args{'G'};

  # get failure group if defined
  if (defined $args{'F'})
  {
    $fgname = $args{'F'};
    tr/a-z/A-Z/ for $fgname;
  }

  # get disk if defined
  if (defined $args{'D'})
  {
    $dname  = $args{'D'};
    tr/a-z/A-Z/ for $dname;
  }

  if ( defined($fgname) and defined($dname) )
  {
    asmcmdshare_error_msg(9394, undef);
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  if (defined($dname))
  {
    $qry = "ALTER DISKGROUP " . $dgname ;
    $qry .= " ONLINE DISK " . $dname;
  }
  elsif (defined($fgname))
  {
    $qry = "ALTER DISKGROUP " . $dgname ;
    $qry .= " ONLINE DISKS IN FAILGROUP " . $fgname;
  }
  else
  {
    $qry = "ALTER DISKGROUP " . $dgname ;
    $qry .= " ONLINE ALL ";
  }

  # if wait argument is specified
  if (defined($args{'w'}))
  {
    $qry .= " WAIT";
  }

  if (!defined($qry))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
  
  if (defined ($ret))
  {
    print "Diskgroup altered.\n" ;
  }
    
}


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

  my (%args);
  my ($qry, $ret);
  my ($dgname, $fgname, $dname);
  my (@error);
  my ($sth, $row);
  my ($timeout);

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

  # get disk group
  if (!defined($args{'G'}))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  $dgname = $args{'G'};

  # always convert parameters to uppercase
  # get failure group if defined
  if (defined $args{'F'})
  {
    $fgname = $args{'F'};
    tr/a-z/A-Z/ for $fgname;
  }

  # get disk if defined
  if (defined $args{'D'})
  {
    $dname  = $args{'D'};
    tr/a-z/A-Z/ for $dname;
  }

  if ( defined($fgname) and defined($dname) )
  {
    asmcmdshare_error_msg(9394, undef);
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  if (defined($dname))
  {
    $qry = "ALTER DISKGROUP " . $dgname ;
    $qry .= " OFFLINE DISK " . $dname;
  }
  elsif (defined($fgname))
  {
    $qry = "ALTER DISKGROUP " . $dgname ;
    $qry .= " OFFLINE DISKS IN FAILGROUP " . $fgname;
  }

  # timeout
  if (defined($args{'t'}))
  {
    
    #$ check format of timeout
    $timeout = $args{'t'};

    $qry .= " DROP AFTER $timeout";
  }

  if (!defined($qry))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);
  
  if (defined ($ret))
  {
    print "Diskgroup altered.\n" ;
  }
    
}


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

  my (%args);
  my ($qry, $ret);
  my ($dgname, $fgname, $dname);
  my (@error);
  my ($power, $wait);
  my ($dgnumber);      #diskgroup number of the disk for which rebal is issued
  my ($rbalOngoing);   #return value, Y or N if rbal is ongoing for that diskgroup

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

  # check for a disk group
  if (!defined($ARGV[0]))
  {
    asmcmddisk_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  $dgname = $ARGV[0];

  # power option
  $power = $args{'power'} if defined $args{'power'};

  # wait option
  $wait  = $args{'w'} if defined $args{'w'};

  $qry = "ALTER DISKGROUP " . $dgname . " REBALANCE";

  $qry .= " POWER " . $power if (defined($power));

  $qry .= " WAIT" if (defined($wait));

  $ret = asmcmdshare_do_stmt($dbh, $qry);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($ret);

  if (defined ($ret))
  {
    $dgnumber = asmcmddisk_get_gnum_from_gname($dbh, $dgname); 
    $rbalOngoing = asmcmdbase::asmcmdbase_is_rbal ($dbh,$dgnumber);

    if ($rbalOngoing eq 'Y')
    {
      print "Rebal on progress.\n";
    }
 
    elsif ($rbalOngoing eq 'N')
    {
	print "Rebal completed.\n";
    }
  }

}



# NAME
#   asmcmddisk_get_fname_from_fnum
#
# DESCRIPTION
#   This routine constructs the SQL used to get the file name given the file number
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gnum   (IN) - the diskgroup number
#   fnum   (IN) - the file number
#
#
# RETURNS
#   The file name.
########
sub asmcmddisk_get_fname_from_fnum
{
  my ($dbh, $gnum, $fnum) = @_;

  my ($sth, $qry, $row);
  my ($fname);                 # Disk number return value; see RETURNS above. #

  # Get disk number from disk name.
  $qry = 'select name from v$asm_alias where file_number=?' .
    ' and system_created=\'N\' and group_number=?';

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

  return $fname;
}

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

  my ($sth, $qry, $row);
  my ($dnum);                 # Disk number return value; see RETURNS above. #

  # Get disk number from disk name.
  $qry = 'select disk_number from v$asm_disk_stat where name=\'' .
    uc($dname) . '\' and group_number=?';

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

  return $dnum;
}

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

  my ($sth, $qry, $row);
  my ($gnum);                 # Disk group number return value. #

  # Remove the leading "+" from the disk group name, if any 
  $gname = substr($gname, 1) if substr($gname, 0, 1) eq "+";

  # Get disk group number from disk group name.
  $qry = 'select group_number from v$asm_diskgroup_stat where name=\'' .
    uc($gname) . '\'';

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

  return $gnum;
}

########
# NAME
#   asmcmddisk_get_dg_ausize
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the AU size of the 
#   disk group with group number <gnum>.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gnum   (IN) - the disk group number
#
# RETURNS
#   The AU size is a string if the disk is mounted in a dg; undefined 
#   otherwise.
########
sub asmcmddisk_get_dg_ausize 
{
  my ($dbh, $gnum) = @_;
  my ($sth, $qry, $row);
  my ($ausize);
  my (@eargs);

  # Get AU size.
  $qry = 'select allocation_unit_size from v$asm_diskgroup_stat ' .
    'where group_number=?';

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

  # make sure we get/return valid values
  @eargs = ($gnum, $gnum, $ausize);
  asmcmdshare_assert((defined($ausize) && $ausize > 0), \@eargs);

  return $ausize;
}

########
# NAME
#   asmcmddisk_get_dsk_secsize
#
# DESCRIPTION
#   This routine constructs the SQL used to fetch the sector size of the 
#   disk group with group number <gnum>.
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   gnum   (IN) - the disk group number
#
# RETURNS
#   The AU size is a string if the disk is mounted in a dg; undefined 
#   otherwise.
########
sub asmcmddisk_get_dsk_secsize 
{
  my ($dbh, $gnum, $dnum) = @_;
  my ($sth, $qry, $row);
  my ($secsize);
  my (@eargs);

  # Get sector size.
  $qry = 'select x$kfkid.blksz_kfkid as blksz from x$kfkid, x$kfdsk ' .
    'where x$kfkid.idptr_kfkid=x$kfdsk.kfkid_kfdsk and ' .
    'x$kfdsk.grpnum_kfdsk = ?' .
    ' and x$kfdsk.number_kfdsk = ?';

  $sth = asmcmdshare_do_prepare($dbh, $qry);
  $sth->bind_param(1,$gnum);
  $sth->bind_param(2,$dnum);
  asmcmdshare_do_execute($sth);
  $row = asmcmdshare_fetch($sth);
  $secsize = $row->{'BLKSZ'};
  asmcmdshare_finish($sth);

  # make sure we get/return valid values
  @eargs = ($gnum, $dnum, $secsize);
  asmcmdshare_assert((defined($secsize) && $secsize > 0), \@eargs);

  return $secsize;
}

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

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

  # If Oracle Database version is less than 10gR2, then always do 
  # discovery, because the *_stat views are not available in 10gR1.
  if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
                               $ASMCMDGLOBAL_VER_10gR2) < 0 )
  {
    $discovery = 1;
  }
  if ($missing)         # for missing disks we must use gv$asm_disk
  {                     # and exclude member disks 
    push (@from, 'gv$asm_disk');
    push (@where, "(gv\$asm_disk.header_status = 'CANDIDATE' 
                    or gv\$asm_disk.header_status = 'PROVISIONED'
                    or gv\$asm_disk.header_status = 'FORMER')");
  }
  elsif ($discovery && $global)
  {
    push (@from, 'gv$asm_disk');
  }
  elsif ($discovery && !$global)
  {
    push(@from, 'v$asm_disk');
  }
  elsif (!$discovery && $global)
  {
    push (@from, 'gv$asm_disk_stat');
  }
  else
  {
    push (@from, 'v$asm_disk_stat');
  }
 
  if (defined($group))
  {
    push (@where, 'group_number=' . $group);
  }  

  if (defined($disk_pattern))
  {
    push (@where, 'path like \'' . $disk_pattern . '\'');
  }

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

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

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

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

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

    push (@dsk_list, \%dsk_info);

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

  asmcmdshare_finish($sth);

  return (@dsk_list);
}

########
# NAME
#   asmcmddisk_get_inst
#
# DESCRIPTION
#   This function queries the gv$instance table to retrieve
#   a list of active instances
#
# PARAMETERS
#   dbh    (IN) - initialized database handle, must be non-null.
#   inst_info (OUT) - hash of active instances
#
# RETURNS
#   A hash with one entry per instance with the instance name as key
#
# NOTES
########
sub asmcmddisk_get_inst
{
  my ($dbh) = @_;                     # db handle
  my ($sth, $row);
  my (@what, @from);                  # contents of select stmt
  my (%inst_info);                    # Hash of instances

  push (@from, 'gv$instance');
  push (@what, 'inst_id');

  $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from);
  asmcmdshare_trace(1, $DBI::errstr, 'y', 'y') unless defined ($sth);
 
  # Fetch results, store each row in hash 
  while (defined ($row = asmcmdshare_fetch($sth))) 
  {
    $inst_info{$row->{'INST_ID'}} = $row->{'INST_ID'}
      if (defined ($row->{'INST_ID'}));      
  }

  asmcmdshare_finish($sth);

  return (%inst_info);
}

########
# NAME
#   asmcmddisk_process_help
#
# DESCRIPTION
#   This function is the help function for the ASMCMDDISK module.
#
# PARAMETERS
#   command     (IN) - display the help message for this command.
#
# RETURNS
#   1 if command found; 0 otherwise.
########
sub asmcmddisk_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 (asmcmddisk_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
#   asmcmddisk_is_cmd
#
# DESCRIPTION
#   This routine checks if a user-entered command is one of the known
#   ASMCMD internal commands that belong to the ASMCMDDISK module.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   True if $arg is one of the known commands, false otherwise.
########
sub asmcmddisk_is_cmd 
{
  my ($arg) = shift;

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


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

  return defined ($asmcmddisk_cmds{ $arg }) &&
         defined ($asmcmddisk_cmds{ $arg }{ wildcard });
}


########
# NAME
#   asmcmddisk_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 asmcmddisk module currently supports only lsdsk as a command that
#   can run without an ASM instance.
########
sub asmcmddisk_is_no_instance_cmd 
{
  my ($arg) = shift;

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

########
# NAME
#   asmcmddisk_parse_int_args
#
# DESCRIPTION
#   This routine parses the arguments for flag options for ASMCMDDISK 
#   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 ASMCMDDISK internal command.
########
sub asmcmddisk_parse_int_args 
{
  my ($cmd, $args_ref) = @_;
  my (@string);
  my ($key);
   #build the list of options to parse using GetOptions
  if($asmcmddisk_cmds{ $cmd }{ flags })
  { 
    foreach $key(keys %{$asmcmddisk_cmds{ $cmd }{ flags }})
    {
      push(@string, $key);
    }
  }

  #include deprecated options if any
  if($asmcmdglobal_deprecated_options{ $cmd })
  {
    foreach my $key(keys %{$asmcmdglobal_deprecated_options{ $cmd }})
    {
      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. #
    asmcmddisk_syntax_error($cmd);
    return undef;
  }
  return 0;
}

########
# NAME
#   asmcmddisk_syntax_error
#
# DESCRIPTION
#   This function prints the correct syntax for a command to STDERR, used 
#   when there is a syntax error.  This function is responsible for 
#   only ASMCMDDISK commands.
#
# PARAMETERS
#   cmd   (IN) - user-entered command name string.
#
# RETURNS
#   1 if the command belongs to this module; 0 if command not found.
#
# NOTES
#   These errors are user-errors and not internal errors.  They are of type
#   record, not signal.  
# 
#   N.B. Functions in this module can call this function directly, without
#   calling the asmcmdshare::asmcmdshare_syntax_error equivalent.  The
#   latter is used only by the asmcmdcore module.
########
sub asmcmddisk_syntax_error 
{
  my ($cmd) = shift;
  my ($cmd_syntax);                               # Correct syntax for $cmd. #
  my ($succ) = 0;

  #display syntax only for commands in this module.
  if (asmcmddisk_is_cmd($cmd))
  {
    $cmd_syntax = asmcmdshare_get_help_syntax($cmd);  # Get syntax for $cmd. #
    $cmd_syntax = asmcmdshare_trim_str ($cmd_syntax);   # Trim blank spaces #

    if (defined ($cmd_syntax))
    {
      print STDERR 'usage: ' . $cmd_syntax . "\n";
      print STDERR 'help:  help ' . $cmd . "\n";
      $succ = 1;
    }

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

  return $succ;
}


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