#!$ORACLE_HOME/perl/bin/perl -w
#
# Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. 
#
#    NAME
#      asmcmdcore - ASM CoMmanD line interface (Driver Program)
#
#    DESCRIPTION
#      ASMCMD is a Perl utility that provides easy nagivation of files within
#      ASM diskgroups.  Simple UNIX shell-like commands allow directory 
#      traversal, directory creation and deletion, user alias creation and 
#      deletion, and file deletion capabilities, among other features.  
#      We implement this utility as a wrapper around existing SQL statements, 
#      and we use the Perl DBI Module to connect to an ASM instance and to 
#      issue these SQL statements.
#
#    NOTES
#      usage: asmcmdcore [-v] [--privilege <sysasm|sysdba>] [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    moreddy    01/13/11 - Backport moreddy_bug-8667038 from main
#    moreddy    01/12/11 - Backport moreddy_bug-6969662 from main
#    shmubeen   04/27/10 - fix of 7193021 for windows
#    amitroy    04/29/10 - BUG 8933243 - USE DIFFERENT CHARACTER IN ASMCMD TO
#                          "HIDE COLUMN DETAILS" INSTEAD OF -H; REPLACE -a
#                           TO --privilege FOR asmcmd
#    pvenkatr   03/31/10 - Syntax, Description, Example - all from XML
#    shmubeen   03/30/10 - fix for #7193021
#    moreddy    03/22/10 - Adding more tracing
#    moreddy    03/23/10 - fixed bug 9412680
#    moreddy    03/22/10 - Adding more tracing
#    sanselva   03/15/10 - clean up asmcmdbase_connect, get rid of
#                          unused asmcmdglobal_hash variables
#    mchimang   02/23/10 - Made change to handle CNTRL-C in Noninteractive Mode
#    moreddy    01/08/10 - Adding tracing infrastructure
#    danagara   08/06/09 - Cntrl + C handled properly bug-8646861
#    lajain     07/24/09 - Fix dsset --profile -f
#    gayavenk   06/11/09 - reconnect after dsset
#    sanselva   06/04/09 - Enable consistencychk when 'c' option used
#    heyuen     04/20/09 - add spbackup
#    heyuen     12/03/08 - reconnect after spmove
#    heyuen     11/10/08 - fix asmcmd prompt in windows
#    heyuen     10/14/08 - use dynamic modules
#    heyuen     08/02/08 - add verbose
#    heyuen     07/28/08 - rewrite asmcmdcore to use getopt
#    heyuen     06/16/08 - force disk scan during startup
#    heyuen     05/14/08 - reconnect after spcopy
#    heyuen     04/15/08 - bug 6957288
#    heyuen     03/26/08 - reconnect at instance startup
#    heyuen     03/21/08 - enable lsdsk in non connected mode
#    heyuen     02/15/08 - add history
#    hqian      11/29/07 - #5661841, #5629952: enable ASMCMD for RDBMS instance
#    heyuen     09/20/07 - flush error messages as well as pending messages
#                          before the prompt
#    heyuen     08/09/07 - refresh from main
#    heyuen     07/24/07 - add error message for connection failure
#    hqian      07/06/07 - #6174647: set $ENV{'PATH'} to include
#                          $ENV{'ORACLE_HOME'}/bin
#    hqian      06/25/07 - Reset $ENV{'PATH'} for security reasons
#    heyuen     04/02/07 - remove error message when issuing asmcmd help-
#                          and not being able to connect to ASM
#    hqian      03/09/07 - Notify user when failed to connect to ASM
#    hqian      03/02/07 - add asmcmdcore_get_asm_version
#    jilim      02/19/07 - fix for invalid $dbh after the cp 
#    hqian      11/21/06 - fix signal_handler
#    jilim      09/27/06 - bug-5402303: added passing arg, contype,
#                          for asmcmdbase_connect()
#    averhuls   07/06/06 - 
#    hqian      07/12/06 - 11gR1: enable AMBR, disk repair, and other features 
#    hqian      06/26/06 - disable asmcmddisk and asmcmdambr for initial merge
#    hqian      06/15/06 - add asmcmddisk module 
#    msharang   03/28/06 - Add asmcmdambr 
#    hqian      01/26/06 - rename asmcmdbase_global to asmcmdglobal_hash
#    hqian      01/24/06 - #4939032: split off callback defs into asmcmdglobal
#    hqian      01/20/06 - #4939032: rename bad_cmd() to show_commands()
#    hqian      01/18/06 - #4939032: support additional modules 
#    hqian      01/18/06 - #4939032: keep only main() and shell()
#    hqian      01/18/06 - #4939032: split up asmcmd into modules 
#    hqian      07/19/05 - Remove RCS header
#    hqian      06/23/05 - #4450221: support wildcards for CD
#    hqian      05/18/05 - Mention 'missing view attributes' in help ls 
#    hqian      05/03/05 - #4329688: improve SQL efficiency 
#    hqian      04/13/05 - ls_get_file_info() -> ls_process_file()
#    hqian      04/08/05 - Improve implementation of ls 
#    hqian      04/08/05 - Improve help documentation
#    hqian      04/07/05 - LRG 1843355: include seconds in mod-time 
#    hqian      04/01/05 - #4261342: use asmcmd messages for certain errors 
#    hqian      02/28/05 - #4204122: change NLS date format for minute to 'MI' 
#    hqian      10/27/04 - hqian_asmcmd_13306_linux_3
#    hqian      10/19/04 - Rename asmcmd0 to asmcmdcore
#    hqian      08/03/04 - hqian_asmcmd_13306_linux_2
#    hqian      07/28/04 - Add % as wildcard char in addition to *. 
#    hqian      07/13/04 - Add implementation of find [-t <type>].
#    hqian      06/30/04 - Make code that uses BigInt work for both Perl 5.6.1 
#                          and Perl 5.8.3; take out -c <connect_string> 
#                          functionality.
#    hqian      06/29/04 - Fix 10gR1 compatibility issues; fix alias name
#                          case-sensitive bug, should be case insensitive
#                          but case retentive. 
#    hqian      06/25/04 - Rename the main program from asmcmd to asmcmd0, so
#                          that we can name the wrapper script asmcmd; rename
#                          global constants, global variables, and function 
#                          names so that they are prefixed by 'asmcmd0_',
#                          as per coding style standards; fix ORA-15128 bug.
#    hqian      06/23/04 - Inaccurate error message bug: add error message
#                          8004, do not print error when '*' matches nothing;
#                          fix rm -rf * double error message bug; fix find 
#                          diskgroup bug; fix print header in empty directory
#                          bug; fix space in alias name bug.
#    hqian      06/22/04 - Give the option to turn off the functionality of 
#                          the -c flag; add constants for better code design. 
#    hqian      06/09/04 - Fix bugs; improve comments. 
#    hqian      06/07/04 - Organize code for better maintenance; code review 
#                          changes (suggested by Dave Friedman).
#    hqian      06/01/04 - Fix some bugs.
#    hqian      05/24/04 - Implement rm [-rf] and rm *; some code review fixes.
#    hqian      05/20/04 - Implement help, instance_type security check, 
#                        - connection error checks, and debug mode. 
#    hqian      05/18/04 - Implement ls flags and lsct. 
#    hqian      05/14/04 - hqian_asmcmd_13306
#    hqian      04/21/04 - Creation
#
#
#
#############################################################################
#
############################ Functions List #################################
#
# Top Level Routines
#   asmcmdcore_main
#   asmcmdcore_shell
#   asmcmdcore_parse_asmcmd_args
#   asmcmdcore_module_driver
#   asmcmdcore_process_help
#   asmcmdcore_show_commands
#   asmcmdcore_is_cmd
#   asmcmdcore_check_global_callbacks
#   asmcmdcore_syntax_error
#############################################################################

