# Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      asmcmdambr - ASM CoMmanD line interface ASM Metadata Backup and 
#                   Restore module
#
#    DESCRIPTION
#      This module implements ASM Metadata Backup and Restore Utility. 
#
#    NOTES
#      usage: asmcmdcore [-p] [command]
#
#    MODIFIED     (MM/DD/YY)
#    manuegar      08/19/13 - Backport manuegar_bug-13951456 from main
#    pvenkatr      03/05/13 - LRG 7215464 - enclose template names within
#                             double quotes.
#    moreddy       01/13/11 - Backport moreddy_bug-8667038 from main
#    pvenkatr      03/31/10 - Syntax, description, example - all from xml
#    moreddy       03/22/10 - Adding more tracing
#    canuprem      02/11/10 - bug 9327020
#    moreddy       01/18/10 - Adding trace messages
#    pvenkatr      09/03/09 - Help message from xml file
#    sanselva      04/06/09 - ASMCMD long options and consistency
#    heyuen        10/30/08 - lrg-3668050
#    heyuen        10/14/08 - use dynamic modules
#    rlong         08/07/08 - 
#    atsukerm      07/17/08 - rename sage to cell
#    nchoudhu      07/17/08 - xbranchmerge sage from pt.oos2
#    heyuen        03/27/08 - Backport heyuen_bug-6487591 from main
#    mpopeang      08/07/08 - bug7259156: backup/restore messages
#    heyuen        07/28/08 - use command properties array
#    nchoudhu      07/21/08 - xbranchmerge sage from 11.1.0.7
#    heyuen        04/15/08 - reorder help messages
#    heyuen        02/01/08 - enable restore dirs inside system dirs
#    heyuen        11/28/07 - add sage_only attribute for diskgroup restore
#    heyuen        09/12/07 - change error msgs to asmcmdshare
#    heyuen        05/25/07 - add return codes for errors
#    pbagal        04/13/07 - Add ASMCMD comment in all SQL
#    heyuen        03/14/07 - add backup of v$asm_attribute
#    hqian         03/09/07 - fix identation and comments
#    hqian         03/02/07 - fix md_restore flag -l and messages
#    hqian         07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks 
#    msharang      04/20/06 - Add creation of SQL file during restore 
#    msharang      04/20/06 - Override options must match diskgroups 
#                             discovered 
#    msharang      04/20/06 - Fix restore for multiple diskgroups 
#    msharang      04/06/06 - Better error checking 
#    msharang      03/28/06 - Creation
#
#############################################################################
#
############################ Functions List #################################
#
#############################################################################

package asmcmdambr;
require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(asmcmdambr_init
                 asmcmdambr_process_cmd
                 asmcmdambr_process_help
                 );

use strict;
use Cwd;
use Getopt::Long qw(:config no_ignore_case bundling);
use Data::Dumper;
use IO::Handle;
use asmcmdglobal;
use asmcmdshare;

use List::Util qw[min max];

####################### ASMCMDAMBR Global Constants ######################

our (%asmcmdambr_cmds) = ('md_backup'  => {no_instance => 'True',
                                           flags       =>  {'G=s'=>'diskGroup'}
                                                          },
                          'md_restore' => {no_instance => 'True',
                                           flags       =>  {'silent'=>
                                                           'ignoreErrors',
                                                            'full'=>
                                                           'fullRestore',
                                                            'nodg'=>
                                                           'metadataRestore',
                                                            'newdg'=>
                                                         'newdgMetadataRestore',
                                                            'S=s'=>
                                                           'sqlScriptFile',
                                                            'o=s'=>
                                                           'oldDiskGroup',
                                                            'G=s'=>'diskGroup'}
                                                          }
 
                         );

####################### ASMCMDAMBR Global Variables ######################
our (%supported_types) = ();

#needed to finish the file with a non-negative assignment
our $true_dummy = 1;


sub is_asmcmd
{
  return 1;
}

########
# NAME
#   asmcmdambr_init
#
# DESCRIPTION
#   This function initializes the asmcmdambr 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, \&asmcmdambr_process_cmd);
  push (@asmcmdglobal_help_callbacks, \&asmcmdambr_process_help);
  push (@asmcmdglobal_command_list_callbacks, \&asmcmdambr_get_asmcmd_cmds);
  push (@asmcmdglobal_is_command_callbacks, \&asmcmdambr_is_cmd);
  push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmdambr_is_wildcard_cmd);
  push (@asmcmdglobal_syntax_error_callbacks, \&asmcmdambr_syntax_error);
  push (@asmcmdglobal_no_instance_callbacks, \&asmcmdambr_is_no_instance_cmd);
  %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdambr_cmds);

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

########
# NAME
#   asmcmdambr_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 asmcmdambr module; 0 if not.
#
# NOTES
#   Only asmcmdcore_shell() calls this routine.
########
sub asmcmdambr_process_cmd 
{
  my ($dbh) = @_;
  my ($succ) = 0;

  # Get current command from global value, which is set by 
  # asmcmdambr_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 ASMCMDAMBR command.
  my (%cmdhash) = ( md_backup      => \&asmcmdambr_process_backup ,
                    md_restore     => \&asmcmdambr_process_restore );

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

  return $succ;
}

