#!$ORACLE_HOME/perl/bin/perl -w
#
# Copyright (c) 2004, 2009, 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] [-a <sysasm|sysdba>] [-p] [command]
#
#    MODIFIED  (MM/DD/YY)
#    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::Std;
use Term::ReadLine;

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

$SIG{INT} = \&asmcmdshare_signal_handler;# Signal handler for asmcmd         #

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

  # Set these environment variables to null. We don't use them for
  # security reasons.
  $ENV{'PATH'} = '';
  $ENV{'CDPATH'} = '';

  # But we still need $ORACLE_HOME/bin in the path for NT compatibility.
  # NT has the necessary dll's in that directory.
  $ENV{'PATH'} = "$ENV{'ORACLE_HOME'}/bin";

  # Un-taint the variable. Note that the extra scope is necessary
  # so that $1 is confined within that scope.
  {
    $ENV{'PATH'} =~ /([^\n^\r^\t]+)/;
    $ENV{'PATH'} = $1;
  }

  # Parse for consistency check option before calling init_modules
  asmcmdcore_consistency_check();

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

  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($asmcmdglobal_hash{'usr'}, 
                            $asmcmdglobal_hash{'pswd'}, 
                            $asmcmdglobal_hash{'ident'},
                            $asmcmdglobal_hash{'contyp'}); # added contyp,   #
                                                           # bug-5402303     #

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

  # 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 
      
      if ($asmcmdglobal_hash{'verbose'} eq 'y')
      {
        print STDERR "$DBI::errstr\n";
        $|++;
        @eargs = ($ENV{'ORACLE_SID'}) if defined($ENV{'ORACLE_SID'});
        asmcmdshare_error_msg (8103, \@eargs);
      }
      else
      {
        print STDERR "Connected to an idle instance.\n";
      }
    }
  }

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

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

    next 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);
    }
    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')
    {
      # 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($asmcmdglobal_hash{'usr'},
                                $asmcmdglobal_hash{'pswd'},
                                $asmcmdglobal_hash{'ident'},
                                $asmcmdglobal_hash{'contyp'}); 
                                # added contyp, bug-5402303 #

      if (!defined($dbh))
      {
        # re-connect to the asm instance failed #
        asmcmdshare_signal_exception (8201);
      }
    }

    # 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($asmcmdglobal_hash{'usr'},
                                $asmcmdglobal_hash{'pswd'},
                                $asmcmdglobal_hash{'ident'},
                                $asmcmdglobal_hash{'contyp'});
      if (!defined($dbh))
      {
        if ($asmcmdglobal_hash{'verbose'} eq 'y')
        {
          print STDERR "$DBI::errstr\n";
          $|++;
          @eargs = ($ENV{'ORACLE_SID'}) if defined($ENV{'ORACLE_SID'});
          asmcmdshare_error_msg (8103, \@eargs);
        }
        else
        {
          print STDERR "Connected to an idle instance.\n";
        }
      }
    }
  }

  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
{
  #consistency check option
  my $element = '-check';
  if (my $pos = grep {$_ eq $element} @ARGV)
  {
    #remove the option from ARGV array if found
    splice(@ARGV,$pos-1,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";
  }
  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 ($flags) = "Vva:p";
  my ($args_ref);
  my (%args) = ();
  my ($i, $len);
  my (@asmcmd_arg) = ();
  my (@cmd_arg) = ();

  # 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; 
  if( !getopts( $flags, \%args))
  {
    asmcmdcore_syntax_error('asmcmd');
    asmcmdcore_show_commands('exit', \*STDERR);
  }

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

  # reconstruct @ARGV for the command arguments
  @ARGV = @cmd_arg;

  if (defined($args{'V'}))
  {
    print 'asmcmd version ' . $asmcmdglobal_hash{'acver'}. "\n";
    exit 0;
  }

  if (defined($args{'v'}))
  {
    $asmcmdglobal_hash{'verbose'} = 'y';
  }

  if (defined($args{'a'}))
  {
    if (($args{'a'} =~ /^sysasm$/i) || ($args{'a'} =~ /^sysdba$/i)) 
    {
      $asmcmdglobal_hash{'contyp'} = $args{'a'};
    }
    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. #

    if ($ASMCMDGLOBAL_USE_CONN_STR) 
    {
      $syntax = asmcmdbase_get_cmd_syntax('asmcmd');
      $desc = asmcmdbase_get_cmd_desc('asmcmd');
    }
    else 
    {              # If -c is deprecated, then use syntax without -c option. #
      $syntax = asmcmdbase_get_cmd_syntax('asmcmd_no_conn_str');
      $desc = asmcmdbase_get_cmd_desc('asmcmd_no_conn_str');
    }
    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),
            scalar(@asmcmdglobal_error_message_callbacks),
            scalar(@asmcmdglobal_signal_exception_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) &&
              (scalar(@asmcmdglobal_error_message_callbacks) == $temp) &&
              (scalar(@asmcmdglobal_signal_exception_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;
}
##############################################################################