use strict;

# load global modules
use asmcmdglobal;
use asmcmdshare;

use Getopt::Long qw(:config no_ignore_case bundling);
use Term::ReadLine;
use File::Path;
use Sys::Hostname;

use List::Util qw[min max];


#find all the modules that exist, and include them
my (%asm_modules);

foreach (@INC)
{
  if (-d $_)
  {
    my ($dir)   = $_;
    my (@files) = ();

    opendir (MODDIR, $dir);
    @files = readdir(MODDIR);
    foreach (@files)
    {
      if ( $_ =~ /^asmcmd/ && $_ =~ /pm$/ )
      {
        my (@temp) = (split(/\./, $_))[0];
        $asm_modules{$temp[0]} = $dir . '/' . $_;
      }
    }
    closedir(MODDIR);
  }
}

delete($asm_modules{'asmcmdglobal'});
delete($asm_modules{'asmcmdshare'});


# If the module does not belong to asmcmd, remove it
my @not_mod = ();
my ($module);
foreach $module(keys %asm_modules)
{
  require "$module.pm";
  my ($is_asm) = $module. "::is_asmcmd()";

  eval ($is_asm);
  push (@not_mod, $module) if ($@);
}

foreach (@not_mod)
{
  delete ($asm_modules{$_});
}

# import modules
foreach $module(sort(keys %asm_modules))
{
  $module->import;
}

