# Copyright (c) 2004, 2009, Oracle and/or its affiliates. All rights reserved. # # NAME # asmcmdbase - ASM CoMmanD line interface (Base Module) # # DESCRIPTION # ASMCMD is a Perl utility that provides easy nagivation of files within # ASM diskgroups. This module contains the functionality of all # the commands that have been supported since Oracle 10g. Namely, # they are the ASM file system related commands. This module is also # responsible for the CONNECT and DISCONNECT functionality of ASMCMD. # # NOTES # usage: asmcmdcore [-v] [-a ] [-p] [command] # # MODIFIED (MM/DD/YY) # sanselva 06/25/09 - remove -r options from cp # sanselva 05/12/09 - remove type checking for cp since done on server side # sanselva 04/06/09 - ASMCMD long options and consistency # heyuen 03/23/09 - update docs # heyuen 12/03/08 - export rm_sql # heyuen 10/14/08 - use dynamic modules # heyuen 10/03/08 - stop cp when a file is invalid # heyuen 09/10/08 - make lsof deterministic # heyuen 08/26/08 - add voting file # heyuen 08/02/08 - add -V # heyuen 08/01/08 - fix windows \ # heyuen 07/28/08 - use command properties array # heyuen 06/16/08 - update help # heyuen 04/15/08 - bug 6957288, reorder help messages # heyuen 03/31/08 - add -p to ls # heyuen 02/20/08 - increase cp performance # heyuen 12/17/07 - change error number ranges # heyuen 08/02/07 - add lsof # siyarlag 09/17/07 - bug/5903321 no quotes for diskgroup name # heyuen 08/02/07 - refresh # heyuen 06/26/07 - add support for multiple source files in cp # hqian 06/07/07 - #5131203: add disk group name to lsct results # heyuen 05/25/07 - add return codes for errors # dfriedma 05/24/07 - Remove unbalanced column # hqian 05/24/07 - Use SYSASM as connection default, since bug 5873184 # is fixed # heyuen 05/09/07 - add support for cp with relative paths # pbagal 04/13/07 - Add ASMCMD comment in all SQL # heyuen 04/02/07 - changed help message for cp, fixed remote db # connection # hqian 03/09/07 - Improve error msg: give msgid # hqian 03/02/07 - -c and -g for ls and lsdg # jilim 11/16/06 - asmcmd cp feature, asmcmdbase_process_cp and # its sub-modules # hqian 02/08/07 - lrg-2839533: temporary revert default connection # type back to sysdba # hqian 08/17/06 - remove SQL dependency on init_global # hqian 07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks # averhuls 07/06/06 - prevent ls from displaying volume info. # hqian 06/15/06 - move asmcmdbase_ls_calc_min_col_wid to shared # module # hqian 02/02/06 - Bug-5007830: signal out of asmcmd if ORA-03114 # hqian 02/01/06 - Fix process_mkdir(): mkdir +dg with uninit $cre # hqian 01/27/06 - merge two error schemes into one: # asmcmdbase_display_msg # hqian 01/25/06 - Split off asmcmdshare.pm from this module # hqian 01/19/06 - More modularization # hqian 01/18/06 - #4939032: remove the main() and shell() commands # hqian 01/18/06 - #4939032: format asmcmdbase.pm into a module # hqian 01/18/06 - Rename asmcmdcore to asmcmdbase.pm, inherit history # hqian 01/18/06 - #4939032: split up asmcmdcore 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 ]. # hqian 06/30/04 - Make code that uses BigInt work for both Perl 5.6.1 # and Perl 5.8.3; take out -c # 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 Command Processing Routines # asmcmdbase_init # asmcmdbase_process_cmd # asmcmdbase_process_mkdir # asmcmdbase_process_rm # asmcmdbase_process_mkalias # asmcmdbase_process_rmalias # asmcmdbase_process_pwd # asmcmdbase_process_cd # asmcmdbase_process_ls # asmcmdbase_process_find # asmcmdbase_process_du # asmcmdbase_process_lsdg # asmcmdbase_process_lsct # asmcmdbase_process_help # asmcmdbase_process_cp # asmcmdbase_process_lsof # # Internal Command Processing Routines # asmcmdbase_ls_process_file # asmcmdbase_ls_get_subdirs # asmcmdbase_ls_subdir_print # asmcmdbase_ls_print # asmcmdbase_ls_print_hdr # asmcmdbase_ls_init_col_wid # asmcmdbase_lsdg_init_col_wid # asmcmdbase_lsdg_print # asmcmdbase_find_int # asmcmdbase_find_one # asmcmdbase_find_match # asmcmdbase_rm_prompt_conf # asmcmdbase_do_file_copy # # Sort Order Routines # asmcmdbase_name_forward # asmcmdbase_name_backward # asmcmdbase_time_forward # asmcmdbase_time_backward # asmcmdbase_rm_recur_order # asmcmdbase_levels # # Parameter Parsing Routines # asmcmdbase_parse_conn_string # asmcmdbase_parse_remote_conn_str # asmcmdbase_getchr_noecho # asmcmdbase_is_cmd # asmcmdbase_is_wildcard_cmd # asmcmdbase_is_no_instance_cmd # asmcmdbase_parse_int_args # asmcmdbase_parse_int_cmd_line # # Error Routines # asmcmdbase_syntax_error # asmcmdbase_error_msg # asmcmdbase_display_msg # asmcmdbase_signal_exception # # Initialization Routines # asmcmdbase_check_insttype # asmcmdbase_init_global # # SQL Routines # asmcmdbase_mkdir_sql # asmcmdbase_rm_sql # asmcmdbase_mkalias_sql # asmcmdbase_rmalias_sql # asmcmdbase_is_rbal # asmcmdbase_get_alias_path # asmcmdbase_get_ct # asmcmdbase_connect # asmcmdbase_disconnect # # Help Routines # asmcmdbase_get_cmd_desc # asmcmdbase_get_cmd_syntax # asmcmdbase_get_asmcmd_cmds # ############################################################################# package asmcmdbase; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(asmcmdbase_init asmcmdbase_process_cmd asmcmdbase_process_help asmcmdbase_parse_conn_string asmcmdbase_is_cmd asmcmdbase_parse_int_args asmcmdbase_parse_int_cmd_line asmcmdbase_check_insttype asmcmdbase_init_global asmcmdbase_syntax_error asmcmdbase_connect asmcmdbase_disconnect asmcmdbase_get_asmcmd_cmds asmcmdbase_get_cmd_syntax asmcmdbase_get_cmd_desc asmcmdbase_rm_sql ); use strict; use DBI; use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat); use Math::BigInt; use asmcmdglobal; use asmcmdshare; #use Term::ReadKey; # Currently not bundled with Oracle.# use List::Util qw[min max]; use POSIX qw(:termios_h); ############################ Global Constants ############################### my (%asmcmdbase_cmds) = (cd => {wildcard => 'True', no_instance => 'True', }, cp => {wildcard => '', no_instance => 'True', flags => {'i'=>'interactive', 'f'=>'force', 'r'=>'recursive'} }, du => {wildcard => 'True', no_instance => 'True', flags => {'H'=>'supressHeaders'} }, find => {wildcard => 'True', no_instance => 'True', flags => {'type=s'=>'fileType'} }, help => { }, ls => {wildcard => 'True', no_instance => 'True', flags => {'l'=>'longListing', 'i'=>'interactive', 's'=>'size', 'd'=>'directory', 'reverse'=> 'reverseOrder', 't'=>'timeStamp', 'L'=>'fileReference', 'a'=>'all', 'g'=>'global', 'H'=>'supressHeaders', 'permission'=> 'showPermission'} }, lsct => {no_instance => 'True', flags => {'g'=>'global', 'H'=>'supressHeaders'} }, lsdg => {no_instance => 'True', flags => {'discovery'=>'noCache', 'g'=>'global', 'H'=>'supressHeaders'} }, mkalias => {no_instance => 'True', }, mkdir => {no_instance => 'True', }, pwd => {no_instance => 'True', }, rmalias => {no_instance => 'True', flags => {'r'=>'recursive'} }, rm => {wildcard => 'True', no_instance => 'True', flags => {'r'=>'recursive', 'f'=>'force'} }, lsof => {no_instance => 'True', flags => {'dbname=s'=>'database', 'G=s'=>'diskGroup', 'C=s'=> 'clientInstanceName', 'H'=>'supressHeaders'} } ); my ($ASMCMDBASE_SPACE) = ' '; # Constant string for a space. # my ($ASMCMDBASE_SIXMONTH) = 183; # Number of days in six months. # my ($ASMCMDBASE_DATELEN) = 15; # Length of the date string. # my ($ASMCMDBASE_MAXPASSWD) = 256; # Max length of user passwd input # # ASMCMD Column Header Names: # Below are the names of the column headers for ls and lsdg. These headers # are the ASMCMD equivalent of the columns of v$asm_alias, v$asm_file, # v$asm_diskgroup, and v$asm_operation. In the comment to the right of each # constant is the fixed view column name that this column name corresponds to. my ($ASMCMDBASE_LS_HDR_TYPE) = 'Type'; # TYPE in v$asm_file. # my ($ASMCMDBASE_LS_HDR_REDUND) = 'Redund'; # REDUNDANCY in v$asm_file. # my ($ASMCMDBASE_LS_HDR_STRIPED) = 'Striped'; # STRIPED in v$asm_file. # my ($ASMCMDBASE_LS_HDR_TIME) = 'Time'; # MODIFICATION_TIME in v$asm_file. # my ($ASMCMDBASE_LS_HDR_SYSCRE) = 'Sys'; # SYSTEM_CREATED in v$asm_alias. # my ($ASMCMDBASE_LS_HDR_BSIZE) = 'Block_Size'; # BLOCK_SIZE in v$asm_file. # my ($ASMCMDBASE_LS_HDR_BLOCKS) = 'Blocks'; # BLOCKS in v$asm_file. # my ($ASMCMDBASE_LS_HDR_BYTES) = 'Bytes'; # BYTES in v$asm_file. # my ($ASMCMDBASE_LS_HDR_SPACE) = 'Space'; # SPACE in v$asm_file. # my ($ASMCMDBASE_LS_HDR_USER) = 'User'; # USER in v$asm_file. # my ($ASMCMDBASE_LS_HDR_GROUP) = 'Group'; # GROUP in v$asm_file. # my ($ASMCMDBASE_LS_HDR_PERM) = 'Permission'; # PERMISSION in v$asm_file. # # These declarations have to be declared with the 'our' keyward # in order for eval() to work correctly in asmcmdbase_lsdg_print(). our ($ASMCMDBASE_LSDG_HDR_INSTID) = 'Inst_ID'; # Instance ID for # gv$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_SECTOR) = 'Sector';# SECTOR_SIZE in v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_REBAL) = 'Rebal'; # OPERATION from v$asm_operation. # our ($ASMCMDBASE_LSDG_HDR_BLOCK) = 'Block'; # BLOCK_SIZE in v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_AU) = 'AU'; # ALLOCATION_UNIT_SIZE in # # v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_STATE) = 'State'; # STATE in v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_TYPE) = 'Type'; # TYPE in v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_TMB) = 'Total_MB'; # TOTAL_MB in v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_FMB) = 'Free_MB'; # FREE_MB in v$asm_diskgroup. # # REQUIRED_MIRROR_FREE_MB from v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_RMFMB) = 'Req_mir_free_MB'; # USABLE_FILE_MB from v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_UFMB) = 'Usable_file_MB'; # OFFLINE_DISKS from v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_ODISK) = 'Offline_disks'; # VOTING_FILE from v$asm_diskgroup. # our ($ASMCMDBASE_LSDG_HDR_VOTING_FILE) = 'Voting_files'; our ($ASMCMDBASE_LSDG_HDR_NAME) = 'Name'; # NAME in v$asm_diskgroup. # our (%asmcmdbase_lsof_header) = ('path_kffof', 'Path', 'dbname_kffof', 'DB_Name', 'instancename_kffof', 'Instance_Name' ); # PLSQL constants my ($PLSQL_DATAFILE) = 2; my ($PLSQL_DATAFILE_CP) = 12; my ($PLSQL_NUMBER) = 22; my ($PLSQL_VARCHAR) = 1024; sub is_asmcmd { return 1; } ################# Top Level Command Processing Routines ###################### ######## # NAME # asmcmdbase_init # # DESCRIPTION # This function initializes the asmcmdbase 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, \&asmcmdbase_process_cmd); push (@asmcmdglobal_help_callbacks, \&asmcmdbase_process_help); push (@asmcmdglobal_command_list_callbacks, \&asmcmdbase_get_asmcmd_cmds); push (@asmcmdglobal_is_command_callbacks, \&asmcmdbase_is_cmd); push (@asmcmdglobal_is_wildcard_callbacks, \&asmcmdbase_is_wildcard_cmd); push (@asmcmdglobal_syntax_error_callbacks, \&asmcmdbase_syntax_error); push (@asmcmdglobal_no_instance_callbacks, \&asmcmdbase_is_no_instance_cmd); push (@asmcmdglobal_error_message_callbacks, \&asmcmdbase_error_msg); push (@asmcmdglobal_signal_exception_callbacks, \&asmcmdbase_signal_exception); %asmcmdglobal_cmds = (%asmcmdglobal_cmds, %asmcmdbase_cmds); #Perform ASMCMD consistency check if enabled if($asmcmdglobal_hash{'consistchk'} eq 'y') { if(!asmcmdshare_check_option_consistency(%asmcmdbase_cmds)) { exit 1; } } } ######## # NAME # asmcmdbase_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; 0 if not. # # NOTES # Only asmcmdcore_shell() calls this routine. ######## sub asmcmdbase_process_cmd { my ($dbh) = @_; my ($succ) = 0; # Get current command from global value, which is set by # asmcmdbase_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 ASMCMD command. Now that ASMCMD is divided # into modules, the help command needs to be removed from this list, # because it's a global command, not a module specific command. my (%cmdhash) = ( cd => \&asmcmdbase_process_cd , du => \&asmcmdbase_process_du , find => \&asmcmdbase_process_find, ls => \&asmcmdbase_process_ls, lsct => \&asmcmdbase_process_lsct, lsdg => \&asmcmdbase_process_lsdg, mkalias => \&asmcmdbase_process_mkalias, mkdir => \&asmcmdbase_process_mkdir, pwd => \&asmcmdbase_process_pwd, rm => \&asmcmdbase_process_rm, rmalias => \&asmcmdbase_process_rmalias, cp => \&asmcmdbase_process_cp, lsof => \&asmcmdbase_process_lsof); if (defined ( $cmdhash{ $cmd } )) { # If user specifies a known command, then call routine to process it. # $cmdhash{ $cmd }->($dbh); $succ = 1; } return $succ; } ######## # NAME # asmcmdbase_process_lsof # # DESCRIPTION # This top-level routine processes the lsof command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() calls this function. ######## sub asmcmdbase_process_lsof { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($dgname, $dbname, $instname, $gnum); my ($sth, $row); my (@what, @from, @where, @order); my (@lsof_list); my ($k, $v, $h); my (%min_col_wid, $print_format, $printf_code, @what_print); # get option parameters # $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); return unless defined ($ret); $dgname = $args{'G'} if (defined($args{'G'})); $dbname = $args{'dbname'} if (defined($args{'dbname'})); $instname = $args{'C'} if (defined($args{'C'})); push (@what, 'dbname_kffof'); push (@what, 'instancename_kffof'); push (@what, 'path_kffof'); push (@from, 'x$kffof'); #filter disk group if (defined($args{'G'})) { #get disk group name $gnum = asmcmdshare_get_gnum_from_gname($dbh, $args{'G'}); if (!defined($gnum)) { return; } push (@where, "group_kffof = ". $gnum); } #filter database if (defined($args{'dbname'})) { push (@where, "dbname_kffof = '".$args{'dbname'}."'"); } #filter instance if (defined($args{'C'})) { push (@where, "instancename_kffof = '".$args{'C'}."'"); } push (@order, 'dbname_kffof'); push (@order, 'instancename_kffof'); push (@order, 'path_kffof'); $sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where, \@order); warn "$DBI::errstr\n" unless defined ($sth); #initialize the min_col_wid array foreach(@what) { $min_col_wid{$_} = length($asmcmdbase_lsof_header{$_}); } #get the rows while (defined($row = asmcmdshare_fetch($sth))) { my(%file_info) = (); while (($k,$v) = each(%{$row})) { $k =~ tr/[A-Z]/[a-z]/; $file_info{$k} = $v; $min_col_wid{$k} = max($min_col_wid{$k}, length($v)); } push (@lsof_list, \%file_info); } asmcmdshare_finish($sth); #create print format $print_format = ''; foreach (@what) { $print_format .= "%-$min_col_wid{$_}s "; } $print_format .= "\\n"; #print header if (!defined ($args{'H'}) ) { $printf_code = "printf \"$print_format\", "; @what_print = (); foreach (@what) { push (@what_print, "\'" . $asmcmdbase_lsof_header{$_} . "\'"); } $printf_code .= "(" . join (", ", @what_print) . ")"; eval $printf_code; } #print rows foreach $h (@lsof_list) { $printf_code = "printf \"$print_format\", "; @what_print = (); foreach (@what) { push (@what_print, "\'" . $h->{$_} . "\'"); } $printf_code .= "(" . join (", ", @what_print) . ")"; eval $printf_code; } } ######## # NAME # asmcmdbase_process_mkdir # # DESCRIPTION # This top-level routine processes the mkdir command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() calls this function. ######## sub asmcmdbase_process_mkdir { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($dir); # Create directory with this path name. # my ($cre); # Last level of in the path $dir; create directory # # with this name. # # 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) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # Process creation one path at a time. while (defined ($dir = shift (@ARGV))) { $dir = asmcmdshare_make_absolute($dir); $dir =~ s,/+$,,; # Remove all trailing '/'. # $dir =~ s,/([^/]+)$,,; # Do not normalize the creation level, remove. # $cre = $1; # Save creation level of path $dir here. # # If the user entered only '+dg' as an argument, then we have to # parse that as creating 'dg' in directory '+'. This will result # in an error, which is the correct behavior. if (!defined ($cre)) { $cre = $dir; # Set $cre to e.g. '+dg'. # $cre =~ s,^\+,,; # Get rid of the '+' from '+dg'. # $dir = '+'; # Set parent dir to '+'. # } %norm = asmcmdshare_normalize_path($dbh, $dir, 0, \$ret); next if ($ret != 0); # Skip creation if normalization failed. # # Fix bug that allowed creation under '+'. Setting group name to '+' # guarantees failure if trying to create under '+', which is the desired # outcome. Oracle will provide the error code. if ($dir eq '+') { $asmcmdglobal_hash{'gname'} = '+'; } $dir = $norm{'path'}->[0]; $dir .= '/' . $cre; # Reattach creation name to path. # # Run creation SQL. # asmcmdbase_mkdir_sql($dbh, $asmcmdglobal_hash{'gname'}, $dir); } return; } ######## # NAME # asmcmdbase_process_rm # # DESCRIPTION # This top-level routine processes the rm command. # # PARAMETERS # dbh (IN) - initialize database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() calls this routine. Note that rm -r removes # aliases in this order: # For each alias argument remove: # 1) All files recursively under alias. # 2) All directories recursively under alias, in order of largest # number of levels to shortest. # 3) The alias itself. # Note also that removing a user alias removes the respective system alias # as well, and vice versa. Only one of the user/system pair needs to match # a wildcard or be under a directory recursively (if -r) for the other to # be deleted as well. So please be careful when using rm! ######## sub asmcmdbase_process_rm { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my (@recur_list); # List of hashes of fields of all entries # # recursively under a directory. # my (%deleted_hash); # Hash of deleted entries. # my (@entries); # List of hashes of fields of matching entries under # # a directory. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($alias_path); # User-entered raw path for deletion. # my ($alias_name); # Alias name of a path. # my ($is_dir); # Flag: 'Y' if alias is a directory; 'N' otherwise. # my ($gnum); # Group number. # my ($gname); # Group name. # my ($par_id); # Parent ID of an alias. # my ($hash_str); # Hash string for %deleted_hash. # my ($iter); # Iteration variable for foreach() loops. # my ($spprserr) = 0; # Whether to suppress errors on normalization. # my ($i); # Iteration variable for for() loops. # # 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 == 0) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # First check flags and args to see if we should ask user for confirmation # before proceeding. We prompt the user when the -f flag is not set, when # the mode is interactive, and when at least one of these conditions # is true: # 1) -r is set, # 2) at least one argument of 'rm' contains a wildcard. # First reset $ret $ret = 0; if (! defined ($args{'f'}) && ($asmcmdglobal_hash{'mode'} eq 'i')) { if (defined ($args{'r'})) { $ret = asmcmdbase_rm_prompt_conf(); # Prompt for confirmation. # return unless ($ret == 1); } else { for ($i = 0; $i < @ARGV; $i++) { if ($ARGV[$i] =~ m,$ASMCMDGLOBAL_WCARD_CHARS,) { $ret = asmcmdbase_rm_prompt_conf(); return unless ($ret == 1); last; } } } } # Process each alias entry one at a time. (rm can take multiple alias # arguments. while (defined ($alias_path = shift (@ARGV))) { # A) First process all entries under $alias_path, if -r. if (defined ($args{'r'})) { @recur_list = asmcmdbase_find_int ($dbh, $alias_path, '*', undef, undef, 0, 0); # We want to delete all the files first before touching the # directories. Also, we want to delete dirs with deepest # number of levels first. @recur_list = sort asmcmdbase_rm_recur_order @recur_list; # Now delete one entry at a time. foreach $iter (@recur_list) { # Make sure we're not trying to delete system created dirs. if (($iter->{'alias_directory'} eq 'N') || ($iter->{'system_created'} eq 'N')) { # We hash by 1) group_number, 2) reference_index, and 3) # file_number. The concatenation of these three values is # guaranteed to be unique per file and directory. Also, the hash # values of a system alias and its user-created counterpart must # be the same. $hash_str = $iter->{'group_number'} . $iter->{'reference_index'} . $iter->{'file_number'}; # Have we deleted this file before? if (! defined ($deleted_hash{ $hash_str} )) { # Hash it so we know it's deleted. $deleted_hash{ $hash_str } = 'done'; $is_dir = $iter->{'alias_directory'}; $gname = asmcmdshare_get_gname_from_gnum ($dbh, $iter->{'group_number'}); # Run SQL to remove an entry. asmcmdbase_rm_sql($dbh, $gname, $iter->{'full_path'}, $is_dir); } } } } # B) Last process $alias_path itself. $spprserr = 1 if (defined ($args{'r'})); %norm = asmcmdshare_normalize_path($dbh, $alias_path, $spprserr, \$ret); next if ($ret != 0); # Remove one entry at a time, if $alias_path contains '*' and has multiple # matches. for ($i = 0; $i < @{ $norm{'path'} }; $i++) { $alias_name = $alias_path = $norm{'path'}->[$i]; $alias_name =~ s,.*/([^/]+)$,$1,; # Get last level of path for name. # $par_id = $norm{'par_id'}->[$i]; $gnum = $norm{'gnum'}->[$i]; # If parent index is -1, then the directory must be a diskgroup or '+'; # thus we cannot remove it. if ($par_id != -1) { asmcmdshare_get_subdirs($dbh, \@entries, $gnum, undef, $par_id, $alias_name, undef, 0, 0); $is_dir = $entries[0]->{'alias_directory'}; $gname = asmcmdshare_get_gname_from_gnum ($dbh, $gnum); # Run SQL to remove an entry. asmcmdbase_rm_sql($dbh, $gname, $alias_path, $is_dir); } } } return; } ######## # NAME # asmcmdbase_process_mkalias # # DESCRIPTION # This top-level routine processes the mkalias command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_mkalias { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($sys_a); # User-specified existing system alias. # my ($usr_a); # User-specified new user alias for creation. # my ($cre); # Last level of in the path $dir; create directory # # with this name. # # 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 != 2) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } $sys_a = shift (@ARGV); $usr_a = shift (@ARGV); $usr_a = asmcmdshare_make_absolute($usr_a); $usr_a =~ s,/+$,,; # Remove all trailing '/'. # $usr_a =~ s,/([^/]+)$,,; # Remove last level, which is to be created. # $cre = $1; # Save creation name. # # If the user entered only '+dg' as an argument, then we have to # parse that as creating the alias 'dg' in directory '+'. This # will result in an error, which is the correct behavior. if (!defined ($cre)) { $cre = $usr_a; # Set $cre to e.g. '+dg'. # $cre =~ s,^\+,,; # Get rid of the '+' from '+dg'. # $usr_a = '+'; # Set parent dir to '+'. # } # Check if system alias exists. %norm = asmcmdshare_normalize_path($dbh, $sys_a, 0, \$ret); return if ($ret != 0); $sys_a = $norm{'path'}->[0]; # Check if directory to contain new user alias exists. %norm = asmcmdshare_normalize_path($dbh, $usr_a, 0, \$ret); return if ($ret != 0); # Fix bug that allowed creation under '+'. Setting group name to '+' # guarantees failure if trying to create under '+', which is the desired # outcome. Oracle will provide the error code. $asmcmdglobal_hash{'gname'} = '+' if ($usr_a eq '+'); $usr_a = $norm{'path'}->[0]; $usr_a .= '/' . $cre; # Reattach creation name to path. # # Run SQL to create user alias. asmcmdbase_mkalias_sql($dbh, $asmcmdglobal_hash{'gname'}, $sys_a, $usr_a); return; } ######## # NAME # asmcmdbase_process_rmalias # # DESCRIPTION # This top-level routine processes the rmalias command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_rmalias { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($alias_path); # User-entered raw path for deletion. # my ($recurse) = 0; # Boolean: 1 if -r; 0 otherwise. # # Get option parameters, if any. $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); return unless defined ($ret); $recurse = 1 if (defined ($args{'r'})); # Check if number of non-option parameters are correct. if (@ARGV == 0) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # Process each alias entry one at a time. (rmalias can take multiple alias # arguments. while (defined ($alias_path = shift (@ARGV))) { # Make sure alias exists. %norm = asmcmdshare_normalize_path($dbh, $alias_path, 0, \$ret); next if ($ret != 0); $alias_path = $norm{'path'}->[0]; # Run SQL to delete user alias. asmcmdbase_rmalias_sql ($dbh, $asmcmdglobal_hash{'gname'}, $alias_path, $recurse); } } ######## # NAME # asmcmdbase_process_pwd # # DESCRIPTION # This top-level routine processes the pwd command. # # PARAMETERS # None. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_pwd { my (%args); my ($ret); # 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 != 0) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } print "$asmcmdglobal_hash{'cwdnm'}\n"; return; } ######## # NAME # asmcmdbase_process_cd # # DESCRIPTION # This top-level routine processes the cd command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_cd { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%norm); # See asmcmdshare_normalize_path() return value comments. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($dir); # Change current directory to this directory path. # my ($maxval); # Reference index value for non-directory aliases in 10gR2. # my ($ref_id); # Reference index value for an alias entry. # my ($gnum); # Group number. # # 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) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } $dir = shift (@ARGV); # Check if path is valid. %norm = asmcmdshare_normalize_path($dbh, $dir, 0, \$ret); return if ($ret != 0); # Error should already be printed. # # Since we support wildcards for CD now, make sure there is only one # match. Otherwise, report error. if (@{ $norm{'path'} } > 1) { # asmcmd: $dir: ambiguous my (@eargs) = ($dir); asmcmdshare_error_msg(8005, \@eargs); return; } $dir = $norm{'path'}->[0]; $ref_id = $norm{'ref_id'}->[0]; $gnum = $norm{'gnum'}->[0]; # Calculate maxval: # $maxval is (group_number + 1) * 2^24 - 1. $maxval = (($gnum + 1) << 24) - 1; $maxval = -2 if ($gnum == -1); # We're in '+', no need to calc maxval. # # Reference index value for non-directory aliases is 0 in 10gR1 and $maxval # in 10gR2. You can't cd to a file, so display error. if (($ref_id == $maxval) || ($ref_id == 0)) { # asmcmd: "entry '%s' does not refer to a valid directory" my (@eargs) = ($dir); asmcmdshare_error_msg(8006, \@eargs); return; } # Update global values for new current directory. $asmcmdglobal_hash{'cwdnm'} = $dir; $asmcmdglobal_hash{'cwdref'} = $ref_id; return; } ######## # NAME # asmcmdbase_process_ls # # DESCRIPTION # This top-level routine processes the ls command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_ls { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (%min_col_wid); # Hash of mininum column widths for each ls column. # my (%subdirs); # Hash of array pointers, each array containing entries # # under a directory. # my (%norm); # See asmcmdshare_normalize_path() return value comments. # # one entry returned. # my (@paths); # Array of normalized paths; $norm{'path'} dereferenced. # my (@ref_ids);#Reference indexes for @paths; $norm{'ref_id'} dereferenced. # my (@par_ids); # Parent indexes for @paths; $norm{'par_id'} dereferenced. # my (@dg_nums); # Diskgroup numbers for @paths; $norm{'gnum'} dereferenced. # my (@entry_list); # List of entries (hashes) that $alias matches after # # normalization; includes full column values # # for every file and directory. # my ($cur_date); # Current date in Julian Date. # my ($get_file_info) = 0; # boolean: true if we want file info as well. # my ($alias); # User-entered alias to be listed. # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($i); # Iteration variable for for() loops. # $cur_date = asmcmdshare_cur_julian_date ($dbh); # Get option parameters, if any. $ret = asmcmdbase_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); $alias = shift(@ARGV) if (@ARGV > 0); $alias = '' unless defined ($alias); # Defaults to '', or current dir. # # See if any entries exist; if so, find all matches for $alias. %norm = asmcmdshare_normalize_path($dbh, $alias, 0, \$ret); return unless ($ret == 0); @paths = @{ $norm{'path'} }; @ref_ids = @{ $norm{'ref_id'} }; @par_ids = @{ $norm{'par_id'} }; @dg_nums = @{ $norm{'gnum'} }; # Obtain all fields for each match; store in @entry_list, an array of # hashes, each containing one entry with all fields. for ($i = 0; $i < @paths; $i++) { my (%entry_info); $entry_info{'path'} = $paths[$i]; $entry_info{'name'} = $paths[$i]; $entry_info{'name'} =~ s,.*/(.*)$,$1,; $entry_info{'name_print'} = $entry_info{'name'}; $entry_info{'reference_index'} = $ref_ids[$i]; $entry_info{'parent_index'} = $par_ids[$i]; $entry_info{'group_number'} = $dg_nums[$i]; # In ASMCMD diskgroups and '+' are both treated as virtual directories. # Ref index for diskgroup is defined to be (group_number * 2^24). Ref # index for '+' is define to be -1. if (($entry_info{'reference_index'} == -1) || ($entry_info{'reference_index'} == $entry_info{'group_number'} << 24)) { # If the entry is a diskgroup of '+', we treat it as a directory. $entry_info{'alias_directory'} = 'Y'; push (@entry_list, \%entry_info); # Save hash entry fieldss in list. # } else { # Get file info only if we need it. $get_file_info = 1 if (defined($args{'l'}) || defined($args{'s'}) || defined($args{'permission'})); # asmcmdshare_normalize_path currently does not return all the # column values in v$asm_alias and v$asm_file, so get the remaining # column values. asmcmdshare_get_subdirs($dbh, \@entry_list, $entry_info{'group_number'}, $entry_info{'reference_index'}, $entry_info{'parent_index'}, $entry_info{'name'}, undef, 0, $get_file_info); $entry_list[$i]->{'path'} = $entry_info{'path'}; $entry_list[$i]->{'name_print'} = $entry_info{'name'}; # If the entry is a file, process the file column values from v$asm_file. if ($entry_list[$i]->{'alias_directory'} eq 'N') { asmcmdbase_ls_process_file ($dbh, $entry_list[$i], \%args, $cur_date, $get_file_info); } } } # Sort @entry_list based on combinations of the -r and -t flags. See sort # routines on sort orderings and priorities. if (defined ($args{'t'}) && defined ($args{'reverse'})) { @entry_list = sort asmcmdbase_time_backward @entry_list; } elsif (defined ($args{'t'}) && !defined ($args{'reverse'})) { @entry_list = sort asmcmdbase_time_forward @entry_list; } elsif (!defined ($args{'t'}) && defined ($args{'reverse'})) { @entry_list = sort asmcmdbase_name_backward @entry_list; } else { @entry_list = sort asmcmdbase_name_forward @entry_list; } # Diskgroups get processed separately. if ($entry_list[0]->{'reference_index'} == -1) { # We're doing an 'ls +'. # if (defined ($args{'d'})) { # 'ls -d +' should list the information for '+', but it has none, so just # print '+'. print "+\n"; } else { # Without -d, we list all directories under '+', which are all the # diskgroups. asmcmdbase_lsdg_print ($dbh, undef, \%args); } return; } # We take care of the case of 'ls -d '. if (($entry_list[0]->{'parent_index'} == -1) && (defined ($args{'d'}))) { # In case pwd is '+dg_name' and we do 'ls -d' with no argument, assign # $alias to current directory, which is a diskgroup name. $alias = $entry_list[0]->{'name'} if ($alias eq ''); # Trim the leading '+', as asmcmdbase_lsdg_print doesn't need it. # $alias =~ s,^\+,,; $alias =~ s,/+$,,; # Trim ending '+', in cases of 'ls /+'. # # Print only diskgroups specified by $alias. asmcmdbase_lsdg_print ($dbh, $alias, \%args); return; } # Prepare column widths for printing. asmcmdbase_ls_init_col_wid(\%min_col_wid); # Min width of headers. # asmcmdshare_ls_calc_min_col_wid(\@entry_list, \%min_col_wid); # Min width of all values. # # Get list of subdirs for each directory entry. Note that we loop through # @entry_list twice here; first time to get all the sub-entries so that the # minimum column width can be calculated. We then save the sub-entries in # a hash so that the second loop and print them without having to run the # SQL again. for ($i = 0; $i < @entry_list; $i++) { if (($entry_list[$i]->{'alias_directory'} eq 'Y') && (!defined ($args{'d'}))) { my (@list); @list = asmcmdbase_ls_get_subdirs($dbh, $entry_list[$i]->{'group_number'}, $entry_list[$i]->{'reference_index'}, \%args, \%min_col_wid, $cur_date); # Save the list of sub-entries in a hash so that next for loop we don't # have to call asmcmdbase_ls_get_subdirs again. $subdirs{ $entry_list[$i]->{'reference_index'} } = \@list; } } # Print column headers, now that we have the correct minimum width. # Print iff all of the following numbers are true: # 1) either -l or -s are set; # 2) -H is not set; # 3) any one or more of the following letters is true: # a) -d is set; # b) the first path matched points to a file; # c) asmcmdshare_normalize_path() returns more than one path; # d) the only path returned by asmcmdshare_normalize_path() is a directory, # it has at least one entry in it. if ((defined ($args{'l'}) || defined ($args{'s'}) || defined($args{'permission'})) && (!defined ($args{'H'})) && ( (defined ($args{'d'})) || ($entry_list[0]->{'alias_directory'} eq 'N') || (@entry_list > 1) || (@{ $subdirs{ $entry_list[0]->{'reference_index'} } } != 0))) { asmcmdbase_ls_print_hdr (\%args, \%min_col_wid); } # Print one row at a time. for ($i = 0; $i < @entry_list; $i++) { if (($entry_list[$i]->{'alias_directory'} eq 'Y') && (!defined ($args{'d'}))) { # Print the directory and its sub-entries if it's a dir and no -d. # if (@entry_list > 1) { print "\n$entry_list[$i]->{'path'}/:\n"; } asmcmdbase_ls_subdir_print($dbh, $subdirs{ $entry_list[$i]->{'reference_index'} }, \%args, \%min_col_wid); } else { # Otherwise print the entry itself. # asmcmdbase_ls_print($entry_list[$i], \%args, \%min_col_wid); } } return; } ######## # NAME # asmcmdbase_process_find # # DESCRIPTION # This top-level routine processes the find command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_find { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my (@match); # Array of hashes of all entry matches. # my ($ret); # asmcmdshare_parse_int_args() return value. # my ($path); # Conduct search under this path, wildcard allowed. # my ($search); # Search string, wildcard allowed. # my ($type); # File type to search for, if -t is set. # my ($iter); # Iteration variable for foreach() loops. # # Get option parameters, if any. $ret = asmcmdbase_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 != 2) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # Get type if --type is set. # $type = $args{'type'} if (defined($args{'type'})); $path = shift(@ARGV); $search = shift (@ARGV); # Run the internal find routine. @match = asmcmdbase_find_int($dbh, $path, $search, $type, undef, 0, 1); return if (@match == 0); # Sort entries @match = sort { $a->{'full_path'} cmp $b->{'full_path'} } @match; # Print each entry. foreach $iter (@match) { $iter->{'full_path'} .= '/' if ($iter->{'alias_directory'} eq 'Y'); print "$iter->{'full_path'}\n"; } return; } ######## # NAME # asmcmdbase_process_du # # DESCRIPTION # This top-level routine processes the du command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_du { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my ($ret); # asmcmdbase_parse_int_args() return value. # my (@match); # Array of hashes of all entry matches. # my (%file_info); # Hash of v$asm_file column values for file entries. # my ($dir); # User-entered directory path, under which du looks for files. # my ($i); # Iteration variable for for() loops. # my ($uMB); # Total amount of space used in MB, not counting redund copies. # my ($mirUMB); # Same as $uMB, but accouting for redundant copies. # my ($uMBBInt) = Math::BigInt->new('0'); # BigInt copy of $uMB. # my ($mirUMBBInt) = Math::BigInt->new('0'); # BigInt copy of $mirUMB. # my ($redund); # Redundancy value for diskgroup; 1=extern, 2=norm, 3=high. # my ($redundBInt); # BigInt copy of $redund. # my ($spaceBInt); # Column 'space' from v$asm_file, in BigInt. # my ($oneMBBInt) = Math::BigInt->new('1048576'); # BigInt for 1MB. # my ($resBInt); # Result BigInt for BigInt operations. # my ($perlv); # Current Perl Version Number. # # 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) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } $dir = shift (@ARGV); $dir = '' unless (defined ($dir)); # Find all system alias under $dir. @match = asmcmdbase_find_int ($dbh, $dir, '*', undef, undef, 1, 0); return if (@match == 0); # Calculate space used one file at at time and tally up the total. for ($i = 0; $i < @match; $i++) { # Get file information from v$asm_file. asmcmdshare_get_file($dbh, $match[$i]->{'group_number'}, $match[$i]->{'file_number'}, \%file_info); # Must use Math::BigInt to preserve accuracy. $spaceBInt = Math::BigInt->new( $file_info{'space'} ); # Divide by 1MB to get units in MB. $resBInt = $spaceBInt / $oneMBBInt; $spaceBInt = $resBInt; # Add to mirrored total $resBInt = $mirUMBBInt + $spaceBInt; $mirUMBBInt = $resBInt; # Calculate space used when not counting mirrored copies. $redund = asmcmdshare_get_redund($dbh, $match[$i]->{'group_number'}); $redundBInt = Math::BigInt->new($redund); $resBInt = $spaceBInt / $redundBInt; $spaceBInt = $resBInt; # Add to unmirrored total. $resBInt = $uMBBInt + $spaceBInt; $uMBBInt = $resBInt; } # Get Perl version number. The syntax for the members of Math::BigInt # between versions is neither backward nor forward compatible. Certain # routines exist in one version but not another, and vice versa. Other # routines changed names over different version number. Some routines' # definition even changed. All of this inconsistency is a source of major # headache. # # The ideal is to check the version number of Math::BigInt; however, the # older version of BigInt, which is bundled with Perl 5.6.1 and 10gR1, does # not offer any capability to retrieve the version number. # # Although checking the Perl version number is not foolproof, # it's the best we can do in order to decide which routines to call. $perlv = $]; if ($perlv <= 5.006001) { # Perl 5.6.1 (or earlier) as in 10gR1. # $uMB = $uMBBInt->stringify(); # Covert back to a string. # $mirUMB = $mirUMBBInt->stringify(); } else { # Any Perl version later than 5.6.1. # $uMB = $uMBBInt->bstr(); # Covert back to a string. # $mirUMB = $mirUMBBInt->bstr(); } $uMB =~ s,^\+,,; # Trim off the leading '+' (positive sign). # $mirUMB =~ s,^\+,,; if (! defined ($args{'H'})) { printf ("%7s%20s\n", 'Used_MB', 'Mirror_used_MB'); } printf ("%7s%20s\n", $uMB, $mirUMB); return; } ######## # NAME # asmcmdbase_process_lsdg # # DESCRIPTION # This top-level routine processes the lsdg command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. This routine does the # equivalent of 'ls -ls +'. ######## sub asmcmdbase_process_lsdg { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my ($ret); # asmcmdbase_parse_int_args() return value. # my ($gname); # User-entered group name. # # Get option parameters, if any. $ret = asmcmdbase_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) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } $gname = shift (@ARGV); # lsdg always returns all columns, so both -l and -s flags are needed. $args{'l'} = 'l'; $args{'s'} = 's'; asmcmdbase_lsdg_print ($dbh, $gname, \%args); return; } ######## # NAME # asmcmdbase_process_lsct # # DESCRIPTION # This top-level routine processes the lsct command. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() can call this routine. ######## sub asmcmdbase_process_lsct { my ($dbh) = shift; my (%args); # Argument hash used by getopts(). # my ($ret); # asmcmdbase_parse_int_args() return value. # my (@client); # Array of hashes of clients in v$asm_client. # my ($gname); # Group name, as entered by user. # my ($row) = ''; # One row of one client, for printing. # my (@header); my (@rowval); my ($iter); # Iteration variable for foreach() loops. # my ($global) = 0; # True iff query global view. # my (%min_col_wid); # Minimum column width to disply. # # 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) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # Check for the -g flag. if (defined($args{'g'})) { $global = 1; } $gname = shift (@ARGV); @client = asmcmdbase_get_ct($dbh, $gname, $global); return if (@client == 0); @client = sort { $a->{'instance_name'} cmp $b->{'instance_name'} } @client; $min_col_wid{'db_name'} = length('DB_Name'); $min_col_wid{'status'} = length('Status'); $min_col_wid{'software_version'} = length('Software_Version'); $min_col_wid{'compatible_version'} = length('Compatible_version'); $min_col_wid{'instance_name'} = length('Instance_Name'); $min_col_wid{'group_name'} = length('Disk_Group'); asmcmdshare_ls_calc_min_col_wid (\@client, \%min_col_wid); if ($global) { $row="%11s "; push @header , ('Instance_ID'); } # First print header. These new columns are 10gR2 or later only. if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { # For 10gR2 compatibility, we have some new columns in v$asm_client. # $row .= "%-$min_col_wid{'db_name'}s " . "%-$min_col_wid{'status'}s " . "%$min_col_wid{'software_version'}s " . "%$min_col_wid{'compatible_version'}s " . "%-$min_col_wid{'instance_name'}s " . "%-$min_col_wid{'group_name'}s\n"; push @header, ('DB_Name', 'Status', 'Software_Version', 'Compatible_version', 'Instance_Name', 'Disk_Group'); printf $row, @header if (!defined ($args{'H'})); } else { # For 10gR1 backward-compatibility, no new columns in v$asm_client. # $row .= "%-$min_col_wid{'db_name'}s " . "%-$min_col_wid{'status'}s " . "%-$min_col_wid{'instance_name'}s " . "%-$min_col_wid{'group_name'}s\n"; $row .= "%-8s %-12s %-20s %-s\n"; push @header, ('DB_Name', 'Status', 'Instance_Name', 'Disk_Group'); printf $row, @header if (!defined ($args{'H'})); } # Print one row at a time. foreach $iter (@client) { @rowval = (); if ($global) { push @rowval, ($iter->{'inst_id'}); } if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { # >= 10gR2 version. # push @rowval, ($iter->{'db_name'}, $iter->{'status'}, $iter->{'software_version'}, $iter->{'compatible_version'}, $iter->{'instance_name'}, $iter->{'group_name'}); printf $row, @rowval; } else { # 10gR1 version. # push @rowval, ($iter->{'db_name'}, $iter->{'status'}, $iter->{'instance_name'}, $iter->{'group_name'}); printf $row, @rowval; } } return; } ######## # NAME # asmcmdbase_process_help # # DESCRIPTION # This function is the help function for the asmcmdbase module. # # PARAMETERS # command (IN) - display the help message for this command. # # RETURNS # 1 if command found; 0 otherwise. ######## sub asmcmdbase_process_help { my ($command) = shift; # User-specified argument; show help on $cmd. # my ($syntax); # Command syntax for $cmd. # my ($desc); # Command description for $cmd. # my ($succ) = 0; # 1 if command found, 0 otherwise. # if (asmcmdbase_is_cmd ($command)) { # User specified a command name to look up. # $syntax = asmcmdbase_get_cmd_syntax($command); $desc = asmcmdbase_get_cmd_desc($command); print " $syntax\n" . "$desc\n"; $succ = 1; } return $succ; } ######## # NAME # asmcmdbase_process_cp # # DESCRIPTION # This top-level routine processes the cp command. # # PARAMETERS # dbh (IN) - initialize database handle, must be non-null. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_cmd() calls this routine. ######## sub asmcmdbase_process_cp { use Text::ParseWords; my ($dbh) = shift; # local auto-variables # my ($ret); my ($i); my (%args); # cmd args # my (@arg_tokens); my (@src_strs) = (); # src file args # my (@src_paths); # src file paths # my (@src_fdata); # src file data # my (@src_norm_paths); my ($src_path); my ($tgt_str); # target file/dir args # my ($tgt_path); my ($src_remote_inst) = 0; my ($tgt_remote_inst) = 0; my ($remote_conn_str) = ''; my ($src_dbh) = $dbh; my ($tgt_dbh) = $dbh; my ($src_rem_ct) = 0; #number of source remote connection parameters # get option parameters $ret = asmcmdbase_parse_int_args($asmcmdglobal_hash{'cmd'}, \%args); return unless defined ($ret); # check if src & tgt parameters are correct # if (@ARGV < 2) { asmcmdbase_syntax_error($asmcmdglobal_hash{'cmd'}); return; } # last element is the target $tgt_str = pop(@ARGV); # remaining is the source @src_strs = @ARGV; # we are not supporting multi-src files from a remote instance yet # foreach (@src_strs) { $src_rem_ct++ if ($_ =~ m':'); } #more than one source, and one of them is remote if ($src_rem_ct > 0 && @src_strs > 1) { my (@eargs) = ($src_rem_ct, @src_strs); asmcmdshare_error_msg(8008, \@eargs); } if ($src_rem_ct == 1) { $src_remote_inst = 1; @arg_tokens = "ewords(':', 0, $src_strs[0]); $remote_conn_str = $arg_tokens[0]; push (@src_paths, $arg_tokens[1]); #get and parse connection data return unless defined (asmcmdbase_parse_remote_conn_str($remote_conn_str)); } else { @src_paths = @src_strs; } # both src & target can't be of remote instance(s) # if ($src_remote_inst == 1 && $tgt_str =~ m':') { my (@eargs) = (@src_strs, $tgt_str); asmcmdshare_error_msg(8008, \@eargs); return; } if ($tgt_str =~ m':') { $tgt_remote_inst = 1; @arg_tokens = "ewords(':', 0, $tgt_str); $remote_conn_str = $arg_tokens[0]; $tgt_path = $arg_tokens[1]; #get and parse connection data return unless defined (asmcmdbase_parse_remote_conn_str($remote_conn_str)); } else { $tgt_path = $tgt_str; } # assert we don't have src and tgt remote_inst { my @eargs=(); asmcmdshare_assert(!($src_remote_inst && $tgt_remote_inst), \@eargs); } # get remote handler if needed if ($src_remote_inst || $tgt_remote_inst) { # ### remote copy ### # my ($remote_dbh, $pswd); # connect to remote instance first # $remote_dbh = asmcmdbase_connect($asmcmdglobal_hash{'usr'}, $asmcmdglobal_hash{'pswd'}, $asmcmdglobal_hash{'ident'}, $asmcmdglobal_hash{'contyp'}); # added contyp, bug-5402303 if (!defined ($remote_dbh)) { # Connection failed; record error. # my (@eargs) = ($asmcmdglobal_hash{'ident'}); asmcmdshare_error_msg(8201, \@eargs); return; } asmcmdbase_check_insttype($remote_dbh); $src_dbh = $remote_dbh if ($src_remote_inst); $tgt_dbh = $remote_dbh if ($tgt_remote_inst); } # perform file copy and path completion asmcmdbase_do_file_copy($src_remote_inst, $tgt_remote_inst, $src_dbh, $tgt_dbh, $remote_conn_str, \@src_paths, $tgt_path); # reset usr # $asmcmdglobal_hash{'usr'} = ''; $asmcmdglobal_hash{'pswd'} = ''; $asmcmdglobal_hash{'ident'} = ''; } ############################################################################## ################### Internal Command Processing Routines ##################### ######## # NAME # asmcmdbase_ls_process_file # # DESCRIPTION # This routine determines the date format for files. Dates older than six # months are displayed in MON DD YYYY format, while others are displayed in # MON DD HH24:MI:SS format. It also finds the corresponding system filename # for an alias and the corresponding alias for a system filename. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # entry_info_ref (IN/OUT) - reference to hash of column values for a file or # directory entry. # args_ref (IN) - reference to hash of user-entered command opions. # cur_date (IN) - current julian date from dual. # file_info (IN) - boolean: whether we have file info. # # RETURNS # Null. # # NOTES # Does not overwrite existing hashed values in %entry_info. ######## sub asmcmdbase_ls_process_file { my ($dbh, $entry_info_ref, $args_ref, $cur_date, $file_info) = @_; my ($related_alias); # The user or system alias for its corresponding # # system or user alias, respectively. # # Calculate dates only if we have file info from v$asm_file. if ($file_info) { # Use separate date format if date older than half a year. if (($cur_date - $entry_info_ref->{'julian_date'}) >= $ASMCMDBASE_SIXMONTH) { # If older than six months, use 'MON DD YYYY' format. # $entry_info_ref->{'date_print'} = $entry_info_ref->{'mod_date'}; } else { # Otherwise, use 'MON DD HH24:MI:SS' format. # $entry_info_ref->{'date_print'} = $entry_info_ref->{'mod_time'}; } } # Find system alias only if we have info from v$asm_file, i.e., if we're # doing ls -l or ls -s. if ($file_info && $entry_info_ref->{'system_created'} eq 'N') { # If user-alias for file, than obtain system-alias. # $related_alias = asmcmdbase_get_alias_path ($dbh, $entry_info_ref->{'group_number'}, $entry_info_ref->{'file_number'}, 'Y'); # Always display in form 'user alias => system alias' for user aliases. $entry_info_ref->{'name_print'} .= " => $related_alias"; } elsif (defined ($args_ref->{'a'}) && ($entry_info_ref->{'system_created'} eq 'Y')) { # If system-alias and -a, then find corresponding user-alias, if any. # # In the (near) future, v$file joined with v$alias will not return volume # information. Until then, we'll need the code below to supress volume # info. my($name, $not_used) = split /\./, $entry_info_ref->{'name_print'}, 2; if (($name eq 'volume') || ($name eq 'DRL')) { # do not print volume information return; } $related_alias = asmcmdbase_get_alias_path ($dbh, $entry_info_ref->{'group_number'}, $entry_info_ref->{'file_number'}, 'N'); $entry_info_ref->{'name_print'} = "$related_alias => " . $entry_info_ref->{'name_print'}; } return; } ######## # NAME # asmcmdbase_ls_get_subdirs # # DESCRIPTION # This routine is a wrapper function for asmcmdshare_get_subdirs() for ls. It # retrieves all entries under a directory by calling # asmcmdshare_get_subdir(), and sorts the entries based on the -t and/or # -r flags. It also calculates the minimum column width of each column value # based on the length of each of the results returned by # asmcmdbase_get_subdirs(). # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gnum (IN) - group number of the directory. # par_id (IN) - parent index of the directory. # args_ref (IN) - reference to hash of ls options entered by user. # min_col_wid_ref (OUT) - reference to hash of minimum column width. # cur_date (IN) - current julian date from dual. # # RETURNS # An array of hashes, each hash containing column values of a file or # directory from v$asm_alias and v$asm_file. # # ls is tricky because it can list entries in two levels: # 1) 'ls ' or 'ls -d ' # 2) 'ls ' # # In case 1), ls lists the specified alias entry itself, whereas in 2), ls # lists the contents of . For 1), asmcmdbase_process_ls() calls # asmcmdbase_ls_print() directly, whereas in 2), asmcmdbase_process_ls() # calls asmcmdbase_ls_get_subdirs() and asmcmdbase_ls_subdir_print() for # each , and asmcmdbase_ls_subdir_print() calls asmcmdbase_ls_print(). ######## sub asmcmdbase_ls_get_subdirs { my ($dbh, $gnum, $par_id, $args_ref, $min_col_wid_ref, $cur_date) = @_; my (@entries);# Entries under a dir returned by asmcmdshare_get_subdirs(). # my ($get_file_info) = 0; # boolean: true if we want file info as well. # my ($i); # Iteration variable for for() loops. # # Get file info only if we need it. $get_file_info = 1 if (defined($args_ref->{'l'}) || defined($args_ref->{'s'}) || defined($args_ref->{'permission'})); # Get all entries under a directory in a diskgroup. asmcmdshare_get_subdirs ($dbh, \@entries, $gnum, undef, $par_id, undef, undef, 0, $get_file_info); # Obtain more information on each entry, if available. for ($i = 0; $i < @entries; $i++) { # By default, the name to be print by ls is the "name" column from # v$asm_alias. However, asmcmdbase_ls_process_file() can modify this # print value if the entry is a file and not a directory. For instance, # user aliases for files are printed in the form # 'user_alias => system_alias'. $entries[$i]->{'name_print'} = $entries[$i]->{'name'}; # If it's a file, then process v$asm_file info. if ($entries[$i]->{'alias_directory'} eq 'N') { asmcmdbase_ls_process_file ($dbh, $entries[$i], $args_ref, $cur_date, $get_file_info); } } # Sort @entries according to -t and -r flags (4 combinations). if (defined ($args_ref->{'t'}) && defined ($args_ref->{'reverse'})) { @entries = sort asmcmdbase_time_backward @entries; } elsif (defined ($args_ref->{'t'}) && !defined ($args_ref->{'reverse'})) { @entries = sort asmcmdbase_time_forward @entries; } elsif (!defined ($args_ref->{'t'}) && defined ($args_ref->{'reverse'})) { @entries = sort asmcmdbase_name_backward @entries; } else { @entries = sort asmcmdbase_name_forward @entries; } # Prepare column widths for printing. asmcmdshare_ls_calc_min_col_wid(\@entries, $min_col_wid_ref); return @entries; } ######## # NAME # asmcmdbase_ls_subdir_print # # DESCRIPTION # This routine prints each entry in @list by calling asmcmdbase_ls_print() # on each entry. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # list_ref (IN) - reference to array of entries to be printed. # args_ref (IN) - reference to hash of ls options entered by user. # min_col_wid_ref (IN) - reference to hash of minimum column width. # # RETURNS # Null. # # NOTES # Only asmcmdbase_process_ls() calls this routine, after first calling # asmcmdbase_ls_get_subdirs() to retrieve all entries under a directory. # # ls is tricky because it can list entries in two levels: # 1) 'ls ' or 'ls -d ' # 2) 'ls ' # # In case 1), ls lists the specified alias entry itself, whereas in 2), ls # lists the contents of . For 1), asmcmdbase_process_ls() calls # asmcmdbase_ls_print() directly, whereas in 2), asmcmdbase_process_ls() # calls asmcmdbase_ls_get_subdirs() and asmcmdbase_ls_subdir_print() for each # , and asmcmdbase_ls_subdir_print() calls asmcmdbase_ls_print(). ######## sub asmcmdbase_ls_subdir_print { my ($dbh, $list_ref, $args_ref, $min_col_wid_ref) = @_; my ($i); # Iteration variable for for() loops. # # Print one line at a time. for ($i = 0; $i < @{$list_ref}; $i++) { asmcmdbase_ls_print($list_ref->[$i], $args_ref, $min_col_wid_ref); } return; } ######## # NAME # asmcmdbase_ls_print # # DESCRIPTION # This routine prints one single alias entry, whether a directory or a file. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # entry_ref (IN) - reference to hash of column values of an entry. # args_ref (IN) - reference to hash of ls options entered by user. # min_col_wid_ref (IN) - reference to hash of minimum column width. # # RETURNS # Null. # # NOTES # asmcmdbase_process_ls() can call this routine directly or indirectly # through asmcmdbase_ls_subdir_print(). ######## sub asmcmdbase_ls_print { my ($entry_ref, $args_ref, $min_col_wid_ref) = @_; my ($row_l) = ''; # printf() format string for ls -l. # my ($row_s) = ''; # printf() format string for ls -s. # my ($row_p) = ''; # printf() format string for ls -p. # my ($row_n) = ''; # printf() format string for alias name. # my ($row) = ''; # Concatenated printf() format string with all options. # my ($mon, $day, $time); my ($diff); # Difference of $min_col_wid_ref->{'date_print'} # # - $ASMCMDBASE_DATELEN # + 1 .# my (@print_args); # In the (near) future), v$file joined with v$alias will not return volume # information. Until then, we'll need the code belowe to supress volume info. my($name, $not_used) = split /\./, $entry_ref->{'name_print'}, 2; if (($name eq 'volume') || ($name eq 'DRL')) { # do not print volume information return; } # Set formats based on minimum column widths in %min_col_wid. $row_l = "%-$min_col_wid_ref->{'type'}" . "s " . "%-$min_col_wid_ref->{'redundancy'}" . "s " . "%-$min_col_wid_ref->{'striped'}" . "s " ; # In case the header length is longer than $ASMCMDBASE_DATELEN, # calculate difference and use it as minimum length of time or year. # Add 1 because default is at least one extra space, compensated by # one less space between the fields 'Time' and 'Sys'. $diff = $min_col_wid_ref->{'date_print'} - $ASMCMDBASE_DATELEN + 1; # MON DD HH:MI:SS or # MON DD YYYY $row_l .= "%-3s %2s %8s" . "%$diff" . "s "; # Month, day, and time/year. # $row_l .= "%-$min_col_wid_ref->{'system_created'}" . "s "; $row_s = "%$min_col_wid_ref->{'block_size'}" . "s " . "%$min_col_wid_ref->{'blocks'}" . "s " . "%$min_col_wid_ref->{'bytes'}" . "s " . "%$min_col_wid_ref->{'space'}" . "s "; $row_p = "%$min_col_wid_ref->{'user'}" . "s " . "%$min_col_wid_ref->{'group'}" . "s " . "%$min_col_wid_ref->{'perm'}" . "s "; $row_n = "%-s\n"; # Now concatenate the format strings based on whether -l and/or -s flags. $row .= $row_l if (defined ($args_ref->{'l'})); # ls -l. # $row .= $row_s if (defined ($args_ref->{'s'})); # ls -s. # $row .= $row_p if (defined ($args_ref->{'permission'}));# ls --permission. # $row .= $row_n; # Always display alias name; concatenate! # if ($entry_ref->{'alias_directory'} eq 'Y') { # Directries have the suffix '/'. # $entry_ref->{'name_print'} .= '/'; } elsif (defined ($args_ref->{'l'})) { # Date values exists for files only. # ($mon, $day, $time) = split (/\s+/, $entry_ref->{'date_print'}); } if (($entry_ref->{'alias_directory'} eq 'Y') || (($entry_ref->{'system_created'} eq 'N') && (!defined ($args_ref->{'L'})))) { $entry_ref->{'type'} = $ASMCMDBASE_SPACE; $entry_ref->{'redundancy'} = $ASMCMDBASE_SPACE; $entry_ref->{'striped'} = $ASMCMDBASE_SPACE; $mon = $ASMCMDBASE_SPACE; $day = $ASMCMDBASE_SPACE; $time = $ASMCMDBASE_SPACE; $entry_ref->{'space'} = $ASMCMDBASE_SPACE; $entry_ref->{'block_size'} = $ASMCMDBASE_SPACE; $entry_ref->{'blocks'} = $ASMCMDBASE_SPACE; $entry_ref->{'bytes'} = $ASMCMDBASE_SPACE; $entry_ref->{'user'} = $ASMCMDBASE_SPACE; $entry_ref->{'group'} = $ASMCMDBASE_SPACE; $entry_ref->{'perm'} = $ASMCMDBASE_SPACE; } @print_args = (); if (defined($args_ref->{'l'})) # ls -l. # { push (@print_args, $entry_ref->{'type'}); push (@print_args, $entry_ref->{'redundancy'}); push (@print_args, $entry_ref->{'striped'}); push (@print_args, $mon); push (@print_args, $day); push (@print_args, $time); push (@print_args, $ASMCMDBASE_SPACE); push (@print_args, $entry_ref->{'system_created'}); } if (defined($args_ref->{'s'})) # ls -s. # { push (@print_args, $entry_ref->{'block_size'}); push (@print_args, $entry_ref->{'blocks'}); push (@print_args, $entry_ref->{'bytes'}); push (@print_args, $entry_ref->{'space'}); } if (defined($args_ref->{'permission'})) # ls --permission -l. # { push (@print_args, $entry_ref->{'user'}); push (@print_args, $entry_ref->{'group'}); push (@print_args, $entry_ref->{'perm'}); } push(@print_args, $entry_ref->{'name_print'}); printf ($row, @print_args); return; } ######## # NAME # asmcmdbase_ls_print_hdr # # DESCRIPTION # This routine prints the column headers for ls. # # PARAMETERS # args_ref (IN) - reference to hash of ls options entered by user. # min_col_wid_ref (IN) - reference to hash of minimum column width. # # RETURNS # Null. ######## sub asmcmdbase_ls_print_hdr { my ($args_ref, $min_col_wid_ref) = @_; my ($hdr_l); # printf() format string for ls -l. # my ($hdr_s); # printf() format string for ls -s. # my ($hdr_p); # printf() format string for ls -p. # my ($hdr_n); # printf() format string for alias name. # my ($hdr) = ''; # Concatenated printf() format string with all options. # my (@print_args); # Set formats based on minimum column widths in %min_col_wid. $hdr_l = "%-$min_col_wid_ref->{'type'}" . "s " . "%-$min_col_wid_ref->{'redundancy'}" . "s " . "%-$min_col_wid_ref->{'striped'}" . "s " . "%-$min_col_wid_ref->{'date_print'}" . "s " . "%-$min_col_wid_ref->{'system_created'}" . "s "; $hdr_s = "%$min_col_wid_ref->{'block_size'}" . "s " . "%$min_col_wid_ref->{'blocks'}" . "s " . "%$min_col_wid_ref->{'bytes'}" . "s " . "%$min_col_wid_ref->{'space'}" . "s "; $hdr_p = "%-$min_col_wid_ref->{'user'}" . "s " . "%-$min_col_wid_ref->{'group'}" . "s " . "%-$min_col_wid_ref->{'perm'}" . "s "; $hdr_n = "%-s\n"; # Now concatenate the format strings based on whether -l and/or -s flags. $hdr .= $hdr_l if (defined ($args_ref->{'l'})); # ls -l. # $hdr .= $hdr_s if (defined ($args_ref->{'s'})); # ls -s. # $hdr .= $hdr_p if (defined ($args_ref->{'permission'})); # ls --perm. # $hdr .= $hdr_n; @print_args = (); if(defined($args_ref->{'l'})) # ls -l. # { push (@print_args, $ASMCMDBASE_LS_HDR_TYPE); push (@print_args, $ASMCMDBASE_LS_HDR_REDUND); push (@print_args, $ASMCMDBASE_LS_HDR_STRIPED); push (@print_args, $ASMCMDBASE_LS_HDR_TIME); push (@print_args, $ASMCMDBASE_LS_HDR_SYSCRE); } if(defined($args_ref->{'s'})) # ls -s. # { push (@print_args, $ASMCMDBASE_LS_HDR_BSIZE); push (@print_args, $ASMCMDBASE_LS_HDR_BLOCKS); push (@print_args, $ASMCMDBASE_LS_HDR_BYTES); push (@print_args, $ASMCMDBASE_LS_HDR_SPACE); } if(defined($args_ref->{'permission'})) # ls pl. # { push (@print_args, $ASMCMDBASE_LS_HDR_USER); push (@print_args, $ASMCMDBASE_LS_HDR_GROUP); push (@print_args, $ASMCMDBASE_LS_HDR_PERM); } push (@print_args, 'Name'); printf $hdr, @print_args; # Do not print header for ls with neither -l nor -s. return; } ######## # NAME # asmcmdbase_ls_init_col_wid # # DESCRIPTION # This routine initializes the minimum column width hash with default values # for ls. These default values are determined by the length of the values # of the printed column headers. These header values are not necessarily # the same as the column names in v$asm_alias or v$asm_file but look # similar. # # PARAMETERS # min_col_wid_ref (OUT) - reference to hash of minimum column width. # # RETURNS # Null. # # NOTES # Must call this routine or asmcmdbase_lsdg_init_col_wid() before calling # asmcmdshare_ls_calc_min_col_wid(). ######## sub asmcmdbase_ls_init_col_wid { my ($min_col_wid_ref) = shift; my ($min_time_len); # Default time format is either 'MON DD YYYY' or 'MON DD HH:MI:SS', which # are at most $ASMCMDBASE_DATELEN characters long. However, if the length # of $ASMCMDBASE_LS_HDR_TIME is larger, then that becomes the minimum # length. $min_time_len = $ASMCMDBASE_DATELEN; $min_time_len = length($ASMCMDBASE_LS_HDR_TIME) if (length($ASMCMDBASE_LS_HDR_TIME) > $ASMCMDBASE_DATELEN); $min_col_wid_ref->{'type'} = length($ASMCMDBASE_LS_HDR_TYPE); $min_col_wid_ref->{'redundancy'} = length($ASMCMDBASE_LS_HDR_REDUND); $min_col_wid_ref->{'striped'} = length($ASMCMDBASE_LS_HDR_STRIPED); $min_col_wid_ref->{'date_print'} = $min_time_len; $min_col_wid_ref->{'system_created'} = length($ASMCMDBASE_LS_HDR_SYSCRE); $min_col_wid_ref->{'block_size'} = length($ASMCMDBASE_LS_HDR_BSIZE); $min_col_wid_ref->{'blocks'} = length($ASMCMDBASE_LS_HDR_BLOCKS); $min_col_wid_ref->{'bytes'} = length($ASMCMDBASE_LS_HDR_BYTES); $min_col_wid_ref->{'space'} = length($ASMCMDBASE_LS_HDR_SPACE); $min_col_wid_ref->{'user'} = length($ASMCMDBASE_LS_HDR_USER); $min_col_wid_ref->{'group'} = length($ASMCMDBASE_LS_HDR_GROUP); $min_col_wid_ref->{'perm'} = length($ASMCMDBASE_LS_HDR_PERM); return; } ######## # NAME # asmcmdbase_lsdg_init_col_wid # # DESCRIPTION # This routine initializes the minimum column width hash with default values # for lsdg. These default values are determined by the length of the values # of the printed column headers. These header values are not necessarily # the same as the column names in v$asm_diskgroup but look similar. # # PARAMETERS # min_col_wid_ref (OUT) - reference to hash of minimum column width. # # RETURNS # Null. # # NOTES # Must call this routine or asmcmdbase_ls_init_col_wid() before calling # asmcmdshare_ls_calc_min_col_wid(). ######## sub asmcmdbase_lsdg_init_col_wid { my ($min_col_wid_ref) = shift; $min_col_wid_ref->{'inst_id'} = length($ASMCMDBASE_LSDG_HDR_INSTID); $min_col_wid_ref->{'sector_size'} = length($ASMCMDBASE_LSDG_HDR_SECTOR); $min_col_wid_ref->{'rebalance'} = length($ASMCMDBASE_LSDG_HDR_REBAL); $min_col_wid_ref->{'block_size'} = length($ASMCMDBASE_LSDG_HDR_BLOCK); $min_col_wid_ref->{'allocation_unit_size'} = length($ASMCMDBASE_LSDG_HDR_AU); $min_col_wid_ref->{'state'} = length($ASMCMDBASE_LSDG_HDR_STATE); $min_col_wid_ref->{'type'} = length($ASMCMDBASE_LSDG_HDR_TYPE); $min_col_wid_ref->{'total_mb'} = length($ASMCMDBASE_LSDG_HDR_TMB); $min_col_wid_ref->{'free_mb'} = length($ASMCMDBASE_LSDG_HDR_FMB); $min_col_wid_ref->{'required_mirror_free_mb'} = length($ASMCMDBASE_LSDG_HDR_RMFMB); $min_col_wid_ref->{'usable_file_mb'} = length($ASMCMDBASE_LSDG_HDR_UFMB); $min_col_wid_ref->{'offline_disks'} = length($ASMCMDBASE_LSDG_HDR_ODISK); $min_col_wid_ref->{'voting_files'} = length($ASMCMDBASE_LSDG_HDR_VOTING_FILE); return; } ######## # NAME # asmcmdbase_lsdg_print # # DESCRIPTION # This routine is responsible for printing diskgroups and their column # values. Both the command 'ls +' and the command 'lsdg' use this routine # to print their results. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gname (IN) - group name, as entered by user. # args_ref (IN) - reference to hash of lsdg options entered by user. # # RETURNS # Null. # # NOTES # Both asmcmdbase_process_ls() and asmcmdbase_process_lsdg() can call this # routine. ######## sub asmcmdbase_lsdg_print { my ($dbh, $gname, $args_ref) = @_; my (@dgroups); # Array of hashes of diskgroup column values, one hash # # for each diskgroup. # my (%min_col_wid); # hash of minimum column width. # my ($row_g); # printf() format string for ls -g. # my ($row_l); # printf() format string for ls -l. # my ($row_s); # printf() format string for ls -s. # my ($row_n); # printf() format string for ls -ls. # my ($row) = ''; # Concatenated printf() format string with all options. # my ($rebal); # 'Y' if diskgroup currently rebalancing; 'N' otherwise. # my ($i); # Iteration variable for for() loops. # my ($discovery) = 0; # True iff do discovery when querying view. # my ($global) = 0; # True iff query global view. # my ($printf_code); # dynamically generated printf() code for eval(). # # Check for the -c flag. if (defined($args_ref->{'discovery'})) { $discovery = 1; } # Check for the -g flag. if (defined($args_ref->{'g'})) { $global = 1; } # Obtain the list of diskgroups. If $gname is not undefined, then only # information on that diskgroup is obtained. @dgroups = asmcmdshare_get_dg($dbh, $gname, $discovery, $global); # If non returned, then diskgroup is not found. if (@dgroups == 0) { my (@eargs) = ($gname); asmcmdshare_error_msg(8001, \@eargs) if (defined ($gname)); return; } # Sort results if (defined ($args_ref->{'reverse'})) { # Reverse alphabetical order by group name. # @dgroups = sort asmcmdbase_name_backward @dgroups; } else { # Alphabetical order by group name. # @dgroups = sort asmcmdbase_name_forward @dgroups; } # Calculate column width. asmcmdbase_lsdg_init_col_wid (\%min_col_wid); asmcmdshare_ls_calc_min_col_wid (\@dgroups, \%min_col_wid); # Set up width values for instance ID for gv$asm_diskgroup. $row_g = "%$min_col_wid{'inst_id'}" . "s "; # Set up width values for printf(). $row_l = "%-$min_col_wid{'state'}" . "s " . "%-$min_col_wid{'type'}" . "s " . "%-$min_col_wid{'rebalance'}" . "s "; $row_s = "%$min_col_wid{'sector_size'}" . "s " . "%$min_col_wid{'block_size'}" . "s " . "%$min_col_wid{'allocation_unit_size'}" . "s " . "%$min_col_wid{'total_mb'}" . "s " . "%$min_col_wid{'free_mb'}" . "s "; # Column in 10gR2 or later. if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { $row_s .= "%$min_col_wid{'required_mirror_free_mb'}" . "s "; $row_s .= "%$min_col_wid{'usable_file_mb'}" . "s "; $row_s .= "%$min_col_wid{'offline_disks'}" . "s "; } # Column in 11gR2 or later. if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_11gR2) >= 0 ) { $row_s .= "%$min_col_wid{'voting_files'}" . "s "; } $row_n = "%-s\n"; $row .= $row_g if (defined ($args_ref->{'g'})); # ls -g. # $row .= $row_l if (defined ($args_ref->{'l'})); # ls -l. # $row .= $row_s if (defined ($args_ref->{'s'})); # ls -s. # $row .= $row_n; # Always display group name. # # Now generate the printf() code based on user-specified flags. $printf_code = 'printf $row, ('; if (defined ($args_ref->{'g'})) { $printf_code .= '$ASMCMDBASE_LSDG_HDR_INSTID, '; } if (defined ($args_ref->{'l'})) { $printf_code .= '$ASMCMDBASE_LSDG_HDR_STATE, $ASMCMDBASE_LSDG_HDR_TYPE, $ASMCMDBASE_LSDG_HDR_REBAL, '; } if (defined ($args_ref->{'s'})) { $printf_code .= '$ASMCMDBASE_LSDG_HDR_SECTOR, $ASMCMDBASE_LSDG_HDR_BLOCK, $ASMCMDBASE_LSDG_HDR_AU, $ASMCMDBASE_LSDG_HDR_TMB, $ASMCMDBASE_LSDG_HDR_FMB, '; if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { # Version is 10gR2 or later. # $printf_code .= '$ASMCMDBASE_LSDG_HDR_RMFMB, $ASMCMDBASE_LSDG_HDR_UFMB, $ASMCMDBASE_LSDG_HDR_ODISK, '; } # Column in 11gR2 or later. if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_11gR2) >= 0 ) { $printf_code .= '$ASMCMDBASE_LSDG_HDR_VOTING_FILE, '; } } # Always include the name. $printf_code .= '$ASMCMDBASE_LSDG_HDR_NAME) '; # Now print the header if all these conditions are met: # 1) at least one of these flags are set: -g, -l, or -s; # 2) the -H flag is not set; and # 3) we have at least one row of results. if ((defined ($args_ref->{'g'}) || defined ($args_ref->{'l'}) || defined ($args_ref->{'s'})) && !defined ($args_ref->{'H'}) && (@dgroups > 0)) { eval($printf_code); warn $@ if $@; } # Print one row at a time. for ($i = 0; $i < @dgroups; $i++) { $dgroups[$i]->{'name'} .= '/'; # Construct the printf() statement to eval() later. $printf_code = 'printf $row, ('; if (defined ($args_ref->{'g'})) { $printf_code .= q|$dgroups[$i]->{'inst_id'}, |; } if (defined ($args_ref->{'l'})) { # Find out whether this diskgroup is currently rebalacing. $rebal = asmcmdbase_is_rbal ($dbh, $dgroups[$i]->{'group_number'}); $printf_code .= q|$dgroups[$i]->{'state'}, $dgroups[$i]->{'type'}, $rebal, |; } if (defined ($args_ref->{'s'})) { if (!defined($dgroups[$i]->{'total_mb'})) { $dgroups[$i]->{'total_mb'}=0; } if (!defined($dgroups[$i]->{'free_mb'})) { $dgroups[$i]->{'free_mb'}=0; } $printf_code .= q|$dgroups[$i]->{'sector_size'}, $dgroups[$i]->{'block_size'}, $dgroups[$i]->{'allocation_unit_size'}, $dgroups[$i]->{'total_mb'}, $dgroups[$i]->{'free_mb'}, |; if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { # Version is 10gR2 or later. # $printf_code .= q|$dgroups[$i]->{'required_mirror_free_mb'}, $dgroups[$i]->{'usable_file_mb'}, $dgroups[$i]->{'offline_disks'}, |; } if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_11gR2) >= 0 ) { $printf_code .= q|$dgroups[$i]->{'voting_files'}, |; } } # Always include the disk group name. $printf_code .= q|$dgroups[$i]->{'name'}) |; # Now evaluate the printf() expression. eval($printf_code); warn $@ if $@; } return; } ######## # NAME # asmcmdbase_find_int # # DESCRIPTION # This routine uses a search string to search under paths for aliases with # names that match the search string. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # path (IN) - path under which to search, can be full or # relative; can contain the wildcard '*'. # search (IN) - the search string; can contain the wildcard '*'. # type (IN) - optional; the file type to search for if -t is set # par_id (IN) - optional; if defined, then return only matches # that have this parent index. # sys_non_dir_only (IN) - boolean; return only system alias names of only # files if true. # with_diskgroup (IN) - boolean; search v$asm_diskgroup in addition to # v$asm_alias if true; search only v$asm_alias # otherwise. # # RETURNS # An array of hashes of search result, each hash with v$asm_alias column # values. The hash also contains the full path to that file or directory. # If the search yields no matches, then return an empty array. # # NOTES # By specifying a parent index, asmcmdbase_find_int() returns at most one # match. Use this feature when you have only an alias name by want the # full path to it. # # asmcmdbase_process_du() is the only routine that calls asmcmdbase_find_int() # with sys_non_dir_only set to true. ######## sub asmcmdbase_find_int { my ($dbh, $path, $search, $type, $par_id, $sys_non_dir_only, $with_diskgroup) = @_; my (%path_rslt); # See asmcmdshare_normalize_path() return value comments. # my (@srch_rslt); # Array of hashes, one for each entry that has an alias # # name that matches $search. # my (@results); # Array of hashes, one for each entry that is found in # # this search. # my (@dgroup);# Array of hashes of all diskgroups whose name match $search. # my ($fnd_str); # The full path to an alias that is found under $path. # my ($gname); # Group name for an entry entry. # my ($iter); # Iteration variable for foreach() loops. # my ($ret);#Is -1 iff asmcmdshare_normalize_path() fails to normalize path. # # First search under v$asm_alias for alias names that match $search. # See if path is valid. Get all paths if $path contains a wildcard. %path_rslt = asmcmdshare_normalize_path ($dbh, $path, 0, \$ret); return @results if ($ret == -1); # return empty array if bad path. # # Obtain a list of all aliases in v$asm_alias that 1) matches the search # string $search, 2) has the parent index $par_id (if defined), and 3) # is a system alias for a file (if $sys_non_dir_only is true). asmcmdshare_run_find_sql ($dbh, \@srch_rslt, $par_id, $search, $type, $sys_non_dir_only); # Now we need to see if the entries returned from # asmcmdshare_run_find_sql() fall under one of the paths returned # from asmcmdshare_normalize_path(). foreach $iter (@srch_rslt) { $gname = asmcmdshare_get_gname_from_gnum($dbh, $iter->{'group_number'}); # See if the alias falls under the list of paths in %path_rslt. $fnd_str = asmcmdbase_find_one($dbh, $path_rslt{'path'}, $path_rslt{'ref_id'}, $iter->{'name'}, $gname, $iter->{'group_number'}, $iter->{'parent_index'}); # If defined, then $fnd_str contains the full path to matching alias. if (defined ($fnd_str)) { $iter->{'full_path'} = $fnd_str; # Hash the path into the entry alias. # push (@results, $iter); # Add the hash to the array of matches. # } } # Now search under v$asm_diskgroup for diskgroup names that match $search. # Search for diskgroups only of all of these conditions are met: # 1) $sys_non_dir_only is false; # 2) $with_diskgroup is true; # 3) $type is undefined; # 4) The path we're searching under normalizes to '+'. # 5) Either $par_id is undefined or is -1. if ( (! $sys_non_dir_only) && ($with_diskgroup) && (! defined ($type)) && ($path_rslt{'path'}->[0] eq '+') && ( (! defined ($par_id)) || ($par_id == -1) ) ) { # Get list of all diskgroups that match $search. @dgroup = asmcmdshare_get_dg ($dbh, $search, 0, 0); foreach $iter (@dgroup) { # Construct full path for asmcmdbase_process_find(). $iter->{'full_path'} = '+' . $iter->{'name'}; # In ASMCMD, a diskgroup is considered a pseudo-directory. We need to # set this to 'Y' so that asmcmdbase_process_find() knows to append the # '/' suffix to the path to denote that this path points to a directory. $iter->{'alias_directory'} = 'Y'; # Save in return array. push (@results, $iter); } } return @results; } ######## # NAME # asmcmdbase_find_one # # DESCRIPTION # This routine checks if the alias with the name $name and parent index # $index resides under any of the paths in the array referenced by # $paths_ref. For instance, '+dgroup/ORCL/DATAFILE/SYSTEM.256.1' resides # under '+dgroup/ORCL', but '+dgroup/ZRCL/DATAFILE/SYSTEM.284.4' does not. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # paths_ref (IN) - reference to an array of paths under which to look for # the alias name $name. # par_ids_ref (IN) - reference to an array parallel to the array $paths_ref # references; this array contains the corresponding # reference indices for deepest directory in each path; # these reference indices are used as possible parent # indices for $name; if $index matches one of the indices, # in @{$par_ids_ref}, then $name must be under the # corresponding path in @{paths_ref}. # name (IN) - name of the alias that we're searching. # gname (IN) - group name of the group in which the search is done. # gnum (IN) - corresponding group number of $gname. # index (IN) - compare this index against all indices in @{$par_ids_ref} # to determine if $name is under a path in @{$paths_ref}; # $index can be the parent index, grandparent index, great # grandparent index, etc., of $name, depending on how # deep $name resides in the directory tree. # # RETURNS # Full path to $name if $name is found under any of the paths in # @{paths_ref}; undefined if not found. # # NOTES # The alias $name is already known to exist in v$asm_alias at this point, as # asmcmdbase_find_int() has already SQL-searched the fixed view for the # search string, and $name is one of the matches that's returned. So here # the question is not whether $name is present in v$asm_alias but whether # $name resides under one of the paths in @{$paths_ref}. # # The algorithm here is to see if any of the 'ancestor' indices of $name # matches any one of the indices in @{$par_ids_ref}. If yes, then $name # is found under the desired location. # # Note that $index can match at most one index in @{$par_ids_ref}, because # all the paths we're searching are derived from one path string that can # have wildcards. Thus, all paths are unique. ######## sub asmcmdbase_find_one { my ($dbh, $paths_ref, $par_ids_ref, $name, $gname, $gnum, $index) = @_; my ($sql); # SQL query string for getting the parent index. # my ($sth); # SQL statement handle. # my ($row); # One row results returned from the SQL execution. # my ($prefix); # One path in @{$par_ids_ref}; returned by # # asmcmdbase_find_match() only if $index matches the index of # # $prefix; $prefix concatenated to $name forms the full # # path to $name. # # This condition is true if $index matches *none* of the indices in # @{$par_ids_ref}, in which case $prefix would be undefined. Otherwise, # $prefix contains the corresponding path of the matching index, and # the loop terminates because a match is found. while (! defined($prefix = asmcmdbase_find_match($paths_ref, $par_ids_ref, $index))) { # If we're in this loop, then we know $index did not match, in which case # we need to update $index to the value of the parent index that # corresponds to the reference index $index. if ($index == -1) { # Special case: we're already at '+', and still no match, so return. # return undef; } elsif ($index == ($gnum << 24)) { # If we're at diskgroup level, assign index to -1, which is defined # to be the reference index for '+' in ASMCMD. $index = -1; $name = $gname . '/' . $name; # Continue building the full path. # } else { # Otherwise, we update $index to the index of the parent using this # SQL statement. $sql = "select name, parent_index from v\$asm_alias where reference_index=$index"; $sth = asmcmdshare_do_select($dbh, $sql); return undef if (! defined ($sth)); $row = asmcmdshare_fetch($sth); asmcmdshare_finish($sth); return undef if (! defined ($row)); $index = $row->{'PARENT_INDEX'}; $name = $row->{'NAME'} . '/' . $name; # Build the full path. # } } # We get here only if there is a match. $name = $prefix . '/' . $name; # Append prefix to get full path. # $name =~ s,^\+/,\+,; # Change '+/' to '+', removing the possible '/'. # return $name; } ######## # NAME # asmcmdbase_find_match # # DESCRIPTION # This routine checks to see if $index matches any of the indices in # @{$par_ids_ref}. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # paths_ref (IN) - reference to an array of paths under which to look for # the alias name $name. # par_ids_ref (IN) - reference to an array parallel to the array $paths_ref # references; this array contains the corresponding # reference indices for deepest directory in each path; # these reference indices are used as possible parent # indices for $name; if $index matches one of the indices, # in @{$par_ids_ref}, then $name must be under the # corresponding path in @{paths_ref}. # index (IN) - compare this index against all indices in @{$par_ids_ref} # to determine if $name is under a path in @{$paths_ref}; # $index can be the parent index, grandparent index, great # grandparent index, etc., of $name, depending on how # deep $name resides in the directory tree. # # RETURNS # Corresponding path in @{$paths_ref} if there is a match; undefined # otherwise. ######## sub asmcmdbase_find_match { my ($paths_ref, $par_ids_ref, $index) = @_; my ($i); for ($i = 0; $i < @{$par_ids_ref}; $i++) { return $paths_ref->[$i] if ($par_ids_ref->[$i] == $index); } return undef; } ######## # NAME # asmcmdbase_rm_prompt_conf # # DESCRIPTION # This routine prompts the user to confirm a delete for the command 'rm'. # # PARAMETERS # None. # # RETURNS # 1 if user response is 'y'; 0 otherwise. # # NOTES # Only asmcmdbase_process_rm() calls this routine. ######## sub asmcmdbase_rm_prompt_conf { my ($response); # String to store the user response. # print "You may delete multiple files and/or directories. \n" . 'Are you sure? (y/n) '; $response = ; chomp ($response); return 1 if ($response eq 'y'); return 0; } ######## # NAME # asmcmdbase_do_file_copy # DESCRIPTION # This routine copy specified src file(s) to target file(dir) using # dbms_diskgroup PL/SQL pkgs. Source cannot be a directory. # # PARAMETERS # src_fname (IN) - the array of src file names. # tgt_fdname (IN) - target file name or directory. # remote_dbh (IN) - remote ASM instance handle given by DBI->connect() # NULL(undef) for local copy mode # # RETURNS # Zero on success; undefined on error. # # NOTES ######## sub asmcmdbase_do_file_copy { my ($src_remote_inst, $tgt_remote_inst, $src_dbh, $tgt_dbh, $remote_conn_str, $src_paths, $tgt_path) = @_; my (@norm_src_fname, @norm_tgt_fname, @src_finfo); my ($src_fil, $tgt_fil); my ($src_sth, $tgt_sth); my ($ret); my (%norm); my (@eargs); my (@paths, @ref_ids, @par_ids, @dg_nums, @entry_list, $name); my ($i); my ($tgt_name); my (%invalid_files); # normalize and validate paths first # iterate through each source file foreach (@{$src_paths}) { my $src_path = $_; my ($src_name); #my $file_to_copy_name; my $tgt_fname = $tgt_path; my @tmp; my ( $src_hdl, $tgt_hdl, $plkSz, $fileSz, $openMode ); my ( $fileType, $blkSz, $fileName, $fileGenName ); my ( $dbname, $dbid, $tsname, $fno, $plid, $sameen ); # size of pl/sql NUMBER type my $pl_sql_number = 22; # size of pl/sql VARCHAR type my $pl_sql_varchar = 1024; # 1Kbytes my $Kbytes = 1024; # normalize file paths # criteria: if it is an OS file, has to be full path, # otherwise it will be treated as a relative path # in the ASM context # check if source file exists if ( $src_path !~ m'^/') { # ASM file case # if source file name is not an an absolute OS path nor ASM path, # assume it is ASM and complete it %norm = asmcmdshare_normalize_path($src_dbh, $src_path, 0, \$ret); if ( $ret != 0 ) { @eargs = ($src_path); asmcmdshare_error_msg(8014, \@eargs); next; } $src_path = $norm{'path'}->[0]; } else { # OS file case if ( !(-r $src_path) ) { @eargs = ($src_path); asmcmdshare_error_msg(8014, \@eargs); next; } } # add the normalized file name to the array push(@norm_src_fname, $src_path); #my ($fileType, $fileSz, $blkSz); $src_name = $src_path; $src_name =~ s,.*/(.*)$,$1,; # if target doesn't start with '/', then it is an ASM relative path # every OS path must be absolute if ( $tgt_path !~ m'^[/\\]' ) { $tgt_path = asmcmdshare_make_absolute($tgt_path); # target is ASM # if target is a relative path, complete it to be a valid ASM path %norm = asmcmdshare_normalize_path($tgt_dbh, $tgt_path, 1, \$ret); # path exists, must be a directory, and need to complete it with # the source name if ( $ret == 0 ) { # complete the path $tgt_path = $norm{'path'}->[0]; @paths = @{ $norm{'path'} }; @ref_ids = @{ $norm{'ref_id'} }; @par_ids = @{ $norm{'par_id'} }; @dg_nums = @{ $norm{'gnum'} }; $name = $paths[0]; $name =~ s,.*/(.*)$,$1,; asmcmdshare_get_subdirs($tgt_dbh, \@entry_list, $dg_nums[0], $ref_ids[0], $par_ids[0], $name, undef, 0, 1); # if the directory is a diskgroup or a directory if ( ($ref_ids[0] == $dg_nums[0] << 24 ) || $entry_list[0]->{'alias_directory'} eq 'Y' ) { $tgt_name = $tgt_path . '/' . $src_name; } else { @eargs = ($tgt_name); asmcmdshare_error_msg(8014, \@eargs); next; } } else { $tgt_name = $tgt_path; } } else { # target is OS # if path exists it must be a directory if ( -d $tgt_path ) { $tgt_name = $tgt_path . '/' . $src_name; } else { $tgt_name = $tgt_path; } } push(@norm_tgt_fname, $tgt_name); } #get file information #very important to do dbms_diskgroup stuff in the end, since the connection #becomes useless after calling it foreach (@norm_src_fname) { my ($src_path) = $_; my ($fileType, $fileSz, $blkSz); my (%info); # get file information, block size, file size, file type if ( asmcmdshare_version_cmp( $asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_11gR1 ) >= 0 ) { $src_sth = $src_dbh->prepare(q{ begin dbms_diskgroup.getfileattr(:fileName, :fileType, :fileSz, :blkSz); end; }); # bind input parameters # $src_sth->bind_param( ":fileName", $src_path ); # bind ouput params # $src_sth->bind_param_inout( ":fileType", \$fileType, $PLSQL_NUMBER); $src_sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER); $src_sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER); # $sth->execute() || die $dbh->errstr; $ret = $src_sth->execute(); if (!defined $fileType || $fileType < 1 ) { my (@eargs) = ($src_path); asmcmdshare_error_msg(8012, \@eargs); warn "$DBI::errstr\n" unless defined ($ret); $invalid_files{$src_path} = 1; next; } if (!defined $blkSz || $blkSz < 1) { my (@eargs) = ($src_path); asmcmdshare_error_msg(8013, \@eargs); warn "$DBI::errstr\n" unless defined ($ret); $invalid_files{$src_path} = 1; next; } } else { my ($sameen) = 1; my ($dbname, $tsname, $dbid, $fno, $plid); # if file path starts with '/' then it is an OS file # # find file attributes, type, blkSz, fileSz(numberOfblks) # # use dbms_back_restore.readFileHeader package # for any instance release older than 11.xx $src_sth = $src_dbh->prepare(q{ begin dbms_backup_restore.readFileHeader(:fileName, :dbname, :dbid, :tsname, :fno, :fileSz, :blkSz, :plid, :sameen); end; }); # bind input params first # $src_sth->bind_param( ":fileName", $src_path ); $src_sth->bind_param( ":sameen", $sameen ); # bind ouput params # $src_sth->bind_param_inout( ":dbname", \$dbname, $PLSQL_VARCHAR ); $src_sth->bind_param_inout( ":tsname", \$tsname, $PLSQL_VARCHAR ); $src_sth->bind_param_inout( ":dbid", \$dbid, $PLSQL_NUMBER ); $src_sth->bind_param_inout( ":fno", \$fno, $PLSQL_NUMBER ); # fileSZ: total_number_lbk - 1, which is 1 lbk(logical blk) # less of v$asm_files.blocks $src_sth->bind_param_inout( ":fileSz", \$fileSz, $PLSQL_NUMBER ); $src_sth->bind_param_inout( ":blkSz", \$blkSz, $PLSQL_NUMBER ); $src_sth->bind_param_inout( ":plid", \$plid, $PLSQL_NUMBER ); $ret = $src_sth->execute(); if ( !defined $fno || $fno <= 0 ) { my @eargs = ($src_path); asmcmdshare_error_msg(8012, \@eargs); warn "$DBI::errstr\n" unless defined ($ret); $invalid_files{$src_path} = 1; next; } # for values for file type, please refer ASM PL/SQL # Fixed Package Quickstart Guide $fileType = $PLSQL_DATAFILE; #figure out file type from path } # if file type is == 2, change it to 12 (datafile copy) if ($fileType == $PLSQL_DATAFILE) { $fileType = $PLSQL_DATAFILE_CP; } $info{'fsize'} = $fileSz; $info{'blksz'} = $blkSz; $info{'ftyp'} = $fileType; push (@src_finfo, \%info); } # remove unexisting files # remove from @norm_src_fname and also from @norm_tgt_fname foreach (keys(%invalid_files)) { for ($i = 0; $i < @norm_src_fname; $i++) { if ($norm_src_fname[$i] eq $_) { splice(@norm_src_fname, $i, 1); splice(@norm_tgt_fname, $i, 1); next; } } } if (!$src_remote_inst && !$tgt_remote_inst) # local to local { for ($i = 0; $i < @norm_src_fname; $i++) { $src_sth = $src_dbh->prepare(q{ begin dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz, :src_fsiz, '','','', :dst_path, 1); end; }); print "copying $norm_src_fname[$i] -> $norm_tgt_fname[$i]\n"; $src_sth->bind_param(":src_path", $norm_src_fname[$i]); $src_sth->bind_param(":src_ftyp", $src_finfo[$i]->{'ftyp'}); $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'}); $src_sth->bind_param(":src_fsiz", $src_finfo[$i]->{'fsize'}); $src_sth->bind_param(":dst_path", $norm_tgt_fname[$i]); $ret = $src_sth->execute(); if ( !defined($ret) ) { my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]); asmcmdshare_error_msg(8016, \@eargs); warn "$DBI::errstr\n"; next; } } } elsif ($src_remote_inst) # remote to local { my (@ident, $host, $port, $sid, $src_str); for ($i = 0; $i < @norm_src_fname; $i++) { $src_sth = $tgt_dbh->prepare(q{ begin dbms_diskgroup.copy(:connect_iden, :usrname, :passwd, :src_path, :src_ftyp, :src_blksz, :src_fsiz, '','','', :dst_path, 1); end; }); @ident = split(':', $asmcmdglobal_hash{'ident'}); $host = shift @ident; $port = 1521; $port = shift @ident if (@ident == 2); $sid = shift @ident if (defined($ident[0])); $src_str = "(description=(address_list=(ADDRESS=(PROTOCOL=tcp)(HOST=".$host.")"; $src_str .= "(privilege=sysdba)(internal_logon=sysdba)(PORT=".$port.")))"; $src_str .= "(connect_data=(service_name=+ASM)(instance_name=".$sid.")))"; $src_sth->bind_param(":connect_iden", $src_str); $src_sth->bind_param(":usrname", $asmcmdglobal_hash{'usr'}); $src_sth->bind_param(":passwd", $asmcmdglobal_hash{'pswd'}); $src_sth->bind_param(":src_path", $norm_src_fname[$i]); $src_sth->bind_param(":src_ftyp", $src_finfo[$i]->{'ftyp'}); $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'}); $src_sth->bind_param(":src_fsiz", $src_finfo[$i]->{'fsize'}); $src_sth->bind_param(":dst_path", $norm_tgt_fname[$i]); $ret = $src_sth->execute(); if ( !defined($ret) ) { my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]); asmcmdshare_error_msg(8016, \@eargs); warn "$DBI::errstr\n"; next; } } } elsif ($tgt_remote_inst) # local to remote { my (@ident, $host, $port, $sid, $dst_str); for ($i = 0; $i <= $#norm_src_fname; $i++) { $src_sth = $src_dbh->prepare(q{ begin dbms_diskgroup.copy('', '', '', :src_path, :src_ftyp, :src_blksz, :src_fsiz, :connect_iden, :usrname, :passwd, :dst_path, 1); end; }); @ident = split(':', $asmcmdglobal_hash{'ident'}); $host = shift @ident; $port = 1521; $port = shift @ident if (@ident == 2); $sid = shift @ident if (defined($ident[0])); $dst_str = "(description=(address_list=(ADDRESS=(PROTOCOL=tcp)(HOST=".$host.")"; $dst_str .= "(privilege=sysdba)(internal_logon=sysdba)(PORT=".$port.")))"; $dst_str .= "(connect_data=(service_name=+ASM)(instance_name=".$sid.")))"; $src_sth->bind_param(":src_path", $norm_src_fname[$i]); $src_sth->bind_param(":src_ftyp", $src_finfo[$i]->{'ftyp'}); $src_sth->bind_param(":src_blksz", $src_finfo[$i]->{'blksz'}); $src_sth->bind_param(":src_fsiz", $src_finfo[$i]->{'fsize'}); $src_sth->bind_param(":connect_iden", $dst_str); $src_sth->bind_param(":usrname", $asmcmdglobal_hash{'usr'}); $src_sth->bind_param(":passwd", $asmcmdglobal_hash{'pswd'}); $src_sth->bind_param(":dst_path", $norm_tgt_fname[$i]); $ret = $src_sth->execute(); if ( !defined($ret) ) { my (@eargs) = ($norm_src_fname[$i], $norm_tgt_fname[$i]); asmcmdshare_error_msg(8016, \@eargs); warn "$DBI::errstr\n"; next; } } } return 0; } ############################################################################## ############################# Sort Order Routines ############################ ######## # NAME # asmcmdbase_name_forward # # DESCRIPTION # Routine for ordering a sort, used by Perl's built-in function sort(). # Order alphabetically by 1) name from v$asm_alias and 2) the full path to # that name. # # PARAMETERS # None. # # RETURNS # N/A. ######## sub asmcmdbase_name_forward { $a->{'name'} cmp $b->{'name'} or $a->{'path'} cmp $b->{'path'}; } ######## # NAME # asmcmdbase_name_backward # # DESCRIPTION # Routine for ordering a sort, used by Perl's built-in function sort(). # Order reverse-alphabetically by 1) name from v$asm_alias and 2) the full # path to that name. # # PARAMETERS # None. # # RETURNS # N/A. ######## sub asmcmdbase_name_backward { $b->{'name'} cmp $a->{'name'} or $b->{'path'} cmp $a->{'path'}; } ######## # NAME # asmcmdbase_time_forward # # DESCRIPTION # Routine for ordering a sort, used by Perl's built-in function sort(). # Order in this way: # # 1) Directory aliases always comes first, then file aliases. # 2a) If directory, sort in reverse-alphabetical order by name and then # by path, just like in asmcmdbase_name_backward(). # 2b) If file, sort by Julian date with most recent first, then reverse- # alphabetically by name and then by path. # # PARAMETERS # None. # # RETURNS # N/A. # # NOTES # We sort by Julian date in absolute number of days. As in Unix versions of # ls, time forward puts the most recently updated files first, as in ls -r. ######## sub asmcmdbase_time_forward { # If one alias is a file and another is a directory, then the directory # comes first. if ($a->{'alias_directory'} ne $b->{'alias_directory'}) { $b->{'alias_directory'} cmp $a->{'alias_directory'}; } # If both are directories, sort in same way as in asmcmdbase_name_backward(). elsif ($a->{'alias_directory'} eq 'Y') { $b->{'name'} cmp $a->{'name'} or $b->{'path'} cmp $a->{'path'}; } # If both are files, sort first by Julian date, most recent first; then # reverse alphabetically as in asmcmdbase_name_backward(). else { # In the (near) future), v$file joined with v$alias will not return volume # information. When that happens, we can get rid of the 'volume' code # below - leaving only the 'else' clause. my($a_name, $a_unused) = split /\./, $a->{'name'}, 2; my($b_name, $b_unused) = split /\./, $b->{'name'}, 2; # volumes don't have 'julian_time' or 'path' and so would generate an error if (($a_name eq 'volume') || ($b_name eq 'volume') || ($a_name eq 'DRL') || ($b_name eq 'DRL')) { $b->{'name'} cmp $a->{'name'}; } else { if (defined($a->{'julian_time'}) && defined($b->{'julian_time'})) { $b->{'julian_time'} <=> $a->{'julian_time'} or $b->{'name'} cmp $a->{'name'} or $b->{'path'} cmp $a->{'path'}; } else { $b->{'name'} cmp $a->{'name'} or $b->{'path'} cmp $a->{'path'}; } } } } ######## # NAME # asmcmdbase_time_backward # # DESCRIPTION # Routine for ordering a sort, used by Perl's built-in function sort(). # Order in this way: # # 1) Directory aliases always comes first, then file aliases. # 2a) If directory, sort in alphabetical order by name and then # by path, just like in asmcmdbase_name_forward(). # 2b) If file, sort by Julian date with most recent last, then # alphabetically by name and then by path. # # PARAMETERS # None. # # RETURNS # N/A. # # NOTES # We sort by Julian date in absolute number of days. As in Unix versions of # ls, time backward puts the most recently updated files last, as in ls -rt. ######## sub asmcmdbase_time_backward { # If one alias is a file and another is a directory, then the directory # comes first. if ($a->{'alias_directory'} ne $b->{'alias_directory'}) { $b->{'alias_directory'} cmp $a->{'alias_directory'}; } # If both are directories, sort in same way as in asmcmdbase_name_forward(). elsif ($a->{'alias_directory'} eq 'Y') { $a->{'name'} cmp $b->{'name'} or $a->{'path'} cmp $b->{'path'}; } # If both are files, sort first by Julian date, most recent last; then # reverse alphabetically as in asmcmdbase_name_forward(). else { if (defined($a->{'julian_time'}) && defined($b->{'julian_time'})) { $a->{'julian_time'} <=> $b->{'julian_time'} or $a->{'name'} cmp $b->{'name'} or $a->{'path'} cmp $b->{'path'}; } else { $a->{'name'} cmp $b->{'name'} or $a->{'path'} cmp $b->{'path'}; } } } ######## # NAME # asmcmdbase_rm_recur_order # # DESCRIPTION # Routine for ordering a sort, used by Perl's built-in function sort(). # Delete in this order: 1) files, 2) dir with deepest number of levels first. # # PARAMETERS # None. # # RETURNS # N/A. # # NOTES # Only asmcmdbase_process_rm() uses this routine. ######## sub asmcmdbase_rm_recur_order { # Delete in this order: 1) files, 2) dir with deepest number of levels first. $a->{'alias_directory'} cmp $b->{'alias_directory'} or asmcmdbase_levels($b->{'full_path'}) <=> asmcmdbase_levels($a->{'full_path'}); } ######## # NAME # asmcmdbase_levels # # DESCRIPTION # This routine returns the number of levels of subdirectries in a path. # For instance, +dgroup/ORCL/DATAFILE has three levels. # # PARAMETERS # path (IN) - a full path to an alias. # # RETURNS # The number of levels of subdirectories in a path. # # NOTES # The number of levels is always one more than the number of forward slashes # in the path. The assumption is true because $path must be normalized # before calling this routine. Normalized paths cannot have duplicate # slashes, as in '+dgroup//ORCL'. ######## sub asmcmdbase_levels { my ($path) = shift; my (@levels); # The number of levels of subdirectories in a path. # @levels = split (/\//, $path); return scalar @levels; } ############################################################################## ######################### Parameter Parsing Routines ######################### ######## # NAME # asmcmdbase_parse_conn_string # # DESCRIPTION # This routine parses the connect string into three components: # 1) user # 2) password # 3) identifier string # # PARAMETERS # cstr (IN) - the connect string to be parsed. # # RETURNS # Zero on success; undefined on error. # # NOTES # The connect string should be in the format user/password@identifier. ######## sub asmcmdbase_parse_conn_string { my ($cstr) = shift; my ($usrpswd); # The username/password portion of the connect string. # my ($ident); # The identifier portion of the connect string. # my ($usr); # The username portion of $usrpswd. # my ($pswd); # The password portion of $usrpswd, or password read from # # asmcmdshare_getpswd(). # ($usrpswd, $ident) = split (/\@/, $cstr); # Split by '@'. # return undef if ($usrpswd eq ''); # $usrpswd cannot be null string. # ($usr, $pswd) = split (/\//, $usrpswd); # Split by '/'. # # User name must contain at least one alphanumeric character and must not # start with a '-', or it's a flag and is a syntax error. return undef if ($usr !~ m,\w, || $usr =~ m,^-,); $asmcmdglobal_hash{'usr'} = $usr; # Save username globally. # # Prompt user for password if not specified on command line. $pswd = asmcmdshare_getpswd() unless defined ($pswd); $asmcmdglobal_hash{'pswd'} = $pswd; # Save password globally. # # Save ident if specified. # $asmcmdglobal_hash{'ident'} = $ident if defined ($ident); return 0; } ######## # NAME # asmcmdbase_parse_remote_conn_str # # DESCRIPTION # This routine parses the remote connect string into two components & # inquire user-input passwd: # 1) user # 2) identifier string # # PARAMETERS # cstr (IN) - the connect string to be parsed. # # RETURNS # Zero on success; undefined on error. # # NOTES # The connect string should be in the format user/password@identifier. ######## sub asmcmdbase_parse_remote_conn_str { my ($cstr) = shift; my ($ident); # The identifier portion of the connect string. # my ($usr); # The username portion of $usrpswd. # my ($pswd); my (@eargs); if ($cstr !~ m'@') # usr name must be specified followed by '@' # { @eargs = ($cstr); asmcmdshare_error_msg(8010, \@eargs); return undef; } ($usr, $ident) = split (/\@/, $cstr); # Split by '@'. # if ($usr eq '') { @eargs = ($cstr); asmcmdshare_error_msg(8010, \@eargs); return undef; } if ($ident eq '') { @eargs = ($cstr); asmcmdshare_error_msg(8011, \@eargs); return undef; } # store parsed usr name & identifier into gobal_hash table # $asmcmdglobal_hash{'usr'} = $usr; $ident =~ s/\./:/g; $asmcmdglobal_hash{'ident'} = $ident; # Prompt user for password # $pswd = asmcmdshare_getpswd(); $asmcmdglobal_hash{'pswd'} = $pswd; return 0; } ######## # NAME # asmcmdbase_getchr_noecho() # DESCRIPTION # This routine reads in usr character input without clear-text echo, thus # provide a simple way of pretecting usr privacy & security. # PARAMETERS # None. # RETURNS # The user entered password in a string. # NOTES ######## sub asmcmdbase_getchr_noecho { my $inputchr; my $term; my $fd_stdin; my $oldterm; my $echo = ECHO | ECHOK | ICANON; my $noecho = $echo & ~$echo; $fd_stdin = fileno(STDIN); $term = POSIX::Termios->new(); $term->getattr($fd_stdin); $oldterm = $term->getlflag(); # disable stdin clear-text echo so we can protect user passwd $term->setlflag($noecho); $term->setcc(VTIME, 1); $term->setattr($fd_stdin, TCSANOW); # read one character at a time for sync. purpose sysread(STDIN, $inputchr, 1); # enable stdin right away so other apps won't be affected $term->setlflag($oldterm); $term->setcc(VTIME, 0); $term->setattr($fd_stdin, TCSANOW); return $inputchr; } ######## # NAME # asmcmdbase_is_cmd # # DESCRIPTION # This routine checks if a user-entered command is one of the known ASMCMD # internal commands that belong to the base module. # # PARAMETERS # arg (IN) - user-entered command name string. # # RETURNS # True if $arg is one of the known commands, false otherwise. ######## sub asmcmdbase_is_cmd { my ($arg) = shift; return defined ( $asmcmdbase_cmds{ $arg } ); } ######## # NAME # asmcmdbase_is_wildcard_cmd # # DESCRIPTION # This routine determines if a 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. # # NOTES # Currently, only cd, du, find, ls, and rm can take wildcard as part of # their arguments. ######## sub asmcmdbase_is_wildcard_cmd { my ($arg) = shift; return defined ($asmcmdbase_cmds{ $arg }) && defined ($asmcmdbase_cmds{ $arg }{ wildcard }); } ######## # NAME # asmcmdbase_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 asmcmdbase module currently supports only the help as a command # that does not require an ASM instance. ######## sub asmcmdbase_is_no_instance_cmd { my ($arg) = shift; return !defined ($asmcmdbase_cmds{ $arg }) || !defined ($asmcmdbase_cmds{ $arg }{ no_instance }); } ######## # NAME # asmcmdbase_parse_int_args # # DESCRIPTION # This routine parses the arguments for flag options for ASMCMD 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 ASMCMD internal command. ######## sub asmcmdbase_parse_int_args { my ($cmd, $args_ref) = @_; my (@string); my ($key); #build the list of options to parse using GetOptions if($asmcmdbase_cmds{ $cmd }{ flags }) { foreach $key(keys %{$asmcmdbase_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. # asmcmdbase_syntax_error($cmd); return undef; } return 0; } ######## # NAME # asmcmdbase_parse_int_cmd_line # # DESCRIPTION # This routine parses a line of command and divides it up into tokens of # arguments, delimited by spaces. # # PARAMETERS # cmd_line (IN) - user-entered line of command, including the command # name and its arguments. # argv_ref (OUT) - Reference to an array of arguments to return, with the # command name stored as element zero of the array, and # its arguments stored as the subsequent elements; much # like the array 'argv' in C. Should be passed in as # an empty array. # # RETURNS # 0 on success, -1 on error. # # NOTES # Arguments are delimited by whitespace, unless that whitespace is enclosed # within single quotes, in which case they are considered as part of one # argument. # # Valid states for the state transition: # NO QUOTE - parsing a portion of $cmd_line that's *not* in quotes. # IN QUOTE - parsing a portion of $cmd_line that's in quotes. # SPACES - same condition for NO QUOTE is true; also true: currently # parsing the delimiter $ASMCMDBASE_SPACE before tokens, or # arguments. # # State transition diagram: # # Input -> # ---------------------------------------------------- # |State | quote | space | other | NULL | # |---------+----------+----------+----------+-------| # |NO QUOTE | IN QUOTE | SPACES* | NO QUOTE | DONE* | # |---------+---------------------+----------+-------| # |IN QUOTE | NO QUOTE | IN QUOTE | IN QUOTE | ERR | # |---------+----------+----------+----------+-------| # |SPACES | IN QUOTE | SPACES | NO QUOTE | DONE* | # |--------------------------------------------------| # # * In these cases, $token must have one complete argument, so add $token # to the output parameter array. ######## sub asmcmdbase_parse_int_cmd_line { my ($cmd_line, $argv_ref) = @_; my ($char); # One character from $cmd_line. # my ($state) = 'NO QUOTE'; my ($token) = ''; # One argument from $cmd_line. # my ($offset); # Offset to interate through $cmd_line using substr(). # my (@eargs); # Array of error arguments. # # Iterate through $cmd_line character by character using substr(). for ($offset = 0; $offset < length($cmd_line); $offset++) { $char = substr ($cmd_line, $offset, 1); if ($state eq 'NO QUOTE') { if ($char eq "'") { $state = 'IN QUOTE'; } elsif ($char eq $ASMCMDBASE_SPACE) { $state = 'SPACES'; push (@{ $argv_ref }, $token); $token = ''; } else { # $char is any non-space, non-single quote character. # $token .= $char; } } elsif ($state eq 'IN QUOTE') { if ($char eq "'") { $state = 'NO QUOTE'; } else { # $char is any non-single quote character. # $token .= $char; } } elsif ($state eq 'SPACES') { if ($char eq "'") { $state = 'IN QUOTE'; } elsif ($char ne $ASMCMDBASE_SPACE) { # $char is any non-space character. # $token .= $char; $state = 'NO QUOTE'; } else { # $char must be a space. # # Multiplie consecutive spaces encountered; do nothing. } } else { # Should never get here. Signal internal error. @eargs = ("asmcmdbase_parse_int_cmd_line_05"); asmcmdshare_signal_exception (8202, \@eargs); } } push (@{ $argv_ref }, $token); if ($state eq 'IN QUOTE') { # Error: somebody forgot to close the quote; parsing failed. # return -1; } return 0; } ############################################################################## ############################# Error Routines ################################# ######## # NAME # asmcmdbase_syntax_error # # DESCRIPTION # This routine prints the correct syntax for a command to STDERR, used # when there is a syntax error. If the command with bad syntax is asmcmd # itself, then asmcmdbase_syntax_error also calls exit() to quit out. # # PARAMETERS # cmd (IN) - user-entered command name string. # # RETURNS # Exits if cmd is "asmcmd" or "asmcmd_no_conn_str"; 1 if another command # that 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. Thus, even if exit() is called, the exit value is # zero. ######## sub asmcmdbase_syntax_error { my ($cmd) = shift; my ($cmd_syntax); # Correct syntax for $cmd. # my ($cmd_print_name) = ''; my ($succ) = 0; # Get the syntax for non -c version of asmcmd if -c is deprecated. $cmd = 'asmcmd_no_conn_str' if (! $ASMCMDGLOBAL_USE_CONN_STR && ($cmd eq 'asmcmd')); $cmd_syntax = asmcmdbase_get_cmd_syntax($cmd); # Get syntax for $cmd. # if (defined ($cmd_syntax)) { $cmd_print_name = $cmd if ($cmd ne 'asmcmd_no_conn_str' && $cmd ne 'asmcmd'); print STDERR 'usage: ' . $cmd_syntax . "\n"; print STDERR 'help: help ' . $cmd_print_name . "\n"; # Can't proceed if asmcmd has bad syntax. # exit -1 if (($cmd eq 'asmcmd') || ($cmd eq 'asmcmd_no_conn_str')); $succ = 1; } if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } return $succ; } ######## # NAME # asmcmdbase_error_msg # # DESCRIPTION # This routine is a wrapper around asmcmdbase_display_msg(), the # function responsible for displaying error messages for the asmcmdbase # module. # # This function is called by the general function asmcmdshare_error_msg, # which calls the respective _error_msg function in each module. # # Call this function to record an error, not to signal one. # # PARAMETERS # err_num (IN) - ASMCMD internal error number. # args_ref (IN) - (Optional) Reference to array of error arguments # # RETURNS # 1 if the error number is supported by the asmcmdbase module; 0 # otherwise. # # NOTES # Note that one should call this routine for recording errors and # asmcmdbase_signal_exception() for signaling exceptions. ######## sub asmcmdbase_error_msg { my ($err_num, $args_ref) = @_; my ($succ) = 0; my (@eargs); # Assert that $err_num is not within 8000-9400, inclusive. @eargs = ("asmcmdbase_error_msg_05", $err_num); asmcmdshare_assert( (($err_num > 8000) || ($err_num < 9400)) , \@eargs); $succ = asmcmdbase_display_msg($err_num, $args_ref); return $succ; } ######## # NAME # asmcmdbase_display_msg # # DESCRIPTION # This routine prints error and exception messages to STDERR for the # asmcmdbase module. # # PARAMETERS # err_num (IN) - ASMCMD internal error number. # args_ref (IN) - (Optional) Reference to array of error arguments # # RETURNS # 1 if error message is found; 0 otherwise. If an $DBI::errmsg error # is ORA-03113 or ORA-03114, then signal out. # # NOTES # This function maintains a hash of error numbers supported by this # module in order to return quickly if the error number is not # supported by asmcmdbase. # # If an error is found, this function prints the error message. ######### sub asmcmdbase_display_msg { my ($err_num, $args_ref) = @_; my ($gname) = ''; my ($path) = ''; my ($alias) = ''; my ($errmsg) = ''; # Error message from from $DBI::errstr. # my ($succ) = 0; # 1 if error message found, 0 otherwise. # my ($argument); # Argument iterator of the $args_ref array. # my ($exit) = 0; # Whether to exit ASMCMD at the end of this function. # my ($source_file) = ''; my ($target) = ''; my ($remote_connt_str) = ''; my ($file_to_copy) = ''; my ($tgt_fdname) = ''; my ($fileGenName) = ''; my ($sid) = ''; # 8001-8300 my (%error_messages) = ( 8001 => q!diskgroup '$gname' does not exist or is not mounted!, 8002 => q!entry '$alias' does not exist in directory '$path'!, 8003 => q!command disallowed by current instance type!, 8004 => q!ASM file path '$path' contains an invalid alias name!, 8005 => q!$path: ambiguous!, 8006 => q!entry '$path' does not refer to a valid directory!, 8007 => q!unclosed single-quote!, 8008 => q!both source->'$source_file' and target->'$target' can not ! . q!from remote instance(s)!, 8009 => q!no multiple source files can copy from a remote instance!, 8010 => q!no usr name is specified in remote instance ! . q!connect_string->'$remote_connt_str'!, 8011 => q!no instance identifier is specified in remote instance ! . q!connect_string->'$remote_connt_str'!, 8012 => q!can not determine file type for file->'$file_to_copy'!, 8013 => q!can not determine logical block size for file->'$file_to_copy'!, 8014 => q!can not open file->'$file_to_copy'!, 8015 => q!can not create file->'$tgt_fdname'!, 8016 => q!copy source->'$file_to_copy' and target->'$tgt_fdname' failed!, 8017 => q!error while reading data from file->'$file_to_copy'!, 8018 => q!error while writing data to file->'$fileGenName'!, 8019 => q!failed to commit file->'$fileGenName'!, 8100 => q!select statement failed!, 8101 => q!see ORA-message for details!, 8102 => q!no connection to ASM; command requires ASM to run!, 8103 => q!failed to connect to ASM; ASMCMD running in non-connected mode, ORACLE_SID='$ORACLE_SID'!, 8200 => q!end-of-file on communication channel!, 8201 => q!ORACLE not available!, 8202 => q!internal error: !, 8203 => q!diskgroup not defined!, ); $errmsg = $error_messages{$err_num}; # Error 8001, bad diskgroup. if ($err_num == 8001) { $gname = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$gname,$gname,; } # Error 8002, directory/file not found. elsif ($err_num == 8002) { $path = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $alias = $args_ref->[1] if (defined($args_ref) && defined($args_ref->[1])); $errmsg =~ s,\$alias,$alias,; $errmsg =~ s,\$path,$path,; } # Error 8003, connected to non-ASM instance. # No argument substitution necessary. # Error 8004, invalid alias name. # Error 8005, ambiguous wildcard match for CD. # Error 8006, "entry '%s' does not refer to a valid directory". elsif (($err_num == 8004) || ($err_num == 8005) || ($err_num == 8006)) { $path = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$path,$path,; } # Error 8007, "unclosed single-quote". # No argument substitution necessary. # Error 8008, no local source file or target file(dir) presented # if ($err_num == 8008) { $source_file = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$source_file,$source_file,; $target = $args_ref->[1] if (defined($args_ref) && defined($args_ref->[1])); $errmsg =~ s,\$target,$target,; } # Error 8010, no usr name or Error 8011, no instance identifier # if (($err_num == 8010) || ($err_num == 8011)) { $remote_connt_str = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$remote_connt_str,$remote_connt_str,; } # Error 8012, 8013, 8014, 8017 # if (($err_num == 8012) || ($err_num == 8013) || ($err_num == 8014) || ($err_num == 8017)) { $file_to_copy = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$file_to_copy,$file_to_copy,; } # Error 8015 # if (($err_num == 8015)) { $tgt_fdname = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$tgt_fdname,$tgt_fdname,; } # Error 8016 # if ($err_num == 8016) { $file_to_copy = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$file_to_copy,$file_to_copy,; $tgt_fdname = $args_ref->[1] if (defined($args_ref) && defined($args_ref->[1])); $errmsg =~ s,\$tgt_fdname,$tgt_fdname,; } # Error 8018, 8019 # if (($err_num == 8018) || ($err_num == 8019)) { $fileGenName = $args_ref->[0] if (defined($args_ref) && defined($args_ref->[0])); $errmsg =~ s,\$fileGenName,$fileGenName,; } # In the following cases, $DBI::errstr should already hold the appropriate # error message, as it's null statement handle or null database handle # exception. Thus, no need to run SQL. Return immediately after printing # $DBI::errstr. # Error 8100, select statement failed. # Error 8101, see ORA-message for details. # Exception 8200, ORA-03113: end-of-file on communication channel # Exception 8201, ORA-01034: ORACLE not available. elsif (($err_num == 8100) || ($err_num == 8101) || ($err_num == 8200) || ($err_num == 8201)) { $errmsg = "$DBI::errstr"; # Bug-5007830: exit if we have ORA-03113 or ORA-03114. if (($errmsg =~ m,311[34],) || ($errmsg =~ m,3135,)) { $exit = 1; } } elsif ( $err_num == 8103 ) { if (defined($args_ref) && defined($args_ref->[0])) { $sid = $args_ref->[0]; } else { $sid = 'undefined'; } $errmsg =~ s,\$ORACLE_SID,$sid,; } # Exception 8202, asmcmd internal error. Note that the Oracle equivalent # here is ORA-00600, but there is no predictable way to force that error, # so print asmcmd message instead. elsif ($err_num == 8202) { foreach $argument (@{$args_ref}) { $errmsg .= "[$argument] "; } } if (defined ($errmsg)) { $succ = 1; if ($err_num == 8100 || $err_num == 8201) { # Do not include ASMCMD error ID for error 8100, because this # type of error already has the ORA-xxxxx error ID. print STDERR $errmsg . "\n"; } else { print STDERR 'ASMCMD-0' . $err_num . ': ' . $errmsg . "\n"; } } if ($exit) { exit 1; } return $succ; } ######## # NAME # asmcmdbase_signal_exception # # DESCRIPTION # This routine is a wrapper around asmcmdbase_display_msg(), the # function responsible for displaying error messages for the asmcmdbase # module. This function should be called only to signal exceptions. # # PARAMETERS # exception_num (IN) - ASMCMD internal error/exception number. # args_ref (IN) - (Optional) Reference to array of error arguments # # RETURNS # 1 if the error number is supported by the asmcmdbase module; 0 # otherwise. # # NOTES # Only call this routine for exceptions. The caller of this function # always exits 1. ######## sub asmcmdbase_signal_exception { my ($exception_num, $args_ref) = @_; my ($succ) = 0; # Assert that $exception_num is within 8000-9400, inclusive. if (($exception_num < 8000) || ($exception_num > 9400)) { die "asmcmd: 8202 internal error: [asmcmdbase_signal_exception_05] " . "[$exception_num]\n"; } $succ = asmcmdbase_display_msg($exception_num, $args_ref); return $succ; } ############################################################################## ########################## Initialization Routines ########################### ######## # NAME # asmcmdbase_check_insttype # # DESCRIPTION # This routine checks the instance type of the connected instance to see # if it is an ASM instance. If not, it prints error and exits 0. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null; may not return if exit 0. # # NOTES # This routine should be called before executing any asmcmd commands. ######## sub asmcmdbase_check_insttype { my ($dbh) = shift; my ($insttype); # Instance type of the connected instance. # $insttype = asmcmdshare_get_insttype ($dbh);# Get instance type using SQL. # # It's 'ASM' in 10gR2 and 'asm' in 10gR1. if ( ($insttype ne 'ASM') && ($insttype ne 'asm') ) { asmcmdshare_error_msg(8003, undef); exit 0; } return; } ######## # NAME # asmcmdbase_init_global # # DESCRIPTION # This routine initializes the global variables in the hash # %asmcmdglobal_hash. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # # RETURNS # Null. # # NOTES # ######## sub asmcmdbase_init_global { my ($dbh) = shift; # Default initial directory is '+'. $asmcmdglobal_hash{'cwdnm'} = '+'; # Default group is first the first group. $asmcmdglobal_hash{'cwdref'} = 1 << 24; $asmcmdglobal_hash{'gnum'} = 1; } ############################################################################## ############################## SQL Routines ################################## ######## # NAME # asmcmdbase_mkdir_sql # # DESCRIPTION # This routine constructs the SQL used to create a new directory and calls # asmcmdshare_do_stmt() to execute it. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gname (IN) - name for diskgroup in which to create the directory. # dir (IN) - full path to the directory to be created. # # RETURNS # Null. ######## sub asmcmdbase_mkdir_sql { my ($dbh, $gname, $dir) = @_; my ($ret, $qry); $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" add directory '$dir'"; $ret = asmcmdshare_do_stmt($dbh, $qry); if (!defined ($ret)) { warn "$DBI::errstr\n"; if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } } return; } ######## # NAME # asmcmdbase_rm_sql # # DESCRIPTION # This routine constructs the SQL used to drop a file or directory from # a diskgroup. It calls asmcmdshare_do_stmt() to execute it. # # PARAMETERS # dbh (IN) - database handle. # gname (IN) - name of diskgroup where to drop the file or directory. # alias (IN) - full path to the alias to be dropped. # is_dir (IN) - 'Y' if $alias is a directory, 'N' otherwise. # # RETURNS # Null. ######## sub asmcmdbase_rm_sql { my ($dbh, $gname, $alias, $is_dir) = @_; my ($ret, $qry); # Construct the correct SQL for either a directory or a file. if ($is_dir eq 'Y') { # Drop directory! # $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop directory '$alias'"; } else { # Drop file! # $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop file '$alias'"; } $ret = asmcmdshare_do_stmt($dbh, $qry); warn "$DBI::errstr\n" unless defined ($ret); if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } return; } ######## # NAME # asmcmdbase_mkalias_sql # # DESCRIPTION # This routine constructs the SQL used to create a user alias. It calls # asmcmdshare_do_stmt() to execute it. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gname (IN) - name of diskgroup where to create the user alias. # sys_a (IN) - full path to the system alias to which the new user alias # will point. # usr_a (IN) - full path specifying the location for the new user alias. # # RETURNS # Null. ######## sub asmcmdbase_mkalias_sql { my ($dbh, $gname, $sys_a, $usr_a) = @_; my ($ret, $qry); $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" add alias '$usr_a' for '$sys_a'"; $ret = asmcmdshare_do_stmt($dbh, $qry); if (!defined ($ret)) { warn "$DBI::errstr\n"; if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } } return; } ######## # NAME # asmcmdbase_rmalias_sql # # DESCRIPTION # This routine constructs the SQL used to drop a user alias or to drop # recursively a directory tree that contains only user aliases. It # calls asmcmdshare_do_stmt() to execute it. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gname (IN) - name of diskgroup where to drop $alias. # alias (IN) - path specifying the location of the alias to be dropped. # recurse (IN) - 1 iff $alias points to a directory to be dropped # recursively. # # RETURNS # Null. ######## sub asmcmdbase_rmalias_sql { my ($dbh, $gname, $alias, $recurse) = @_; my ($ret, $qry); if ($recurse == 1) { # Drop directory force! # $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop directory '$alias' force"; } else { # Drop user alias! # $qry = "alter diskgroup /*ASMCMD*/ \"$gname\" drop alias '$alias'"; } $ret = asmcmdshare_do_stmt($dbh, $qry); if (!defined ($ret)) { warn "$DBI::errstr\n"; if ($asmcmdglobal_hash{'mode'} eq 'n') { $asmcmdglobal_hash{'e'} = -1; } } } ######## # NAME # asmcmdbase_is_bal # # DESCRIPTION # This routine constructs the SQL used to determine whether diskgroup number # $gnum is currently rebalancing. It calls asmcmdshare_do_select() to # execute it. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gnum (IN) - group number of the diskgroup in question. # # RETURNS # String: 'Y' if diskgroup $gnum is currently rebalancing; 'N' otherwise. ######## sub asmcmdbase_is_rbal { my ($dbh, $gnum) = @_; my ($sth); # SQL statement handler. # my ($qry); # SQL query string. # my ($row); # One row of query results. # my ($rebal) = 'N'; # Return string; see RETURN above. # $qry = 'select operation from v$asm_operation where group_number=' . $gnum; $sth = asmcmdshare_do_select($dbh, $qry); $row = asmcmdshare_fetch($sth); $rebal = 'Y' if (defined ($row) && ($row->{'OPERATION'} eq 'REBAL')); asmcmdshare_finish ($sth); return $rebal; } ######## # NAME # asmcmdbase_get_alias_path # # DESCRIPTION # This routine constructs the SQL used to retrieve either a system or a # user alias name when given a group number and a file number. It calls # asmcmdshare_do_select() to execute it. Finally, it calls # asmcmdbase_find_int() to retrieve the full path to the user or system # alias in question. # # PARAMETERS # dbh (IN) - initialized database handle, must be non-null. # gnum (IN) - group number for the file in question. # fnum (IN) - file number for the file in question. # sys_cre (IN) - 'Y' if we query for system alias; 'N' if we query for user # alias. # # RETURNS # Full path to the request alias; 'none' if not found (only when sys_cre # is 'N'. # # NOTES # ######## sub asmcmdbase_get_alias_path { my ($dbh, $gnum, $fnum, $sys_cre) = @_; my ($sth, $qry, $row); my ($name); # Name of the retrieved alias. # my ($par_id); # Parent index of the retrieved alias. # my ($path); # Full path to the retrieved alias. # my (@results); # Array of hashes: return-format of asmcmdshare_find_int(). # $qry = 'select name, parent_index from v$asm_alias where group_number=' . $gnum . ' and file_number=' . $fnum . ' and system_created=\'' . $sys_cre . '\''; $sth = asmcmdshare_do_select($dbh, $qry); $row = asmcmdshare_fetch($sth); $name = $row->{'NAME'}; $par_id = $row->{'PARENT_INDEX'}; asmcmdshare_finish($sth); # If sys_cre is 'N', then the alias may not exist. Return 'none'. return 'none' unless (defined ($name)); # Retrieve the full path to the alias. @results = asmcmdbase_find_int ($dbh, '+', $name, undef, $par_id, 0, 0); $path = $results[0]->{'full_path'}; return $path; } ######## # NAME # asmcmdbase_get_ct # # DESCRIPTION # This routine constructs the SQL used to fetch a list of row(s) from # v$asm_client or gv$asm_client. # # PARAMETERS # dbh (IN) - database handle. # gname (IN) - optional: limit select by this group number. # # RETURNS # An array containing zero or one or more rows from v$asm_client. The column # values for each row are stored in a hash, the reference to which # is indexed in the array. ######## sub asmcmdbase_get_ct { my ($dbh, $gname, $global) = @_; my ($sth, $qry, $row); my (@ct_list); # The return array of hashes; see RETURNS above. # my ($gnum); # The group number for the diskgroup specified by $gname. # if ($global) { $qry = 'select * from gv$asm_client'; } else { $qry = 'select * from v$asm_client'; } # Narrow select if $gname is specified. if (defined ($gname)) { $gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname); # Get group num. # return @ct_list unless (defined ($gnum)); # Group must exist. # $qry = $qry . ' where group_number=' . $gnum; } $sth = asmcmdshare_do_select($dbh, $qry); # Fetch results row by row and storeeach row in %ct_info, and reference # each %ct_info in @ct_list. while (defined ($row = asmcmdshare_fetch($sth))) { my (%ct_info); # Allocate fresh hash for next row. # $ct_info{'group_number'} = $row->{'GROUP_NUMBER'}; $ct_info{'instance_name'} = $row->{'INSTANCE_NAME'}; $ct_info{'db_name'} = $row->{'DB_NAME'}; $ct_info{'status'} = $row->{'STATUS'}; $ct_info{'group_name'} = asmcmdshare_get_gname_from_gnum($dbh, $ct_info{'group_number'}); # New columns for 10gR2, so check for forward compatiblity. if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'}, $ASMCMDGLOBAL_VER_10gR2) >= 0 ) { $ct_info{'software_version'} = $row->{'SOFTWARE_VERSION'}; $ct_info{'compatible_version'} = $row->{'COMPATIBLE_VERSION'}; } # If we want to see global information if ($global) { $ct_info{'inst_id'} = $row->{'INST_ID'}; } push (@ct_list, \%ct_info); } asmcmdshare_finish($sth); return (@ct_list); } ######## # NAME # asmcmdbase_connect # # DESCRIPTION # This routine initializes the database handle by establishing a connection # to an ASM instance, either with a bequeath connection or by the listener. # # PARAMETERS # usr (IN) - username of the connect string. # pswd (IN) - password for the username. # ident (IN) - the identifier part of the connect string. # # RETURNS # An initialized database handle; undefined if connect fails. # # NOTES # The connect string is in the form of [/password][@connect_identifier]. # The identifier is optional. If no identifier is in the connect string, # then we use the value of the ORACLE_SID environment variable as the # identifier. # # The identifier can have one, two, or three tokens, separately by ':'. # # If one token, then that token contains the SID. # # If two tokens, as in # 'token1:token2', # then token1 is the hostname of the host where listener is run, and token2 # is the SID. # # If three tokens, as in # 'token1:token2:token3', # then token1 is the hostname of the host where listener is run, token2 is # the port where the listener is run, and token3 is the SID. # # Must call this routine before calling any routines that require an # initialized database handle. ######## sub asmcmdbase_connect { my ($usr, $pswd, $ident, $contype) = @_; my ($dbh); # Database handle, to be initialized and returned. # my ($driver); # Driver parameter for the DBI->connect() call. # my ($token1, $token2, $token3); # Three tokens of $ident; see NOTES. # my ($host); # The hostname of the host where listener is run. # my ($port); # The port where the listener is run. # my ($sid); # The SID of the ASM instance we're connecting to. # my (%session_mode); # bug-5402303, session attr for DBI->connect(). # my (@eargs); # Error arguments for assert. # # Verify connection admin type, sysdba or sysasm, and assign proper ora # # session mode. (sysasm = 32768 or sysdba = 2) # if (($contype =~ /^sysdba$/i)) { $session_mode{'ora_session_mode'} = 2; } else { $session_mode{'ora_session_mode'} = 32768; } $session_mode{'PrintError'} = 0; $driver = 'dbi:Oracle:'; if ($ident ne '') { # Identifier exists, so parse into tokens as described above in NOTES. ($token1, $token2, $token3) = split (/:/, $ident, 3); if (! defined ($token2)) { $sid = $token1; } elsif (! defined ($token3)) { $host = $token1; $sid = $token2; } else { $host = $token1; $port = $token2; $sid = $token3; } # Now we have the values for $host, $port, and $sid, finish contructing # the $driver string. if (defined ($host)) { $driver .= 'host=' . $host; } else { # Default host string is 'localhost'. # $driver .= 'host=localhost'; } if (defined ($port)) { $driver .= ';port=' . $port; } # No port specification needed by default; defaults to 1521. # $driver .= ';sid=' . $sid; # SID always required. # } elsif (defined ($ENV{'ORACLE_SID'}) && ($usr ne '')) { # No identifier in connect string, but username specified, use # # ORACLE_SID as SID and 'localhost' as host. # $driver .= 'host=localhost;sid=' . $ENV{'ORACLE_SID'}; } # Now try to connect! ora_session_mode => 2 means to connect as sysdba $dbh = DBI->connect($driver, $usr, $pswd, \%session_mode); return $dbh; } ######## # NAME # asmcmdbase_disconnect # # DESCRIPTION # This routine disconnects from the ASM instance, given an initialized # database handle. # # PARAMETERS # dbh (IN) - initialized database handle. # # RETURNS # Undefined if $dbh is undefined; the return value of $dbh->disconnect() # upon success. ######## sub asmcmdbase_disconnect { my $dbh = shift; return undef unless(defined $dbh); return $dbh->disconnect; } ############################################################################## ############################## Help Routines ################################# ######## # NAME # asmcmdbase_get_cmd_desc # # DESCRIPTION # This routine returns the help description of the command specified by $cmd. # # PARAMETERS # cmd (IN) - the name of the command of which we're looking up the # description. # # RETURNS # The description paragraph(s) for command $cmd; undefined if $cmd does not # exist. ######## sub asmcmdbase_get_cmd_desc { my ($cmd) = shift; my (%cmd_desc); # Hash storing the description for each internal command. # $cmd_desc{'asmcmd_no_conn_str'} = ' The environment variables ORACLE_HOME and ORACLE_SID determine the instance to which the program connects, and ASMCMD establishes a bequeath connection to it, in the same manner as a SQLPLUS / AS SYSASM. The user must be a member of the OSASM group. Specifying the -V option prints the asmcmd version number and exits immediately. Specifying the -v option prints extra information that can help advanced users diagnose problems. Specify the -a option to choose the type of connection. There are only two possibilities: connecting as SYSASM or as SYSDBA. The default value if this option is unspecified is SYSASM. Specifying the -p option allows the current directory to be displayed in the command prompt, like so: ASMCMD [+DATA/ORCL/CONTROLFILE] > [command] specifies one of the following commands, along with its parameters. Type "help [command]" to get help on a specific ASMCMD command.'; $cmd_desc{'asmcmd'} = ' Specify the connect string after the -c option to use the listener to connect. By default, ASMCMD asks for a password in a non-echoing prompt, unless the password is specified as part of the connect string. The connect identifier is in the form of "host:port:sid", with the host and the port being option. Host defaults to "localhost" and port to 1521. If the sid is not specified as a part of the connect identifier, then it is read from the environment variable ORACLE_SID. If no connect string is used, then the environment variables ORACLE_HOME and ORACLE_SID determine the instance to which the program connects, and asmcmd establishes a bequeath connection to the it, in the same manner as a SQLPLUS / AS SYSASM. The user must be a member of the SYSDBA group to exercise this option. Specifying the -V option prints the asmcmd version number and exits immediately. Specifying the -v option prints extra information that can help advanced users diagnose problems. Specify the -a option to choose the type of connection. There are only two possibilities: connecting as SYSASM or as SYSDBA. The default value if this option is unspecified is SYSDBA. Specifying the -p option allows the current directory to be displayed in the command prompt, like so: ASMCMD [+DATA/ORCL/CONTROLFILE] > The parameter command specifies one of the following commands, along with its parameters. Type "help [command]" to get help on a specific ASMCMD command.'; $cmd_desc{'cd'} = ' Change the current directory to .'; $cmd_desc{'du'} = ' Display total space used for files located recursively under [dir], similar to "du -s" under UNIX; default is the current directory. Two values are returned, both in units of megabytes. The first value does not take into account mirroring of the diskgroup while the second does. For instance, if a file occupies 100 MB of space, then it actually takes up 200 MB of space on a normal redundancy diskgroup and 300 MB of space on a high redundancy diskgroup. [dir] can also contain wildcards. The -H flag suppresses the column headers from the output.'; $cmd_desc{'find'} = ' Find the absolute paths of all occurrences of under . can be a directory and may include wildcards. may also include wildcards. Note that directory names in the results have the "/" suffix to clarify their identity. The --type option allows searching by file type. For instance, one can search for all the control files at once. must be one of the valid values in V$ASM_FILE.TYPE.'; $cmd_desc{'help'} = ' If [command] is specified, then help returns its syntax as well as an explanation of its parameters. Otherwise, help returns a list of all commands, in addition to the asmcmd invocation syntax.'; $cmd_desc{'ls'} = ' List [name] or its contents alphabetically if [name] refers to a directory. [name] can contain the wildcard "*" and is the current directory if unspecified. Directory names in the display list have the "/" suffix to clarify their identity. The first two optional flags specify how much information is displayed for each file, in the following manner: (no flag) V$ASM_ALIAS.NAME -l V$ASM_ALIAS.NAME, V$ASM_ALIAS.SYSTEM_CREATED; V$ASM_FILE.TYPE, V$ASM_FILE.REDUNDANCY, V$ASM_FILE.STRIPED, V$ASM_FILE.MODIFICATION_DATE -s V$ASM_ALIAS.NAME; V$ASM_FILE.BLOCK_SIZE, V$ASM_FILE.BLOCKS, V$ASM_FILE.BYTES, V$ASM_FILE.SPACE If the user specifies both flags, then the command shows an union of their respective columns, with duplicates removed. If an entry in the list is an user-defined alias or a directory, then -l displays only the V$ASM_ALIAS columns, and -s shows only the alias name and its size, which is zero because it is negligible. Moreover, the displayed name contains a suffix that is in the form of an arrow pointing to the absolute path of the system-created filename it references: t_db1.f => +diskgroupName/DBName/DATAFILE/SYSTEM.256.1 See the -L option below for an exception to this rule. For disk group information, this command queries V$ASM_DISKGROUP_STAT by default, which can be modified by the -c and -g flags. The remaining flags have the following meanings: -d If an argument is a directory, list only its name (not its contents). --reverse Reverse the sorting order. -t Sort by time stamp (latest first) instead of by name. -L If an argument is an user alias, display information on the file it references. -a If an argument is a system-created filename, show the location of its user-defined alias, if any. -g For disk group information, select from GV$ASM_DISKGROUP_STAT, or GV$ASM_DISKGROUP, if the -c flag is also active; GV$ASM_DISKGROUP.INST_ID is included in the output. --permission Display permission information for a file. -H Suppress the column header information, so that scripting is easier. Note that "ls +" would return information on all diskgroups, including whether they are mounted. Not all possible file columns or disk group columns are included. To view the complete set of columns for a file or a disk group, query the V$ASM_FILE and V$ASM_DISKGROUP views.'; $cmd_desc{'lsct'} = ' List all clients and their information from V$ASM_CLIENT. If group is specified, then return only information on that group. -g Select from GV$ASM_CLIENT. -H Suppress the column headers from the output.'; $cmd_desc{'lsdg'} = ' List all diskgroups and their information. If [group] is specified, then return only information on that group. The command also informs the user if a rebalance is currently under way for a diskgroup. This command queries V$ASM_DISKGROUP_STAT by default, which can be modified by the --discovery flag and V$ASM_DISKGROUP will be used instead. --discovery Select from V$ASM_DISKGROUP, or GV$ASM_DISKGROUP, if the -g flag is also active. If the ASM software version is 10gR1, then the effects of this flag is always active, whether or not it is set. -g Select from GV$ASM_DISKGROUP_STAT, or GV$ASM_DISKGROUP, if the -c flag is also active; GV$ASM_DISKGROUP.INST_ID is included in the output. -H Suppress the column headers from the output. Not all possible disk group columns are included. To view the complete set of columns for a disk group, query the V$ASM_DISKGROUP view.'; $cmd_desc{'mkalias'} = ' Create the specified for the . A user-defined alias must reside in the same diskgroup as the system-created filename, and only one user-defined alias is permitted per file. The SQL equivalent is ALTER DISKGROUP ADD ALIAS FOR .'; $cmd_desc{'mkdir'} = ' Create directories . This command is equivalent to ALTER DISKGROUP ADD DIRECTORY in SQL. A directory cannot be created in the root directory "+".'; $cmd_desc{'pwd'} = ' Display the absolute path of the ASMCMD current directory. Note that this current directory is not to be confused with the current directory maintained by the operating system, if any.'; $cmd_desc{'rm'} = ' If is an empty directory, then rm removes it. It is equivalent to ALTER DISKGROUP DROP DIRECTORY in SQL. Note that rm cannot remove system-created or non-empty directories. Otherwise, rm treats as files and deletes them, removing both system-created filenames and user-defined aliases. It is equivalent to ALTER DISKGROUP DROP FILE in SQL. If using a wildcard, then rm deletes all matches with the exception of system-created directories and non-empty directories. If using the -r flag, then all entries under the specified are deleted recursively. Note that rm does not specifically issue the DROP DIRECTORY SQL command to drop system-created directories. However, these are removed automatically by ASM once they are empty. If using the -r flag or wildcard matching, then rm prompts the user to confirm the deletion before proceeding, unless the user also specifies the -f flag. Warning! Removing an user-defined alias removes the system-created filename as well, and vice versa. This rule applies even when you use wildcard or the -r option! The wildcard needs to match only one of the two, and both are deleted. For -r, only one of the two needs to be under the directory to be deleted, and both are deleted. For instance, if you have an user-defined alias "+dg/dir1/file.alias" that points to "+dg/ORCL/DATAFILE/System.256.1", doing a "rm -r +dg1/dir1" will remove System.256.1 as well, even though it is not technically under "+dg/dir1".'; $cmd_desc{'rmalias'} = ' Delete the specified user-defined aliases, while preserving the files and their system-created filenames. The SQL equivalent is ALTER DISKGROUP DROP ALIAS . Note that if the -r flag is specified, then rmalias uses ALTER DISKGROUP DROP DIRECTORY FORCE to delete a user-defined directory recursively. This action also removes all user-defined subdirectories and user-defined aliases beneath it. System-created filenames and system-created directories cannot be removed this way.'; $cmd_desc{'cp'} = ' Copy one or more source ASM file(s) to a destination. If the source is a single file, then desination can be a the target directory or a filename, otherwise it has to be a directory. The destination is of the form target/connect_identifier, where connect_identifier is of the form HOSTNAME, HOSTNAME.SID or HOSTNAME.[PORT.]SID where the port is optional.'; $cmd_desc{'lsof'} = ' Lists the open files in an ASM instance. [--dbname ] list files only from database [-G ] list files only from from disk group [-C ] list files only from instance '; return $cmd_desc{$cmd}; } ######## # NAME # asmcmdbase_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 # The syntax for command $cmd; undefined if $cmd does not exist. ######## sub asmcmdbase_get_cmd_syntax { my ($cmd) = shift; my (%cmd_syntax); # Hash storing the syntax for each internal command. # $cmd_syntax{'asmcmd_no_conn_str'} = 'asmcmd [-vV] [-a ] ' . '[-p] [command]'; $cmd_syntax{'asmcmd'} = 'asmcmd [-vV] [-a ] ' . '[-c [/password]' . '[\@connect_identifier]] [-p] [command]'; $cmd_syntax{'cd'} = 'cd '; $cmd_syntax{'du'} = 'du [-H] [dir]'; $cmd_syntax{'find'} = 'find [--type ] '; $cmd_syntax{'help'} = 'help [command]'; $cmd_syntax{'ls'} = 'ls [-lsdtLagH] [--permission] [--reverse] [name]'; $cmd_syntax{'lsct'} = 'lsct [-gH] [group]'; $cmd_syntax{'lsdg'} = 'lsdg [-gH] [--discovery] [group]'; $cmd_syntax{'mkalias'} = 'mkalias '; $cmd_syntax{'mkdir'} = 'mkdir '; $cmd_syntax{'pwd'} = 'pwd'; $cmd_syntax{'rm'} = 'rm [-rf] '; $cmd_syntax{'rmalias'} = 'rmalias [-r] '; $cmd_syntax{'cp'} = 'cp [-if] <[\@connect_identifier:]src>' . ' <[\@connect_identifier:]tgt>'; $cmd_syntax{'lsof'} = 'lsof [-G dgroup] [--dbname dbname] [-C clientinstname]'; return $cmd_syntax{$cmd}; } ######## # NAME # asmcmdbase_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. ######## sub asmcmdbase_get_asmcmd_cmds { return asmcmdshare_print_cmds(sort(keys %asmcmdbase_cmds)); } ############################################################################## 1;