########
# NAME
#   asmcmdambr_process_backup
#
# DESCRIPTION
#   This function performs AMBR backup operation. It connects to ASM instance
#   and gathers enough information about the diskgroups we are interested
#   in, creates an intermediate file, that stores this information to be
#   processed later by restore operation.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdambr_process_cmd() calls this function.
########
sub asmcmdambr_process_backup
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (%norm);      # See asmcmdshare_normalize_path() return value comments. #
  my (@eargs);                                   # Array of error arguments. #
  my ($ret);                     # asmcmdambr_parse_int_args() return value. #
  my ($path);                            # Create directory under this path. #
  my ($sth, $qry, $row);                              # SQL query statement. #
  my (@diskgroup_names);                 # Name of diskgroup to be backed up #
  my ($dgnamesstr);            # Diskgroup names string as specified by user #
  my (@diskgroup_set);        # Top level hash table that stores all dg info #
  my ($i);                                                        # Iterator #
  my ($curpos);                           # 
  my ($FILE);                            # File handle for intermediate file #
  my ($intermediate_file_name); 
  my ($max_key);
  my ($dgname);                                     # current diskgroup name #
  my ($stmt);                                            # stmt for querying #

  # Get the supported files in ASM
  $stmt = 'select description from x$ksfdftyp';
  $ret = asmcmdshare_do_select($dbh, $stmt);
  if (!defined($ret))
  {
    (@eargs) = ($DBI::errstr);
    asmcmdshare_error_msg(9352, \@eargs);
    return;
  }

  while (defined ($row = asmcmdshare_fetch($sth)))
  {
    $supported_types{uc($row->{'DESCRIPTION'})} = 1;
  }
  asmcmdshare_finish($sth);

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

  #Set the correct options if deprecated options were used and print WARNING.
  asmcmdshare_handle_deprecation($asmcmdglobal_hash{'cmd'},\%args);
 
  # Check if number of non-option parameters are correct.
  if (@ARGV != 1)
  {
    asmcmdambr_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
  # -b options is deprecated as this argument is mandatory bug#6960868
  $intermediate_file_name = shift (@ARGV);

  # Store the information fetched in intermediate file to be
  # used later as part of AMBR restore 
  # We open using mode '>' means open for write only, and clobber existing file
  # so test if a file already exists
  if ( -e $intermediate_file_name)
  {
    (@eargs) = ($intermediate_file_name);
    asmcmdshare_error_msg(9357, \@eargs);
    return;
  }

  if (defined($args{'G'}))
  {
    # User has specified one or more diskgroups using -g option.
    # We will perform backup of only those diskgroups. 

    # Process the ',' separated list of diskgroup names
    $dgnamesstr = uc($args{'G'});
    $dgnamesstr =~ s/\s+//g; # stripe off any white spaces 
    @diskgroup_names = split /,/, $dgnamesstr; # delimiter is ','
    foreach (@diskgroup_names)
    {
      print "Disk group metadata to be backed up: $_\n";
    }
  }
  else
  {
    # By default backup all diskgroups mounted by this instance
    $qry = 'select NAME from v$asm_diskgroup where state=\'MOUNTED\'';
    $sth = asmcmdshare_do_select($dbh, $qry);

    while (defined ($row = asmcmdshare_fetch($sth)))
    {
      push (@diskgroup_names, uc($row->{'NAME'}));
    }
    asmcmdshare_finish($sth);

    if (!@diskgroup_names)
    {
      # No mounted diskgroups found, nothing can be done after this,
      # print error and return
      asmcmdshare_error_msg(9351, undef);
      return;
    }

    foreach (@diskgroup_names)
    {
      print "Disk group metadata to be backed up: $_\n";
    }
  } 

  # Now that we know what diskgroups to work on, define data structure that 
  # will hold information about the diskgroups, then execute SQL commands
  # to gather information about the diskgroups.  
  foreach $dgname (@diskgroup_names)
  {
    my ($current_diskgroup_number);
    my (%diskgroup_info);                            # Diskgroup info record #
    my (%disks_info);                                    # Disks info record #
    my (%disks_info_array);                        # Disks info record array #
    my (%attribute_info_array);                # Attribute info record array #
    my (%alias_info);                                    # Alias info record #
    my (%alias_info_array);                        # Alias info record array #
    my ($alias_info_array_length);       # Length of Alias info record array #
    my (%template_info);                              # Template info record #
    my (%template_info_array);                  # Template info record array #
    my (%diskgroup_record);        # Record to store information for this dg # 
    my ($iter1);
    my ($iter2);
    my ($temp);
    my (%to_delete);

    # Fetch diskgroup information
    $qry = 'select GROUP_NUMBER, TYPE, COMPATIBILITY, DATABASE_COMPATIBILITY,
            ALLOCATION_UNIT_SIZE, STATE from v$asm_diskgroup WHERE 
            NAME=\'' . $dgname . '\'';
    $sth = asmcmdshare_do_select($dbh, $qry);
    $row = asmcmdshare_fetch($sth);

    if (!defined($row))
    {
      # Could not find any row for current diskgroup, maybe user specified
      # incorrect diskgroup name, print error and fail command; syntax error.
      (@eargs) = ($dgname);
      asmcmdshare_error_msg(9349, \@eargs);
      return;
    }
    asmcmdshare_finish($sth);

    if (uc($row->{'STATE'}) eq 'DISMOUNTED')
    {
      # The diskgroup is dismounted, print error message and fail command;
      # syntax error.
      (@eargs) = ($dgname);
      asmcmdshare_error_msg(9350, \@eargs);
      return;
    }

    $current_diskgroup_number = $row->{'GROUP_NUMBER'};

    %diskgroup_info = (
      DGNAME => $dgname,
      DGTYPE => $row->{'TYPE'},
      DGTORESTORE => 0,
      DGCOMPAT => $row->{'COMPATIBILITY'},
      DGDBCOMPAT => $row->{'DATABASE_COMPATIBILITY'},
      DGAUSZ => $row->{'ALLOCATION_UNIT_SIZE'},
    );

    # Fetch Disk information
    # We fetch information for all disks, not just MEMBER disks, 
    # which means during restore time, we will try to create a diskgroup 
    # with all those disks. If user wants to do some filtering, he always
    # has the option of creating an output SQL file as part of restore
    # and editing it.
    # Should we look only for MEMBER disks ??
    $qry = 'select d.NAME, d.PATH, d.TOTAL_MB, d.FAILGROUP 
            from v$asm_disk d, v$asm_diskgroup g 
            where g.GROUP_NUMBER = d.GROUP_NUMBER 
            and g.NAME=\'' .$dgname . '\'';
    $sth = asmcmdshare_do_select($dbh, $qry);

    # We group disks based on their failgroups, so we have a complex
    # structure which has (1) A level 3 Hash table for all disks 
    # (disks_info_array) each entry of which is (2) a level 2 Hash table
    # of failgroups (disks_in_failgroup) each entry of which is (3)
    # a level 1 hash table per disk that has the v$asm_disk column names
    # as its keys and stores the corresponding column values.
    while (defined ($row = asmcmdshare_fetch($sth)))
    {
      my (%disks_in_failgroup_table);
      my ($disks_in_failgroup); #reference
 
      if (defined($disks_info_array{$row->{'FAILGROUP'}}))
      {
        $disks_in_failgroup = $disks_info_array{$row->{'FAILGROUP'}};
      }
      else
      {
        $disks_in_failgroup = \%disks_in_failgroup_table;
      }
       
      # One table per disk
      $disks_info{'DGNAME'} = $dgname;
      $disks_info{'NAME'} = $row->{'NAME'};  
      $disks_info{'PATH'} = $row->{'PATH'};
      $disks_info{'TOTAL_MB'} = $row->{'TOTAL_MB'};
      $disks_info{'FAILGROUP'} = $row->{'FAILGROUP'};

      # One table per failgroup
      $disks_in_failgroup->{$row->{'NAME'}} = { %disks_info };

      # One table per diskgroup for all failgroups in it
      $disks_info_array{$row->{'FAILGROUP'}} = { %{$disks_in_failgroup} };
    }# end while #
    asmcmdshare_finish($sth);

    # Fetch attribute information
    # Just for ASM diskgroup compatibility versions 11.1 or newer
    if ( asmcmdshare_version_cmp($diskgroup_info{'DGCOMPAT'}, 
                                 "11.1.0.0.0") >= 0 )
    {

      $qry = 'select a.GROUP_KFENV, a.NAME_KFENV as NAME, a.VALUE_KFENV
              as VALUE, decode(bitand(a.flags_kfenv, 2), 2, \'Y\', \'N\')
              as F from x$kfenv a, v$asm_diskgroup g where
              g.GROUP_NUMBER = a.GROUP_KFENV and 
              g.name =\'' .$dgname .'\'';
 
      $sth = asmcmdshare_do_select($dbh, $qry);

      while (defined($row = asmcmdshare_fetch($sth)))
      {
        if($row->{'F'} ne 'Y')
        {
          $attribute_info_array{ uc($row->{'NAME'}) } = $row->{'VALUE'};
        }
      } # end while #
      asmcmdshare_finish($sth);
    }

    # Fetch alias information
    # We are only interested in user created aliase directories
    $qry = 'select a.GROUP_NUMBER, a.NAME, a.REFERENCE_INDEX
            from v$asm_alias a, v$asm_diskgroup g 
            where g.GROUP_NUMBER = a.GROUP_NUMBER and 
            a.ALIAS_DIRECTORY=\'Y\' 
            and g.NAME=\'' .$dgname . '\'';
    $sth = asmcmdshare_do_select($dbh, $qry);

    $curpos = 0;

    # First pass, store only alias name temporarily
    while (defined ($row = asmcmdshare_fetch($sth)))
    {
      $alias_info{'DGNAME'} = $dgname;
      $alias_info{'ALIASNAME'} = $row->{'NAME'};
      $alias_info{'REFERENCE_INDEX'} = $row->{'REFERENCE_INDEX'};
      $alias_info{'LEVEL'} = 0;  # To be used for sorting entries later
      $alias_info_array{$curpos} = { %alias_info };

      $curpos++;
    } # end while #
    asmcmdshare_finish($sth);

    $alias_info_array_length = $curpos;

    # We need to build full path for the alias directory in order to be 
    # able to re-create it at a later point.
    foreach $curpos (keys %alias_info_array)
    {
      my ($current_alias_name) = $alias_info_array{$curpos}{'ALIASNAME'};
      my ($current_reference_index) = 
          $alias_info_array{$curpos}{'REFERENCE_INDEX'};
      my ($current_parent_index);
      my ($current_alias_path);
      my ($level);

      $qry = 'select PARENT_INDEX from v$asm_alias 
              where group_number=? ' .
              ' and REFERENCE_INDEX=?' .
              ' and name=\''. $current_alias_name . '\'';

      $sth = asmcmdshare_do_prepare($dbh, $qry);
      $sth->bind_param(1,$current_diskgroup_number);
      $sth->bind_param(2,$current_reference_index);
      asmcmdshare_do_execute($sth);
      $row = asmcmdshare_fetch($sth);
      $current_parent_index = $row->{'PARENT_INDEX'};
      $current_alias_path = $current_alias_name;
      # Build full path name for the alias directory starting from alias
      # name and working upwards towards diskgroup name 
      $level = 0;
      for (;;)
      {
        if ($current_parent_index == ($current_diskgroup_number << 24))
        {
          # We have reached diskgroup level, prepend diskgroup name
          # to the path and break out of loop
          last;
        }

        $qry = 'select NAME, PARENT_INDEX from v$asm_alias 
                where GROUP_NUMBER= ' . $current_diskgroup_number .
                ' and REFERENCE_INDEX=' . $current_parent_index;
        $sth = asmcmdshare_do_select($dbh, $qry);
        $row = asmcmdshare_fetch($sth);
        $current_alias_path = 
          $row->{'NAME'} . '/' . $current_alias_path;
        $current_parent_index = $row->{'PARENT_INDEX'};
        $level++;
      } # end for #
      asmcmdshare_finish($sth);

      print "Current alias directory path: $current_alias_path\n";
      $alias_info_array{$curpos}{'ALIASNAME'} = $current_alias_path;
      $alias_info_array{$curpos}{'LEVEL'} = $level;
    }

    #discard the directories of filetype names
    #select the entries to delete
    foreach $curpos (keys %alias_info_array)
    {
      my ($path, @path_arr);

      $path = $alias_info_array{$curpos}{'ALIASNAME'} . "\n";
      $path =~ s/\s+$//;

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

      if ($#path_arr == 1)
      {
        if (defined($supported_types{$path_arr[1]}))
        {
          $to_delete{$curpos} = 1;
        }
      }
    }

    $max_key = $alias_info_array_length;
    #delete the records
    foreach (keys %to_delete)
    {
      delete ($alias_info_array{$_});
      $alias_info_array_length = $alias_info_array_length - 1;
    }

    # coalesce the alias_info_array, filling the holes with existing entries
    for ($iter1 = 1; $iter1 < $max_key; $iter1++)
    {
      if (defined($alias_info_array{$iter1}))
      {
        next;
      }
      for ($iter2 = $iter1+1; 
           !defined($alias_info_array{$iter2}) && $iter2 < $max_key;
           $iter2++){};

      if (defined($alias_info_array{$iter2}))
      {
        $alias_info_array{$iter1} = $alias_info_array{$iter2};
      }
      delete($alias_info_array{$iter2});
    }

    # Sort alias info array based on level, we do this because restore
    # operation needs to restore alias directory based on level since 
    # parent directory needs to be restored before any of its children
    # are restored.
    for ($iter1 = 0; $iter1 < $alias_info_array_length; $iter1++)
    {
      for ($iter2 = 0; $iter2 < $alias_info_array_length - 1; $iter2++)
      {
        if ($alias_info_array{$iter2}{'LEVEL'} > 
            $alias_info_array{$iter2+1}{'LEVEL'} )
        {
          $temp = $alias_info_array{$iter2};
          $alias_info_array{$iter2} = 
            $alias_info_array{$iter2+1};
          $alias_info_array{$iter2+1} = $temp;
        }
      }#end fop iter2
    }#end for iter1

    # Fetch template information
    $qry = 'select g.GROUP_NUMBER, t.NAME, t.REDUNDANCY, t.STRIPE, t.SYSTEM 
            from v$asm_template t, v$asm_diskgroup g where 
            g.GROUP_NUMBER = t.GROUP_NUMBER and 
            g.NAME=\'' .$dgname . '\'';
    $sth = asmcmdshare_do_select($dbh, $qry);

    $curpos = 0;
    while (defined ($row = asmcmdshare_fetch($sth)))
    {
      $template_info{'DGNAME'} = $dgname;
      $template_info{'TEMPNAME'} = $row->{'NAME'};
      $template_info{'REDUNDANCY'} = $row->{'REDUNDANCY'};
      $template_info{'STRIPE'} = $row->{'STRIPE'};
      $template_info{'SYSTEM'} = $row->{'SYSTEM'};

      $template_info_array{$curpos} = { %template_info };
      $curpos++;
    } #end while #
    asmcmdshare_finish($sth);

    # Create final record for this diskgroup
    # If the instance version is greater than 11.1, then it has Attributes
    # that means that attribute_info_array has entries
    if ( keys(%attribute_info_array) )
    {
      %diskgroup_record = (
                           DGINFO => { %diskgroup_info },
                           DISKSINFO => { %disks_info_array },
                           ATTRINFO => { %attribute_info_array },
                           ALIASINFO => { %alias_info_array },
                           TEMPLATEINFO => { %template_info_array },
                           );
    }
    else
    {
      %diskgroup_record = (
                           DGINFO => { %diskgroup_info },
                           DISKSINFO => { %disks_info_array },
                           ALIASINFO => { %alias_info_array },
                           TEMPLATEINFO => { %template_info_array },
                           );
    }

    # Store this record in the array containing records for all diskgroups
    push (@diskgroup_set, \%diskgroup_record);
  } # end for #

  # We untaint/tell perl to trust the user specified intermediate file name 
  # by matching it with "match all" regular expression and resetting the 
  # variable to the match. This is not a safe way to untaint outside data 
  # but we already checked that the intermediate file specified by user does not
  # already exist, thus making sure that we will not accidently overwrite 
  # an existing file/directory, hence it is safe to do so at this point.
  ($intermediate_file_name) = $intermediate_file_name =~ m/(.*)/;

  if (!open (FILE, ">", $intermediate_file_name))
  {
    @eargs = ($intermediate_file_name, $!);
    asmcmdshare_error_msg(9345, \@eargs);
    return; 
  }

  # Write string version of diskgroup data to intermediate file 
  print FILE Data::Dumper->Dump([\@diskgroup_set], ['*diskgroup_set']);
  close (FILE);

  return;
}

########
# NAME
#   asmcmdambr_process_restore
#
# DESCRIPTION
#   This function performs AMBR restore operation. It reads and interprets 
#   the input restore file and version of the diskgroup to be restored, 
#   and then for all diskgroups to be restored, it creates appropriate 
#   restore commands depending on the version.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Only asmcmdambr_process_cmd() can call this routine.
########
sub asmcmdambr_process_restore
{
  my ($dbh) = shift;

  my (%args);                             # Argument hash used by getopts(). #
  my (@eargs);                                   # Array of error arguments. #
  my ($ret);                     # asmcmdambr_parse_int_args() return value. #
  my ($qry);                                          # SQL query statement. #
  my ($stmt);                                                 # SQL command. #
  my ($defreadmode);                                # Default file read mode #
  my (@diskgroup_names);                  # Name of diskgroup to be restored #
  my ($dgnamesstr);            # Diskgroup names string as specified by user # 
  my (@diskgroup_set);      # Record to store information for this diskgroup #
  my ($i);                                                        # Iterator #
  my ($FILE);                            # File handle for intermediate file #
  my ($SQLFILE);                         # File handle for restore SQL  file #
  my ($intermediate_file_name);                     # Intermediate file name #
  my ($restoremode) = 'full';                    # Mode of restore operation #
  my ($overridestr);                                       # Override string #
  my (@override_options);                 # Override options <odldg>:<newdg> #
  my (%diskgroups_replace);   
  my ($ignore_errors) = 0; # Whether to proceed with next fiskgroups on error #
  my ($create_restore_sql) = 0; # Execute restore or dump restore SQL to file #
  my ($restore_sql_file_name); # SQL file where to write the restore commands #
  my ($file_content);                    # Contents of the intermediate file. #
  my ($asm_instance_version);                          # ASM instance version #
  my ($compatible_rdbms);
  my ($compatible_asm);
  my ($au_size);
  my ($sec_size);
  my ($smart_scan_capable);


  # Get the ASM version of the instance we are connected to
  $asm_instance_version = asmcmdshare_get_asm_version($dbh);
  return unless defined ($asm_instance_version);

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

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

  # Check if number of non-option parameters are correct.
  if (@ARGV != 1)
  {
    asmcmdambr_syntax_error($asmcmdglobal_hash{'cmd'});
    return;
  }
   # -b options is deprecated as this argument is mandatory bug#6960868
   # Read Intermediate file into memory.
  $intermediate_file_name = shift (@ARGV); 

  if (!open (FILE, "< $intermediate_file_name"))
  {
    # Cannot open intermediate file, nothing can be done, print error 
    # and exit
    @eargs = ($intermediate_file_name, $!);
    asmcmdshare_error_msg(9345, \@eargs);
    return; 
  }
  FILE->untaint();  
  # Remember the default read mode #  
  $defreadmode = $/;
  # Read in file all at once #
  undef $/;
  $file_content = <FILE>;
  # Restore the default #
  $/ = $defreadmode;
  close (FILE);

  if ($file_content !~ /^\@diskgroup_set/)
  {
    # The intermediate file must begin with the string "@diskgroup_set".
    # If not, then we must not have a valid intermediate file.
    @eargs = ($intermediate_file_name);
    asmcmdshare_error_msg(9347, \@eargs);
    return;
  }

  # Now evaluate the file contents into an array for hashes.
  @diskgroup_set = eval $file_content;
  if ($@)
  {
    # Error evaluating the intermediate file, print error and return.
    @eargs = ($intermediate_file_name);
    asmcmdshare_error_msg(9347, \@eargs);
    return; 
  } 

  if (!@diskgroup_set)
  {
    # Backup file specified by user is empty or cannot be interpreted.
    @eargs = ($intermediate_file_name);
    asmcmdshare_error_msg(9356, \@eargs);
    return;
  }


  # The following section checks the sanity of the command parameters. 
  # Errors will not be ignored.

  if (defined($args{'G'}))
  {
    my ($backup_diskgroup_name);   # Name of a disk group that was backed up. #
    my ($found) = 0;      # Whether or not a disk group in the list is found. #
    my ($iter1);
    my ($iter2);

    # User has specified one or more diskgroups using -g option.
    # We will perform backup of only those diskgroups. 

    # Process the ',' separated list of diskgroup names.
    $dgnamesstr = uc($args{'G'});
    $dgnamesstr =~ s/\s+//g;                   # stripe off any white spaces. #
    @diskgroup_names = split (/,/, $dgnamesstr);         # delimiter is ',' . #
   
    # If user has specified list of diskgroups to restore, restore only them.
    # Make sure that we find information for those diskgroups in backup file,
    # if any one is missing, we print error and exit.
    for ($iter1 = 0; $iter1 < @diskgroup_names; $iter1++)
    {
      $found = 0;

      # Now look for the user-specified disk group in the backup intermediate
      # file.
      for ($iter2 = 0; $iter2 < @diskgroup_set; $iter2++)
      {
        $backup_diskgroup_name = $diskgroup_set[$iter2]{DGINFO}{DGNAME};

        if ($diskgroup_names[$iter1] eq $backup_diskgroup_name)
        {
          $found = 1;
          $diskgroup_set[$iter2]{DGINFO}{DGTORESTORE} = 1;
          last; # break out of loop
        }
      } # end for iter2 # 

      if (!$found)
      {
        # User specified a diskgroup to be restored but we could not find
        # information for that diskgroup in backup file. Print an error 
        # and exit.
        @eargs = ($diskgroup_names[$iter1]);
        asmcmdshare_error_msg(9355, \@eargs);
        return;
      }
    } # end for iter1 #   
  }
  else
  { # No -G flag specified. #
    my ($iter2);

    # By default restore all diskgroups specified in backup file.
    undef $dgnamesstr;
    for ($iter2 = 0; $iter2 < @diskgroup_set; $iter2++)
    {
      $diskgroup_set[$iter2]{DGINFO}{DGTORESTORE} = 1;
    }
  }

  if (!defined($args{'newdg'}) && defined($args{'o'})) 
  {
    # Override option can only be specified with -t newdg
    asmcmdshare_error_msg(9348, undef);
    asmcmdambr_syntax_error($asmcmdglobal_hash{'cmd'});  
    return;
  }

  if (defined($args{'newdg'}) && !defined($args{'o'})) 
  {
    # Option 'newdg' specified with no override options;
    # it's a syntax error; fail command.
    asmcmdshare_error_msg(9358, undef); 
    return;
  }
  
  if (defined($args{'o'}))
  {
    # Currently only a replacement diskgroup name can be specified
    # using override option, only valid syntax is 
    # -o '<olddgname>:<newdgname>,...', check input for syntax
    $overridestr = uc($args{'o'});
    $overridestr =~ s/\s+//g;                  # stripe off any white spaces. #
    @override_options = split (/,/, $overridestr);        # delimiter is ','. #

    # Loop through the override options one at a time.
    for ($i = 0; $i < @override_options; $i++)
    {
      my ($olddg);
      my ($newdg);
      my ($iter2);
      my ($found);

      $override_options[$i] = uc($override_options[$i]);
      ($olddg, $newdg) = split (/:/, $override_options[$i]);
      
      # Make sure the old diskgroup name specified in override options
      # actually matches some diskgroup to be restored.
      for ($iter2 = 0; $iter2 < @diskgroup_set; $iter2++)
      {
        if (($diskgroup_set[$iter2]{DGINFO}{DGTORESTORE} == 1) &&
            uc($diskgroup_set[$iter2]{DGINFO}{DGNAME}) eq $olddg)
        {
          $found = 1;
          last;
        }
      }

      if (!$found)
      {
        # Invalid Diskgroup name specified in override options
        # Print error and exit
        @eargs = ($olddg);
        asmcmdshare_error_msg(9359, \@eargs);
        return;
      }

      $diskgroups_replace{$olddg} = $newdg;
    }
  }

  if(defined($args{'newdg'}) || defined($args{'full'}) ||
      defined($args{'nodg'}))
  {
    my ($newdg) = defined($args{'newdg'});
    my ($nodg) = defined($args{'nodg'});
    my ($full) = defined($args{'full'});

    if(($newdg+$nodg+$full) != 1)
    {
       # Not a valid value for mode of operation
      asmcmdambr_syntax_error($asmcmdglobal_hash{'cmd'});
      return;
    }
   
    $restoremode = 'newdg' if(defined($args{'newdg'}));
    $restoremode = 'nodg' if(defined($args{'nodg'}));
    $restoremode = 'full' if(defined($args{'full'}));
  }

  if (defined($args{'silent'}))
  {
    # User has asked us to proceed with other diskgroups in case of 
    # error with current one, by default we exit on occurrence of an
    # error.
    $ignore_errors = 1;
  }       
  
  if (defined($args{'S'}))
  {
    # User has asked us to dump SQL commands for ASM restore to an 
    # output SQL file instead of doing the restore.
    $create_restore_sql = 1;
    $restore_sql_file_name = $args{'S'};

    # The file cannot already exist. We don't overwrite existing files.
    if ( -e $restore_sql_file_name)
    {
      @eargs = ($restore_sql_file_name);
      asmcmdshare_error_msg(9357, \@eargs);
      return;    
    }

    # Untaint the input
    ($restore_sql_file_name) = $restore_sql_file_name =~ m/(.*)/;

    if ( !($restore_sql_file_name =~  /([\w]+)(\.[\w+])*/) )
    {
      $restore_sql_file_name = "";
    }
 
    if (!open (SQLFILE, "> $restore_sql_file_name"))
    {
      @eargs = ($restore_sql_file_name, $!);
      asmcmdshare_error_msg(9345, \@eargs);
      return; 
    } 
    SQLFILE->untaint();
  } 
  # End parameter checking section.

  # New section begins here: restore the disk groups; errors can be ignored
  # if the -i flag is set.

  # We fetched information about diskgroups to work on, now look at them
  # one at a time and re-create the ones user wants us to re-create.
  for ($i = 0; $i < @diskgroup_set; $i++)
  {
    my $old_diskgroup_name = $diskgroup_set[$i]{DGINFO}{DGNAME};
    my $new_diskgroup_name;
    my $current_redundancy = $diskgroup_set[$i]{DGINFO}{DGTYPE};
    my $disks_info_array = $diskgroup_set[$i]{DISKSINFO}; #reference
    my $attribute_info_array;
    my $alias_info_array = $diskgroup_set[$i]{ALIASINFO}; #reference
    my $template_info_array = $diskgroup_set[$i]{TEMPLATEINFO}; #reference
    my $template;
    my $alias;
    my $found;
    my $iter1;
    my $attribute_name;
    my $diskgroup_compatibility = $diskgroup_set[$i]{DGINFO}{DGCOMPAT};

    # If the ASM version is smaller than the diskgroup compatibility
    # of the backup, then we can't restore.
    if (asmcmdshare_version_cmp($asm_instance_version,
                                $diskgroup_compatibility) < 0)
    {
      @eargs = ($diskgroup_compatibility);
      asmcmdshare_error_msg(9361, \@eargs);
      return;
    }

    # If the diskgroup is version 11.1 or more, it has an 
    # attribute directory to restore
    if (asmcmdshare_version_cmp($diskgroup_compatibility, "11.1.0.0.0") >= 0)
    {
        $attribute_info_array = $diskgroup_set[$i]{ATTRINFO}; #reference
    }

    # If the user specified the -g option, then make sure the group 
    # we're trying to restore is actually one of the ones the user
    # specified to restore using the -g option.
    if (defined($dgnamesstr))
    {
      $found = 0;
      for ($iter1 = 0; $iter1 < @diskgroup_names; $iter1++)
      {
        if ($diskgroup_names[$iter1] eq $old_diskgroup_name)
        {
          $found = 1;
          last; # break out of loop
        }   
      }

      # If the group is not one that the user specified, skip it.
      if (!$found)
      {
        next;
      }
    }

    print "Current Diskgroup metadata being restored: $old_diskgroup_name\n";

    if ($restoremode eq 'newdg' && 
        defined($diskgroups_replace{$old_diskgroup_name}))
    {
      # Replace diskgroup name and disks/failgroups names with new
      # values in diskgroup_set
      # $$$ implement replace  disks/failgroups names
      $new_diskgroup_name = uc($diskgroups_replace{$old_diskgroup_name});
      print "Current Diskgroup name replace by: $new_diskgroup_name\n";
    }
    else
    {
      $new_diskgroup_name = uc($old_diskgroup_name);
    }

    # Recreate the disk group if -t nodg is not specified.
    if ($restoremode ne 'nodg')
    {
      my ($first_disk_in_failgroup);  # Boolean to include FAILGROUP keyword. #
      my ($current_failgroup_disks); # Iterator for failgroups in disk group. #
      my ($current_disk);                # Iterator for disks in a failgroup. #
      my ($current_disk_info);         # Pointer to hash of disk information. #
      my ($current_failgroup_disks_table);  # Pointer to hash of disks in FG. #

      # We have to create the diskgroup
      # Create SQL command grouping disks based on failgroups 
      if (uc($current_redundancy) eq 'EXTERN')
      {
        $current_redundancy = 'EXTERNAL';
      }
      $stmt = 'create diskgroup ' . $new_diskgroup_name . ' '
              . $current_redundancy . ' redundancy '; 

      # Iterate through the list of failgroups in the disk group and
      # format them into the disk group creation SQL.
      foreach $current_failgroup_disks (keys %{$disks_info_array})
      {
        # Set to TRUE so that we print the FAILGROUP and DISK keywords.
        $first_disk_in_failgroup = 1;

        # Set pointer to hash of disks in current failgroup indexed by
        # $current_failgroup_disks.
        $current_failgroup_disks_table = 
                     $disks_info_array->{$current_failgroup_disks};

        # Iterate through the list of disks in the failgroup and format
        # them into the disk group creation SQL.
        foreach $current_disk (keys %{$current_failgroup_disks_table})
        {
          # Set pointer to disk information hash.
          $current_disk_info = $current_failgroup_disks_table->{$current_disk};

          # Include the FAILGROUP and DISK keywords only once--at the
          # very beginning.
          if ($first_disk_in_failgroup)
          {
            # The FAILGROUP keyword can be included only if the disk group
            # is not an EXTERNAL redundancy disk group.
            if ($current_redundancy ne 'EXTERNAL')
            {
              $stmt .= 'failgroup ' 
                    . $current_disk_info->{'FAILGROUP'} . ' disk ';
            }
            else
            {
              $stmt .= ' disk ';
            }

            $first_disk_in_failgroup = 0;
          }
          else
          {
            # Comma separate the disk names if not the first disk
            # in disk group.
            $stmt .= ', ';
          }

          # Now include the disk information in the recreation SQL 
          # statement for the disk in question.
          $stmt .= '\'' . $current_disk_info->{'PATH'} . '\' name '
                   . $current_disk_info->{'NAME'} . ' size '
                   . $current_disk_info->{'TOTAL_MB'} . 'M ';
        }
      }

      # Restore au_size, compatible.asm, and compatible.rdbms
      # if the diskgroup is version 11.1 or higher
      if( asmcmdshare_version_cmp($diskgroup_compatibility,
                                  "11.1.0.0.0" ) >= 0)
      {       
        #take the parameters from disk group creation and delete
        #them from the array
        if(defined($attribute_info_array->{'COMPATIBLE.ASM'}))
        {
          $compatible_asm = $attribute_info_array->{'COMPATIBLE.ASM'};
          $stmt .= 'attribute \'compatible.asm\' = \'' . $compatible_asm. '\'' ;
          delete($attribute_info_array->{'COMPATIBLE.ASM'});
        }
                   
        if(defined($attribute_info_array->{'COMPATIBLE.RDBMS'}))
        {
          $compatible_rdbms = $attribute_info_array->{'COMPATIBLE.RDBMS'};
          $stmt .= ', \'compatible.rdbms\' = \'' . $compatible_rdbms . '\'';
          delete($attribute_info_array->{'COMPATIBLE.RDBMS'});
        }
                   
        if(defined($attribute_info_array->{'AU_SIZE'}))
        {
          $au_size = $attribute_info_array->{'AU_SIZE'};
          $stmt .= ', \'au_size\' = \'' . $au_size . '\'';
          delete($attribute_info_array->{'AU_SIZE'});
        }

        if (defined($attribute_info_array->{'SECTOR_SIZE'}))
        {
          $sec_size = $attribute_info_array->{'SECTOR_SIZE'};
          delete($attribute_info_array->{'SECTOR_SIZE'});
          $stmt .= ', \'sector_size\' = \''. $sec_size . '\'';
        }

        if (defined($attribute_info_array->{'CELL.SMART_SCAN_CAPABLE'}))
        {
          $smart_scan_capable = $attribute_info_array->{'CELL.SMART_SCAN_CAPABLE'};
          delete($attribute_info_array->{'CELL.SMART_SCAN_CAPABLE'});
          $stmt .= ', \'cell.smart_scan_capable\' = \''. $smart_scan_capable . '\'';
        }
      }

      if ($create_restore_sql)
      { 
        # If we are creating a SQL file, simply print the disk group
        # creation SQL statement to it.
        print SQLFILE "$stmt" . ";\n";
      }
      else
      {
        # Otherwise, execute the SQL statement to re-create the disk group.
        $ret = asmcmdshare_do_stmt($dbh, $stmt);
        if (!defined($ret))
        {
          # Creation of diskgroup failed, nothing can be further done for 
          # this diskgroup; print error and proceed to next diskgroup 
          # if -i flag is specified by user, else exit.
          @eargs = ($DBI::errstr);
          asmcmdshare_error_msg(9352, \@eargs);
          if ($ignore_errors)
          {
            next;
          }
          else
          {
            return;
          }
        }

        print "Diskgroup $new_diskgroup_name created!\n";
      }

    } # end if restoremode ne 'nodg' #

    # At this point we have either created required diskgroup or mounted
    # the diskgroup if 'nodg' option is specified, now restore attributes, 
    # alias and template directories


    # Restore Attributes
    if( asmcmdshare_version_cmp($diskgroup_compatibility,
                                "11.1.0.0.0" ) >= 0)
    { 
      foreach $attribute_name (keys %{$attribute_info_array})
      {
        my ($attribute_value)=$attribute_info_array->{$attribute_name};
            
        $stmt = 'alter diskgroup /*ASMCMD AMBR*/' . $new_diskgroup_name .
                ' set attribute \'' . $attribute_name .
                '\' = \''. $attribute_value . '\'';
        
        if ($create_restore_sql)
        {
          # If we are creating a SQL file, simply print the disk group
          # creation SQL statement to it.
          print SQLFILE "$stmt" . ";\n";
        }
        else
        {
          # Otherwise, execute the SQL statement to modify or re-create the
          # attributes.
          $ret = asmcmdshare_do_stmt($dbh, $stmt);
          if (!defined($ret))
          {
            # Create/alter attribute failed, print error.
            @eargs = ($DBI::errstr);
            asmcmdshare_error_msg(9360, \@eargs);
            if ($ignore_errors)
            {
              next;
            }
            else
            {
              return;
            }

            print "Attribute $attribute_name created!\n";
          }
        }
      }
    }

    # Restore Templates
    foreach $template (keys %{$template_info_array}) 
    {
      my ($current_template_name) = 
          $template_info_array->{$template}{'TEMPNAME'};
      my ($current_template_redundancy) = 
          $template_info_array->{$template}{'REDUNDANCY'};
      my ($current_template_stripe) = 
          $template_info_array->{$template}{'STRIPE'};
      my ($alter_or_add);

      if (uc($template_info_array->{$template}{'SYSTEM'}) eq 'Y') 
      {
        # System template, it should have already been created as part 
        # of 'create diskgroup' command, 
        # execute alter statements for all template attributes
        $alter_or_add = 'alter';
      }
      else
      {
        # User created template, create a new one
        $alter_or_add = 'add';
      }

      # The keyword for unprotected in the v$ view is different from
      # the SQL keyword, so modify it.
      if (uc($current_template_redundancy) eq 'UNPROT')
      {
          $current_template_redundancy = 'UNPROTECTED';
      }

      # Now construct the SQL command to modify or recreate the
      # the templates.
      $stmt = 'alter diskgroup /*ASMCMD AMBR*/' . $new_diskgroup_name . ' ' 
                . $alter_or_add . ' template ' 
                . '"' . $current_template_name . '"'. ' attributes ('
                . $current_template_redundancy . ' ' 
                . $current_template_stripe . ')';

      if ($create_restore_sql)
      {
        # If we are creating a SQL file, simply print the disk group
        # creation SQL statement to it.
        print SQLFILE "$stmt" . ";\n";
      }
      else
      {
        # Otherwise, execute the SQL statement to modify or re-create the
        # templates.
        $ret = asmcmdshare_do_stmt($dbh, $stmt);
        if (!defined($ret))
        {
          # Create/alter template failed, print error.
          @eargs = ($DBI::errstr);
          asmcmdshare_error_msg(9353, \@eargs);
          if ($ignore_errors)
          {
            next;
          }
          else
          {
            return;
          }
        }

        if ($alter_or_add eq 'add')
        {
          print "User template $current_template_name created!\n";
        }
        else
        {
          print "System template $current_template_name modified!\n";
        }
      }

    }  # end for templates #     

    # Restore Alias directories
    # foreach $alias (keys %{$alias_info_array}) 
    # Note: ambr currently restores only user-created alias directories.
    #       System created directories or filenames obviously cannot
    #       be restored this way. User-created aliases also cannot be
    #       restored because the system-created filename is likely
    #       unknown at this point in time.

    # if a user directory is created inside a system created directory,
    # it will be restored through dbms_diskgroup
    for ($alias = 0; $alias < (keys %{$alias_info_array}); $alias++)
    {
      # Get alias directory name.
      my ($current_alias_directory_name) = 
          $alias_info_array->{$alias}{'ALIASNAME'};

      my @path = split(/\//,$current_alias_directory_name);
      my $type;

      if (defined($path[1]) && defined($supported_types{uc($path[1])}))
      {
        # this is a system created directory
        # Construct full-path of the directory.
        $current_alias_directory_name = '+' . $new_diskgroup_name . '/' . 
                                        $current_alias_directory_name;

        $stmt = 'begin' . "\n";
        $stmt .= 'dbms_diskgroup.createdir(\'' . 
                 $current_alias_directory_name . '\');' . "\n";
        $stmt .= "end;\n/";
      }
      else
      {
        # Construct full-path of the directory.
        $current_alias_directory_name = '+' . $new_diskgroup_name . '/' . 
                                        $current_alias_directory_name;

        # Construct the SQL to re-create the user-specified alias directory.
        $stmt = 'alter diskgroup /*ASMCMD AMBR */ ' . $new_diskgroup_name
              . ' add directory \''
              . $current_alias_directory_name . '\';';
      }

      if ($create_restore_sql)
      {
        print SQLFILE "$stmt" . "\n";
      }
      else
      {
        $stmt =~ s/\;$//;

        $ret = asmcmdshare_do_stmt($dbh, $stmt);
        if (!defined($ret))
        {
          # Create alias directory failed, print error.
          @eargs = ($DBI::errstr);
          asmcmdshare_error_msg(9354, \@eargs);
          if ($ignore_errors)
          {
            next;
          }
          else
          {
            return;
          }
        }

        print "Directory $current_alias_directory_name re-created!\n";
      }
    } # end for alias #    
  } # end for #

  # Close restore SQL file
  if ($create_restore_sql)
  {
    close (SQLFILE);
  }

  return;
}

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

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

########
# NAME
#   asmcmdambr_is_wildcard_cmd
#
# DESCRIPTION
#   This routine determines if an ASMCMDAMBR 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 asmcmdambr_is_wildcard_cmd 
{
  my ($arg) = shift;

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

########
# NAME
#   asmcmdambr_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 asmcmdambr module currently supports no command that can run 
#   without an ASM instance.
########
sub asmcmdambr_is_no_instance_cmd 
{
  my ($arg) = shift;

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

########
# NAME
#   asmcmdambr_parse_int_args
#
# DESCRIPTION
#   This routine parses the arguments for flag options for ASMCMDAMBR 
#   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 ASMCMDAMBR internal command.
########
sub asmcmdambr_parse_int_args 
{
  my ($cmd, $args_ref) = @_;
  my ($key);
  my (@string);

  #build the list of options to parse using GetOptions
  if($asmcmdambr_cmds{ $cmd }{ flags })
  {
    foreach $key(keys %{$asmcmdambr_cmds{ $cmd }{ flags }})
    {
      push(@string, $key);
    }
  }

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

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

  return 0;
}

########
# NAME
#   asmcmdambr_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 ASMCMDAMBR 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 asmcmdambr_syntax_error 
{
  my ($cmd) = shift;
  my ($cmd_syntax);                               # Correct syntax for $cmd. #
  my ($succ) = 0;

  # display syntax only for commands from this module.
  if ( asmcmdambr_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
#   asmcmdambr_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 asmcmdambr_get_asmcmd_cmds 
{
  return asmcmdshare_print_cmds(sort(keys %asmcmdambr_cmds));
}
1;