######################## ASMCMDCORE Global Variables ########################
#                                                                           #
# Each module needs to specify its initialization function here.
my (@asmcmdcore_init_modules) = ();
foreach $module(sort(keys %asm_modules))
{
  push (@asmcmdcore_init_modules, $module . "::init()");
}
our (%asmcmdcore_cmds) = (asmcmd  => {no_instance =>'False',
                                      flags      =>  {'V'=>'version',
                                                      'v=s'=>'tracingLevel',
                                                      'privilege=s'=>
                                                     'systemPrivilege',
                                                      'p'=>'commandHistory'
                                                     }
	                              }
                         );

  %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdcore_cmds);

############################ Top Level Routines ##############################
#
# Routines that calls exit():
#   asmcmdcore_main              - exit 0
#   asmcmdcore_syntax_error      - exit 0
#   asmcmdbase_show_commands     - exit 0
#   asmcmdbase_check_insttype    - exit 0
#   asmcmdshare_signal_exception  - exit 1
#   asmcmdcore_shell             - exit -1
#   asmcmddisk_process_lsdsk     - exit -1 0 1 2
########
# NAME
#   asmcmdcore_main
#
# DESCRIPTION
#   This function is the main function of ASMCMD.  It is the first
#   function that is called.
#
# PARAMETERS
#   None.
#
# RETURNS
#   Null.
#
########
sub asmcmdcore_main 
{
  my ($dbh);
  my ($module);
  my ($mypath);

  $mypath = $ENV{'PATH'};
 
  # $ORACLE_HOME/bin is appended for NT compatibility.
  # NT has the necessary dll's in that directory.
  $ENV{'PATH'} = "$ENV{'ORACLE_HOME'}/bin";

  # Parse for consistency check option before calling init_modules
  asmcmdcore_consistency_check();
  asmcmdcore_trace();
 
  # Initialize modules.
  for $module (@asmcmdcore_init_modules)
  {
    eval($module);
  }

  # Check to see if all the modules have initialized the global callbacks
  # correctly.
  asmcmdcore_check_global_callbacks();
  asmcmdcore_parse_asmcmd_args();
  asmcmdcore_get_system_endian();

  # If we're in non-interactive mode, and command is help, then there is
  # no need to attempt a network connection.  Just run help and exit.
  #  if (($asmcmdglobal_hash{'mode'} eq 'n') && 
  #       $asmcmdglobal_hash{'cmd'} eq 'help') 
  #  {
  #    asmcmdcore_process_help();
  #    exit 0;
  #  }

  # Now run the shell to process command(s).
  $dbh = asmcmdcore_shell($mypath);

  asmcmdbase_disconnect($dbh) if defined ($dbh);

  # Always exit zero here.  Exiting non-zero is done only from exception 
  # routine.  See asmcmdcore_signal_exception().
  exit 0;
}
asmcmdcore_main();


########
# NAME
#   asmcmdcore_get_system_endian
#
# DESCRIPTION
#   This routine gets the system's endianness and stores it in a global
#   variable.
#
# PARAMETERS
#    None.
#
# RETURNS
#   Null.
#
# NOTES
########
sub asmcmdcore_get_system_endian
{
  # The first bit of a little endian system is 1, and that of big endian
  # system is 0.
  if (unpack("b*", pack("s", 1)) =~ /^1/)
  {
    # System is little endian.
    $asmcmdglobal_hash{'endn'} = 1;
  }
  else
  {
    # System is big endian.
    $asmcmdglobal_hash{'endn'} = 0;
  }
}


########
# NAME
#   asmcmdcore_connect
#
# DESCRIPTION
#   This routine initiates a conneciton to the ASM instance.  Calls
#    asmcmdbase_connect().
#
# PARAMETERS
#    None.
#
# RETURNS
#   Null.
#
# NOTES
########
sub asmcmdcore_connect
{
  my ($dbh);

  # Connect to ASM instance first
  $dbh = asmcmdbase_connect(undef);

  if (defined ($dbh)) 
  {
    my ($qry, $sth, $row);

    # Bugs 5661841 and 5629952: now that these bugs are fixed, ASMCMD
    # can work on both ASM and RDBMS instances. No need to check instance
    # type, anymore.

    # Initialize global variables, including getting group_number and name
    # of the first diskgroup.
    asmcmdbase_init_global($dbh);
  }

  return $dbh;
}


