# Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved. # # NAME # asmcmdamdu - ASM CoMmanD AMDU # # DESCRIPTION # This module takes a asm file alias name, finds the file number and # initiates an AMDU extract for that file. # # NOTES # # # MODIFIED (MM/DD/YY) # apfwkr 07/02/17 - Backport anovelo_bug-21159907 from main # anovelo 01/19/17 - 25397936: Allow full path use in amdu_extract # diguzman 05/31/16 - 19654070: Little change at _no_instance_cmd routine # gmengel 09/01/15 - LRG 18246299: Make temp file portable # gmengel 11/11/13 - Bug 17710683: Parsing problem with kfed output. # gmengel 04/29/13 - Copied from asmcmdtemplate.pm # ############################################################################# # ############################ Functions List ################################# # ############################################################################# package asmcmdamdu; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(asmcmdamdu_init ); use strict; use Getopt::Long qw(:config no_ignore_case bundling); use asmcmdglobal; use asmcmdshare; use asmcmdparser; #################### ASMCMDAMDU Global Constants #################### my (%asmcmdamdu_cmds) = (amdu_extract => {} ); #################### ASMCMDAMDU Global Variables #################### sub is_asmcmd { return 1; } ######## # NAME # asmcmdamdu_init # # DESCRIPTION # This function initializes the asmcmdamdu 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, \&asmcmdamdu_process_cmd); push (@asmcmdglobal_help_callbacks, \&asmcmdamdu_process_help); push (@asmcmdglobal_command_list_callbacks, \&asmcmdamdu_get_asmcmd_cmds); push (@asmcmdglobal_is_command_callbacks, \&asmcmdamdu_is_cmd); push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmdamdu_is_wildcard_cmd); push (@asmcmdglobal_syntax_error_callbacks, \&asmcmdamdu_syntax_error); push (@asmcmdglobal_no_instance_callbacks, \&asmcmdamdu_is_no_instance_cmd); %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdamdu_cmds); } ######## # NAME # asmcmdamdu_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 asmcmdamdu module; 0 if not. # # NOTES # Only asmcmdcore_shell() calls this routine. ######## sub asmcmdamdu_process_cmd { my ($dbh) = @_; my ($succ) = 0; # Get current command from global value, which is set by # asmcmdamdu_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 ASMCMDAMDU command. my (%cmdhash) = ( amdu_extract => \&asmcmdamdu_process_extract ); if (defined ( $cmdhash{ $cmd } )) { # If user specifies a known command, then call routine to process it. # $cmdhash{ $cmd }->($dbh); $succ = 1; } return $succ; } ######## # NAME # asmcmdamdu_process_extract # # DESCRIPTION # This function gets the file number associated with an alias name # and initiates AMDU to extract that file. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdamdu_process_cmd() calls this function. ######## sub asmcmdamdu_process_extract { my $dbh = shift; my %args; # Argument hash used by getopts(). # my $disk_pattern; # disk pattern and group name. # my $i; my $ret; my $amdu_args; my $exfile; # the extract specification for the file # my $fnum; # file number # my @elements; my @amduout; my $fname; # command line argument # my $gname; # command line argument # my $ausz; # command line argument # my $blksz; # command line argument # my $exdir = $asmcmdglobal_hash{'tempdir'} . "/$$"; my $exout = $asmcmdglobal_hash{'tempdir'} . "/$$/exout"; my %norm; # See asmcmdshare_normalize_path() return value comments. # my @paths; # Array of normalized paths; $norm{'path'} dereferenced. # # Get option parameters, if any. $ret = asmcmdamdu_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); return unless defined ($ret); # Name of disk group for extract $gname = shift(@{$args{'amdu_extract'}}); # Name of file to be extracted $fname = shift(@{$args{'amdu_extract'}}); # Discovery disk string $disk_pattern = shift(@{$args{'amdu_extract'}}); mkdir "$exdir"; # directory for working files # Substitute any acceptable wild card character with '*', which # is known to be accepted. This way, any asmcmd wild card # character can be used. $disk_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\*,g; # directories in which amdu can be found. my (@patharr) =("$ENV{'ORACLE_HOME'}/bin/", "$ENV{'ORACLE_HOME'}/rdbms/bin/"); # If --sys_filename option is not being used, user should have provided alias # file name. if (!defined ($args{'sys_filename'})) { $exfile = "$gname.ALIAS"; $amdu_args = "-diskstring='$disk_pattern' -nodir -noheart -extract $exfile". " -output $exout"; @amduout = asmcmdshare_execute_tool("amdu", ".exe", $amdu_args, \@patharr); } # replace windows style '\\' to Unix style '\/' $fname =~ s/\\/\//g; # Is the instance available? if (defined ($dbh)) { # See if any entries exist; if so, find all matches for $fname. %norm = asmcmdshare_normalize_path($dbh, $fname, 0, \$ret); return unless ($ret == 0); @paths = @{ $norm{'path'} }; $fname = $paths[0]; } # No instance: absolute path name must be specified (including '+') elsif ($fname !~ /^\+/) { # ASMCMD-8025 "Invalid file name specified. Absolute path name is required # when there is no connection to Oracle ASM instance." asmcmdshare_error_msg(8025, undef); return; } if (defined ($args{'sys_filename'})) { my @filepath = split /\./, $fname; $fnum = $filepath[-2]; if (!defined($fnum) || $fnum !~ m/[\d]+/) { my @eargs = "$fname"; # ASMCMD-8038 "invalid path to ASM system file name '%s'" asmcmdshare_error_msg(8038, \@eargs); return; } } else { # If the --sys_filename option was not used, provided file name is the # alias. Call asmcmdamdu_get_filenum to obtain the file number. @elements = split(/\//, $fname); # call get_filenum recursively to traverse path $fnum = asmcmdamdu_get_filenum(0, $exout, @elements); } if ($fnum > 0 && $fnum < $asmcmdglobal_maxub4) { $exfile = "$gname.$fnum"; $amdu_args="-diskstring='$disk_pattern' -noheart -extract $exfile"; @amduout = asmcmdshare_execute_tool("amdu", ".exe", $amdu_args, \@patharr); asmcmdshare_print @amduout; } else { my @eargs = "$fname"; # ASMCMD-8024 "file number could not be determined for alias name '%s' " asmcmdshare_error_msg(8024, \@eargs); } return; } ######## # NAME # asmcmdamdu_get_filenum # # DESCRIPTION # This function is called recursively to identify the file number for # a given alias path name (e.g. +datafile/aliasname) # # PARAMETERS # index (IN) - the kfade refer number identifies block of next branch # ext_file (IN) - the extract file for the alias directory # levels (IN) - the number of branhes left: levels=1 -> leaf # # RETURNS # file number for file name alias # ######## sub asmcmdamdu_get_filenum { my $index = shift(@_); my $ext_file = shift(@_); my @parms = @_; shift(@parms); my $branch = $parms[0]; my $levels = @parms; my ($label, $i); my ($kfed_args, $kfarefnum, $kfarefnum2, $kfaname, $kfafnum); my (@kfedout, @fields); # if branch is null, user probably entered file name in wrong form if (!defined($branch)) { return 0; } # directories in which kfed can be found. my (@patharr) =("$ENV{'ORACLE_HOME'}/bin/", "$ENV{'ORACLE_HOME'}/rdbms/bin/"); # Form the kfed execution line to read the disk header. $kfed_args = "op=read blknum=$index dev=\'$ext_file\'"; # Execute the kfed command and capture the results. @kfedout = asmcmdshare_execute_tool("kfed", ".exe", $kfed_args, \@patharr); # Read the kfed dump containing this for ($i = 0; $i < @kfedout; $i++) { if($kfedout[$i] =~ /\.refer\.number:/) { @fields = split ' ', $kfedout[$i]; $kfarefnum = hex($fields[@fields-1]); if ($kfarefnum == 0) { next; # skip over empty kfa entries } ($label,$kfaname) = split /[:,\s]+/, $kfedout[$i+=2]; ($label,$kfafnum) = split ' ', $kfedout[++$i]; if ($kfaname =~/^$branch$/i) # we have a hit { if ($levels eq 1) # this is the leaf, we're done { return $kfafnum; } else # still on branch, recurse to next branch/leaf { return asmcmdamdu_get_filenum($kfarefnum, $ext_file, @parms); } } } } return $asmcmdglobal_maxub4; } ######## # NAME # asmcmdamdu_process_help # # DESCRIPTION # This function is the help function for the ASMCMDAMDU module. # # PARAMETERS # command (IN) - display the help message for this command. # # RETURNS # 1 if command found; 0 otherwise. ######## sub asmcmdamdu_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 (asmcmdamdu_is_cmd ($command)) { # User specified a command name to look up. # $desc = asmcmdshare_get_help_desc($command); asmcmdshare_print "$desc\n"; $succ = 1; } return $succ; } ######## # NAME # asmcmdamdu_is_cmd # # DESCRIPTION # This routine checks if a user-entered command is one of the known # ASMCMD internal commands that belong to the ASMCMDAMDU module. # # PARAMETERS # arg (IN) - user-entered command name string. # # RETURNS # True if $arg is one of the known commands, false otherwise. ######## sub asmcmdamdu_is_cmd { my ($arg) = shift; return defined ($asmcmdamdu_cmds {$arg}); } ######## # NAME # asmcmdamdu_is_wildcard_cmd # # DESCRIPTION # This routine determines if an ASMCMDAMDU 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 asmcmdamdu_is_wildcard_cmd { my ($arg) = shift; return defined ($asmcmdamdu_cmds{ $arg }) && (asmcmdshare_get_cmd_wildcard($arg) eq "true" ) ; } ######## # NAME # asmcmdamdu_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 # 1 if $arg is a command that can run without an ASM instance or it does not # belong to this module # 0 if $arg is a command that needs to connect to an ASM instance # -1 if $arg is a command that may use an ASM instance. # # NOTES # The asmcmdamdu module currently supports no command that can run # without an ASM instance. ######## sub asmcmdamdu_is_no_instance_cmd { my ($arg) = shift; my ($rc); return 1 unless defined($asmcmdamdu_cmds{$arg}); $rc = asmcmdshare_get_cmd_noinst($arg); if ($rc eq "true") { return 1; } elsif ($rc eq "undef") { return -1; } return 0; } ######## # NAME # asmcmdamdu_parse_int_args # # DESCRIPTION # This routine parses the arguments for flag options for ASMCMDAMDU # 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 ASMCMDAMDU internal command. ######## sub asmcmdamdu_parse_int_args { my ($cmd, $args_ref) = @_; my (@string); # Use asmcmdparser_parse_issued_command() from the asmcmdparser package to # parse arguments for internal commands. These arguments are stored in @ARGV. if (!asmcmdparser_parse_issued_command($cmd, $args_ref,\@string)) { # Print correct command format if syntax error. # asmcmdamdu_syntax_error($cmd); return undef; } return 0; } ######## # NAME # asmcmdamdu_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 ASMCMDAMDU 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 asmcmdamdu_syntax_error { my ($cmd) = shift; my ($cmd_syntax); # Correct syntax for $cmd. # my ($succ) = 0; #display syntax only for commands in this module. if (asmcmdamdu_is_cmd($cmd)) { $cmd_syntax = asmcmdshare_get_help_syntax($cmd); # Get syntax for $cmd. # $cmd_syntax = asmcmdshare_trim_str ($cmd_syntax); # Trim blank spaces # asmcmdshare_printstderr 'usage: ' . $cmd_syntax . "\n"; asmcmdshare_printstderr 'help: help ' . $cmd . "\n"; $succ = 1; if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } } return $succ; } ######## # NAME # asmcmdamdu_get_cmd_syntax # # DESCRIPTION # This routine returns the help syntax of the command specified by $cmd. # # PARAMETERS # cmd (IN) - the name of the command of which we're looking up the # syntax. # # RETURNS # 1 if the command is defined; 0 otherwise ######## sub asmcmdamdu_get_cmd_syntax { my ($cmd) = shift; my $cmd_syntax; my $succ = 0; if ( asmcmdamdu_is_cmd($cmd)) { $cmd_syntax = asmcmdshare_get_help_desc($cmd); if (defined ($cmd_syntax)) { asmcmdshare_printstderr 'usage: ' . $cmd_syntax . "\n"; asmcmdshare_printstderr 'help: help ' . $cmd . "\n"; $succ = 1; } } return $succ; } ######## # NAME # asmcmdamdu_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 asmcmdamdu_get_asmcmd_cmds { return asmcmdshare_filter_invisible_cmds(%asmcmdamdu_cmds); }