########
# NAME
#   asmcmdcore_shell
#
# DESCRIPTION
#   This routine contains the top-level shell loop that prompts the user for
#   for commands and calls other routines to process them.
#
# PARAMETERS
#   dbh   (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
# NOTES
#   Should call asmcmdcore_connect to initialize $dbh before calling this 
#   routine.
########
sub asmcmdcore_shell 
{
  my ($dbh);

  my ($line);                                 # One line of input from user. #
  my ($prompt) = 'ASMCMD> ';                  # ASMCMD user prompt value.    #
  my ($term);
  my (@eargs);
  my ($lastflag) = 0;        #Flag to indicate last iteration of while loop
  my ($mypath) = shift;

  # This is the right place for registering the signal handler. Initially it
  # was registered when the modules where loaded in global scope. Handling 
  # sthe ctrl-C ignal before connecting to the ASM instance (and starting 
  # ASM command prompt) 
 
  $SIG{INT} = \&asmcmdshare_signal_handler;# Signal handler for asmcmd       #
 
  # Try to connect to ASM if the command is different from help
  if ($asmcmdglobal_hash{'cmd'} ne 'help')
  {
    $dbh = asmcmdcore_connect();
    
    if (defined($dbh))
    {
      $asmcmdglobal_hash{'ver'} = asmcmdshare_get_asm_version($dbh);
      
      # $$$ In order to test different version numbers of ASM and their
      # relative behaviors on ASMCMD, comment out the line above and
      # uncomment the line below. Set the version number to the one
      # that's to be tested. Note that this testing method works only
      # 
      # $asmcmdglobal_hash{'ver'} = '11.1.0.3.0';
    }
    else
    {
      # Inform user that connection to ASM failed, except when the issued
      # command is help, startup, shutdown, or lsdsk 
      
      asmcmdshare_trace(5, $DBI::errstr, 'y', 'y');
      @eargs = ($ENV{'ORACLE_SID'}) if defined($ENV{'ORACLE_SID'});
      if ($asmcmdglobal_hash{'verbose'} eq 'debug')
      {
        asmcmdshare_error_msg (8103, \@eargs);
      }
      asmcmdshare_trace(3, "Connected to an idle instance.", 'y', 'y');
    }
  }

  # If non-interactive mode, process command and return.
  if ($asmcmdglobal_hash{'mode'} eq 'n') 
  {
    if (!defined ($dbh) && 
        asmcmdshare_is_no_instance_cmd($asmcmdglobal_hash{'cmd'}) != 1) 
    {  # Connection failed, and command requires ASM instance; record error. #
      asmcmdshare_error_msg (8102, undef);
    }
    else
    {
      asmcmdcore_module_driver($dbh);
    }

    exit $asmcmdglobal_hash{'e'};
  }

  $asmcmdglobal_hash{'cwdnm'} = '+';

  if (-e "/dev/tty")
  {
    $term = new Term::ReadLine("", \*STDIN, \*STDOUT);
  }
  else
  {
    $term = new Term::ReadLine("CON", \*STDIN, \*STDOUT);
  }

  if ($term->Features->{ornaments})
  {
    local $Term::ReadLine::termcap_nowarn = 1;
    $term->ornaments(0);
  }
 
  while (1)
  {
    
    if ($lastflag == 1)
    {
      $lastflag = 0;   #Reset flag (which is reduandant as of now)
      last;
    }  
 
    eval
    {
      my (@token);# Need fresh array of parsed tokens of arguments from $line. #

      # Prepare prompt, if long version of prompt is needed.
      if ($asmcmdglobal_hash{'lprmt'} eq 'y') 
      {
        $prompt = 'ASMCMD [' . $asmcmdglobal_hash{'cwdnm'} . '] > ';
      }

      select STDERR;
      $|++;
      select STDOUT;
      $|++;

      $line = $term->readline($prompt);

      if (defined($line)) 
      {
        chomp($line);                           # Remove newline character.    #
        $line =~ s,^\s+,,g;                     # Remove initial spaces.       #
        $line =~ s,\s+$,,g;                     # Remove trailing spaces.      #
      }

      # Terminate if EOF or 'exit'.
      if (! defined($line)) 
      {
        $line = 'exit';
        print "exit\n";
      }
      #last if ($line eq 'exit');
      #last if ($line eq 'quit');
      if ($line eq 'exit' || $line eq 'quit')
      {
        $lastflag = 1;
        die;
      }

      if ( $line =~ /^!/ )
      {        
        $ENV{'PATH'} = $mypath; 
        $line =~ s/^!//;

        my $cmd;
        my $os = $^O;

        if($os eq "MSWin32")
        {
          $cmd = "$line";
        }	
        else
        {
          $cmd="$line 1>&1";
        }

        system($cmd); 

        # NT has the necessary dll's in that directory.
        $ENV{'PATH'} = "$ENV{'ORACLE_HOME'}/bin";
    } 
    else
    {  
      # Parse $line into an array of arguments.
      if (asmcmdbase_parse_int_cmd_line($line, \@token))
      {                      # asmcmdcore_parse_int_cmd_line() returned error. #
        asmcmdshare_error_msg(8007, undef);
        #next;    # Error, so done with this command, move on to next comand. #
        die;
      }
   
      die if ($token[0] eq '');                # Empty line, so skip command. #

      $asmcmdglobal_hash{'cmd'} = shift(@token);          # Save command name. #
      @ARGV = @token;     # Save in global @ARGV for internal command parsing. #

      if (!defined ($dbh) && 
          asmcmdshare_is_no_instance_cmd($asmcmdglobal_hash{'cmd'}) != 1) 
      {  # Connection failed, and command requires ASM instance; record error. #
        asmcmdshare_error_msg (8102, undef);
      }
      else
      {
        asmcmdcore_module_driver($dbh);
      }

      $term->addhistory($line);

      if ($asmcmdglobal_hash{'cmd'} eq 'cp' ||
          $asmcmdglobal_hash{'cmd'} eq 'spcopy' ||
          $asmcmdglobal_hash{'cmd'} eq 'spmove' ||
          $asmcmdglobal_hash{'cmd'} eq 'spset' ||
          $asmcmdglobal_hash{'cmd'} eq 'spbackup' ||
          $asmcmdglobal_hash{'cmd'} eq 'dsset' || 
          $asmcmdglobal_hash{'cmd'} eq 'rm')
      {
        # re-connect to asm instance after the cp #
        # this must be done since $dbh is now invalidated by the cp #
        # and it was orginally set at this module level # 
        asmcmdbase_disconnect($dbh) if defined ($dbh);

        $dbh = asmcmdbase_connect(undef);
      }

      # re-connect if we startup/shutdown an instance
      # if we cannot get a $dbh, that means there is no instance
      # but this is not a reason to signal error since we can
      # try startup again
      if ($asmcmdglobal_hash{'cmd'} eq 'startup' ||
          $asmcmdglobal_hash{'cmd'} eq 'shutdown')
      {
        asmcmdbase_disconnect($dbh) if defined ($dbh);

        $dbh = asmcmdbase_connect(undef);
        if (!defined($dbh))
        {
          asmcmdshare_trace(5, $DBI::errstr, 'y', 'y');
          @eargs = ($ENV{'ORACLE_SID'}) if defined($ENV{'ORACLE_SID'});
          if ($asmcmdglobal_hash{'verbose'} eq 'debug')
          {
            asmcmdshare_error_msg (8103, \@eargs);
          }
          asmcmdshare_trace(3, "Connected to an idle instance.", 'y', 'y');
        }
      }
      }
    };
  }

  return $dbh;
}

########
# NAME
#  asmcmdcore_consistency_check
#
# DESCRIPTION
#  This routine parses command line arguments only for consistency check option
#  not anyother the arguments  related to ASMCMD or commands internal to ASMCMD.
#
# PARAMETERS
#   GLOBAL: @ARGV (IN/OUT) - list of all command line arguments for asmcmd.
#
# RETURNS
#   Null.
#
#  Note also that this routine removes the consistency check option if found in
#  ARGV
########
sub asmcmdcore_consistency_check
{
  my $i;
  for ($i = 0; $i < $#ARGV+1; $i++)
  {
    if ($ARGV[$i] eq '-check')
    {
      #remove the option from ARGV array if found
      splice(@ARGV,$i,1);

      #set the global to denote that consistency check is ON
      #later used in init() function in all perl modules
      $asmcmdglobal_hash{'consistchk'} = 'y';
      print STDERR "WARNING: ASMCMD consistency check enabled\n";
      last;
    }
  }
  return;  
}

########
# NAME
#  asmcmdcore_trace
#
# DESCRIPTION
#  This routine parses command line arguments only for verbose option -v and
#  not anyother the arguments  related to ASMCMD or commands internal to ASMCMD.
#
# PARAMETERS
#   GLOBAL: @ARGV (IN/OUT) - list of all command line arguments for asmcmd.
#
# RETURNS
#   Null.
#
#  Note also that this routine removes the verbose option -v and trace level 
#  value if found in ARGV
########
sub asmcmdcore_trace
{
  my ($i, $user, $host);
  
  $user = getlogin || getpwuid($<);
  $host = hostname();

  $asmcmdglobal_trace_path = 
     "$ENV{'ORACLE_HOME'}/log/diag/asmcmd/user_$user/$host";

  if (!-d $asmcmdglobal_trace_path)
  {
    $asmcmdglobal_trace_path =~ /([^\n^\r^\t]+)/;
    $asmcmdglobal_trace_path =$1;

    eval { mkpath ("$asmcmdglobal_trace_path/alert") };
    if ($@)
    {
      print STDERR "Can not create path $asmcmdglobal_trace_path/alert \n";                  
    }
    eval { mkpath ("$asmcmdglobal_trace_path/trace") };
    if ($@)
    {
      print STDERR "Can not create path $asmcmdglobal_trace_path/trace \n";  
    }
  }

  for ($i = 0; $i < $#ARGV+1; $i++)
  {
    if ($ARGV[$i] eq '-v')
    {
      #remove the option from ARGV array if found
      splice(@ARGV,$i,1);
      if (defined($ARGV[$i]))
      {
        if (defined($asmcmdshare_trace_levels{$ARGV[$i]}))
        {
          $asmcmdglobal_hash{'verbose'} =$ARGV[$i]; 
        }
        else
        {
          print STDERR "WARNING: Specified tracing level '$ARGV[$i]' does".
                                                " not exist.\n";
          $asmcmdglobal_hash{'verbose'} ='normal';
          print STDERR "Default level of tracing is enabled.\n";       
        }
        #remove the value  of trace level from ARGV array if found
        splice(@ARGV,$i,1);
      }
      else
      {
        print STDERR "WARNING: Tracing level not specified\n";
        $asmcmdglobal_hash{'verbose'} ='normal';
        print STDERR "Default level of tracing is enabled.\n";
      }
      last;
    }
  }
  return;
}

########
# NAME
#   asmcmdcore_parse_asmcmd_args
#
# DESCRIPTION
#   This routine parses the command line arguments for ASMCMD.  These are
#   not the arguments for commands internal to ASMCMD.  
#   asmcmdbase_parse_int_args() handles the latter cases.
#
# PARAMETERS
#   GLOBAL: @ARGV (IN/OUT) - list of all command line arguments for asmcmd.
#
# RETURNS
#   Null.
#
#  Note also that this routine *modifies* @ARGV; all parsed arguments are
#  removed from this array.
########
sub asmcmdcore_parse_asmcmd_args 
{
  my ($args_ref);
  my (%args) = ();
  my ($i, $len);
  my (@asmcmd_arg) = ();
  my (@cmd_arg) = ();

  my ($cmd) = 'asmcmd';
  my (@string);
  my ($key);

  # chop off the @ARGV array, so we process asmcmd arguments
  for ($i = 0; $i < $#ARGV+1; $i++)
  {
    if (defined ($asmcmdglobal_cmds{$ARGV[$i]}))
    {
      last;
    }
  }
  $len = $#ARGV;
  @asmcmd_arg = @ARGV[0..$i-1] if (defined(@ARGV[0..$i-1]));
  @cmd_arg    = @ARGV[$i..$len] if (defined(@ARGV[$i..$len]));
 
  @ARGV = @asmcmd_arg; 

   #build the list of options to parse using GetOptions
  if($asmcmdcore_cmds{ $cmd }{ flags })
  { 
    foreach $key(keys %{$asmcmdcore_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,@string)) || (@ARGV > 0) )
  {
    # Print command list if unknown arguement
    # Print correct command format if syntax error. #
    if(@ARGV > 0)
    {
      asmcmdcore_show_commands('exit', \*STDERR);
    }
    else 
    {
       asmcmdcore_syntax_error($cmd);
    }
    return;
  }
 
  #Set the correct options if deprecated options were used and print WARNING.
  #asmcmdshare_handle_deprecation($asmcmdglobal_hash{ 'cmd'},\%args);
  asmcmdshare_handle_deprecation($cmd, \%args);

  # reconstruct @ARGV for the command arguments
  @ARGV = @cmd_arg; 
  # if a command is passed, check that it is a valid one
  if (defined($ARGV[0]))
  {
    if (!defined($asmcmdglobal_cmds{$ARGV[0]}))
    {
      asmcmdcore_show_commands('exit', \*STDERR);
      return;
    }
  }
 
  if (defined($args{'V'}))
  {
    print 'asmcmd version ' . $asmcmdglobal_hash{'acver'}. "\n";
    exit 0;
  }

  #default is sysasm
  $asmcmdglobal_hash{'contyp'} = 'sysasm';
  if (defined($args{'privilege'}))
  {
    if (($args{'privilege'} =~ /^sysasm$/i) || 
        ($args{'privilege'} =~ /^sysdba$/i)) 
    {
      $asmcmdglobal_hash{'contyp'} = $args{'privilege'};
    }
    else
    {
      asmcmdcore_syntax_error('asmcmd');
      return;
    }
  }
  
  if (defined($args{'p'}))
  {
    $asmcmdglobal_hash{'lprmt'} = 'y';
  }

  if (defined($ARGV[0]))
  {
    $asmcmdglobal_hash{'mode'} = 'n';

    if (asmcmdcore_is_cmd($ARGV[0]))
    {
      $asmcmdglobal_hash{'cmd'} = $ARGV[0];
      shift(@ARGV);
      return;
    }
    else
    {
      asmcmdcore_show_commands('exit', \*STDERR);
    }
  }
  return;
}


########
# NAME
#   asmcmdcore_module_driver
#
# DESCRIPTION
#   This function calls in each module the respective function that 
#   processes commands responsible by the said module.  All ASMCMD
#   commands must pass through this function before being processed
#   by the modules.
#
# PARAMETERS
#   dbh         (IN) - initialized database handle, must be non-null.
#
# RETURNS
#   Null.
#
########
sub asmcmdcore_module_driver
{
  my ($dbh) = shift;
  my ($succ) = 0;
  my ($module);
  my (@eargs);                                   # Array of error arguments. #

  foreach $module (@asmcmdglobal_command_callbacks)
  {
    if ($module->($dbh))
    {
      # Assert that we find only one occurrence of this command in
      # the modules.
      @eargs = ("asmcmdcore_module_driver_05", $asmcmdglobal_hash{'cmd'});
      asmcmdshare_assert(($succ == 0), \@eargs);
      $succ = 1;
    }
  }

  if ($asmcmdglobal_hash{'cmd'} eq 'help')
  {
    # Assert that we find only one occurrence of this command in
    # the modules.
    @eargs = ("asmcmdcore_module_driver_10", $asmcmdglobal_hash{'cmd'});
    asmcmdshare_assert(($succ == 0), \@eargs);
    asmcmdcore_process_help();
    $succ = 1;
  }

  if (! $succ)
  {
    asmcmdcore_show_commands(undef, \*STDERR);
  }

  return;
}

########
# NAME
#   asmcmdcore_process_help
#
# DESCRIPTION
#   This top-level routine processes the help command.
#
# PARAMETERS
#   None.
#
# RETURNS
#   Null.
#
########
sub asmcmdcore_process_help 
{
  my (%args);                             # Argument hash used by getopts(). #
  my ($ret);                     # asmcmdbase_parse_int_args() return value. #
  my ($cmd);      # User-specified command-name argument; show help on $cmd. #
  my ($syntax);                                   # Command syntax for $cmd. #
  my ($desc);                                # Command description for $cmd. #
  my ($module);                                  # A module's help function. #
  my ($succ) = 0;                        # 1 if command exists, 0 otherwise. #
  my (@eargs);                                   # Array of error arguments. #

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

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

  $cmd = shift (@ARGV);

  if (defined ($cmd))
  {
    # Search each module for the command's help message.
    foreach $module (@asmcmdglobal_help_callbacks)
    {
      if ($module->($cmd) == 1)
      {
        # Assert that we find only one occurrence of this command in
        # the modules.
        @eargs = ("asmcmdcore_process_help_05", $cmd);
        asmcmdshare_assert(($succ == 0), \@eargs);

        # We found the command's help message.
        $succ = 1;
      }
    }
  }

  if (! defined ($cmd) || ($succ == 0)) 
  {                       # No command name specified, or command not found; #
                                # show help on asmcmd and list all commands. #

    $syntax = "";
    if ($ASMCMDGLOBAL_USE_CONN_STR) 
    {
      $syntax = asmcmdshare_get_help_syntax ('asmcmd');
      $desc = asmcmdshare_get_help_desc('asmcmd');
      $desc = asmcmdshare_trim_str ($desc);
    }
    else 
    {              # If -c is deprecated, then use syntax without -c option. #
      $syntax = asmcmdshare_get_help_syntax('asmcmd_no_conn_str') ;
      $desc = asmcmdshare_get_help_desc('asmcmd_no_conn_str');
      $desc = asmcmdshare_trim_str ($desc);
    }
    print "        $syntax\n";
    print "$desc\n\n";
    asmcmdcore_show_commands(undef, \*STDOUT); # Print list of all commands. #
  }

  return;
}

########
# NAME
#   asmcmdcore_show_commands
#
# DESCRIPTION
#   This routine prints a list of all valid internal commands, used
#   as an error message when the user has entered an invalid command name.
#   If $exit is set to 'exit', then also call exit(0).  This option is to 
#   accommodate the non-interactive option, when quitting asmcmd is necessary.
#   The caller can specify whether he wants to direct the output to
#   STDOUT or STDERR.
#
# PARAMETERS
#   exit        (IN) - flag: causes asmcmdbase_show_commands() to call exit() 
#                      iff value is 'exit'.
#   IO_handle   (IN) - handle where to print output: STDOUT or STDERR.
#
# RETURNS
#   Null.
#
########
sub asmcmdcore_show_commands
{
  my ($exit, $output_handle) = @_;
  my ($asmcmd_cmds) = '';
  my ($module);

  print $output_handle "        commands:\n";
  print $output_handle "        --------\n\n";

  foreach $module (@asmcmdglobal_command_list_callbacks)
  {
    $asmcmd_cmds .= $module->() . "\n";
  }

  print $output_handle $asmcmd_cmds;

  exit 0 if (defined($exit) && ($exit eq 'exit'));
  return;
}

########
# NAME
#   asmcmdcore_is_cmd
#
# DESCRIPTION
#   This routine checks if a user-entered command is one of the known ASMCMD
#   internal commands.
#
# PARAMETERS
#   arg   (IN) - user-entered command name string.
#
# RETURNS
#   1 if $arg is one of the known commands, 0 otherwise.
#
# NOTES
#   This routine calls the callbacks from each module to check if $arg
#   belongs to any of the modules.  It asserts that the command is found
#   in only one module.
########
sub asmcmdcore_is_cmd
{
  my ($command) = shift;

  


  my ($module);
  my ($succ) = 0;
  my ($count) = 0;
  my (@eargs);                                   # Array of error arguments. #

  

  foreach $module (@asmcmdglobal_is_command_callbacks)
  {
    if ($module->($command))
    {
      $succ = 1;
      $count++;
    }
  }

  # Assert that $count is at most 1 and at least 0.
  @eargs = ("asmcmdcore_is_cmd_05", $asmcmdglobal_hash{'cmd'}, $count);
  asmcmdshare_assert( (($count == 1) || ($count == 0)), \@eargs);

  return $succ;
}

########
# NAME
#   asmcmdcore_check_global_callbacks
#
# DESCRIPTION
#   This function checks to see if the global callback arrays have been
#   initialized correctly. 
#
# PARAMETERS
#   None
#
# RETURNS
#   Null if the assertion passes; signals exception otherwise.
#
# NOTES
#   This function asserts that all the callback arrays, including
#   asmcmdcore_init_modules, have the same number of elements.
########
sub asmcmdcore_check_global_callbacks
{
  my (@eargs);                             # Error arguments for the assert. #
  my ($temp);

  @eargs = ("asmcmdcore_check_global_callbacks_05", 
            scalar(@asmcmdcore_init_modules),
            scalar(@asmcmdglobal_command_callbacks),
            scalar(@asmcmdglobal_help_callbacks),
            scalar(@asmcmdglobal_command_list_callbacks),
            scalar(@asmcmdglobal_is_command_callbacks),
            scalar(@asmcmdglobal_is_wildcard_callbacks),
            scalar(@asmcmdglobal_syntax_error_callbacks),
            scalar(@asmcmdglobal_no_instance_callbacks));
           
  $temp = scalar(@asmcmdcore_init_modules);

  asmcmdshare_assert(((scalar(@asmcmdglobal_command_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_help_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_command_list_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_is_command_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_is_wildcard_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_syntax_error_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_no_instance_callbacks) == $temp)),
                                  \@eargs);

  return;
}

########
# NAME
#   asmcmdcore_syntax_error
#
# DESCRIPTION
#   This function calls into each module to display the correct syntax
#   for a given ASMCMD command.
#
# PARAMETERS
#   command     (IN) - the user-specified ASMCMD command
#
# RETURNS
#   Null.
#
# NOTES
#   This routine calls the callbacks from each module to display the 
#   correct syntax for $command.
########
sub asmcmdcore_syntax_error
{
  my ($command) = shift;
  my ($module);
  my ($count) = 0;
  my (@eargs);                                   # Array of error arguments. #

  foreach $module (@asmcmdglobal_syntax_error_callbacks)
  {
    if ($module->($command))
    {
      $count++;
    }
  }

  # Assert that $count is at most 1 and at least 0.
  @eargs = ("asmcmdcore_syntax_error_05", $asmcmdglobal_hash{'cmd'}, $count);
  asmcmdshare_assert( (($count == 1) || ($count == 0)), \@eargs);

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