MINI MINI MANI MO
# Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved.
#
# NAME
# asmcmdshare - ASM CoMmanD line interface (Shared Functionality Module)
#
# DESCRIPTION
# ASMCMD is a Perl utility that provides easy nagivation of files within
# ASM diskgroups. This module provides some basic functionalities
# that are shared by multiple other modules, such as path, normalization,
# SQL access, etc.
#
# NOTES
# usage: asmcmdcore [-p] [command]
#
# MODIFIED (MM/DD/YY)
# apfwkr 06/04/18 - Backport prabbala_acmdpip_27948050 from main
# apfwkr 03/28/18 - Backport moreddy_bug-27688692 from main
# prabbala 05/31/18 - 27948050: add new timeout for checking pipes presence
# moreddy 03/19/18 - 27688692: option to skip tracing in execute_tool
# anovelo 05/19/17 - 26036398: return code 0 for ASMCMD 9526
# anovelo 05/17/17 - 26083595: Remove hostname from example
# diguzman 10/20/16 - 24923984: improve code coverage
# anovelo 10/18/16 - 24706236: Enable different date formats in get_file
# diguzman 10/13/16 - 12825292: add asmcmdshare_humanBytes
# diguzman 09/22/16 - 11736372: add asmcmdshare_stack_trace
# diguzman 09/01/16 - 24570890: get_redund returns FLEX & EXTEND values
# emsu 06/08/16 - Add site_guid to disk queries
# samjo 06/01/16 - 23511600: Fix asmcmdshare_is_dev_env()
# diguzman 05/30/16 - 19654070: improve pasing of XML 'nointance' option
# diguzman 05/20/16 - 23061091: get instance name from running processes
# diguzman 04/12/16 - 22600475: add _filegroup_exists and _xml_exists
# diguzman 03/08/16 - 21456380: add asmcmdshare_set_instance
# jesugonz 02/16/15 - 22660914: add site_status info to disk queries
# dacavazo 11/27/15 - 20766180: Make bg pipe filename contain its pid
# emsu 10/18/15 - 22088795: +optional arg in asmcmdshare_execute_tool
# to redirect stderr to stdout,+asmcmdshare_is_dev_env
# emsu 08/31/15 - 21827297: Add site info to disk queries
# ykatada 08/06/15 - 21936597: add trace when opening pipe file
# dacavazo 08/06/15 - 21453939: flag removed files on get_file
# prabbala 07/30/15 - 21140011:use both LANG/NLS_LANG in asmcmdshare_runcmd
# emsu 06/24/15 - 20732199: print informational error 9489 to stdout,
# make asmcmdshare_execute_tool fail when tool signals
# byyang 06/04/15 - bug#21204705: do not trace msg to console when write
# to pipe fails
# siyarlag 05/20/15 - fix syntax error
# sanselva 05/12/15 - remove password validation in asmcmd
# shlian 03/31/15 - 20672249: trim password from trace in share_do_stmt
# dacavazo 03/10/15 - 20657441: added optional argument to
# asmcmdshare_do_stmt to trim DBD ERROR from trace
# prabbala 01/30/15 - 20430157: log error in asmcmdshare_execute_tool
# only in cmd failure
# prabbala 01/30/15 - 20434627: set SIG{CHLD} to DEFAULT before executing
# commands via backticks
# dacavazo 01/21/15 - Showing user as <user>@<cluster> if cluster user
# siyarlag 01/08/15 - do not remove new lines from runcmd output
# ykatada 12/04/14 - #15949549: Add asmcmdshare_init_disk_attr and
# asmcmdshare_get_dsk
# siyarlag 12/10/14 - runcmd to store the command return value
# prabbala 11/24/14 - bug19512500: add asmcmdshare_runcmd
# ykatada 10/03/14 - #19617921: Enhance asmcmdshare_do_construct_select
# shlian 09/09/14 - 19561559: filter out domain part in host name
# cgraybl 09/05/14 - bug19065962: add logical sector size to lsdg
# dacavazo 08/18/14 - 19445832: Daemon now reads pipe to a buffer
# dacavazo 05/15/14 - 18274328: Added support for multiple concurrent FGs
# hppancha 03/28/14 - Change behaviour of global_hash{'spawnutil'}
# siyarlag 05/12/14 - increase the upper limit for asmcmd errors
# hppancha 04/14/14 - bug18462686: relax error message range to accomodate
# afd related errors
# shlian 03/13/14 - bug17377794: Use a generic name for SIGUSR1
# pvenkatr 03/05/14 - Bug# 18034732 - replace all Windows style '\' to
# Unix style '/'.
# pvenkatr 01/23/14 - Bug #18113228 Added check for SYSASM requirement.
# diguzman 11/25/13 - #17786672: Fix pipe mask on asmcmdshare_mkfifo.
# pvenkatr 11/04/13 - # 17395893 - Using PERL2C interface to pickup
# connection string. Added asmcmdshare_trace_msg,
# pvenkatr 10/08/13 - LRG #9924405,9924821,9925240 - untaint the hostname
# value.
# pvenkatr 09/25/13 - 17398626 - using uname-n & %COMPUTERNAME% instead of
# $HOSTNAME env var.
# diguzman 01/10/13 - Bug #17346246 Check if the process exists before
# trying to kill it.
# pvenkatr 07/31/13 - Bug #17180882 skipping TAB char.
# manuegar 05/02/13 - Bug13951456 - added asmcmdshare_do_prepare,
# asmcmdshare_do_validate_sth and
# asmcmdshare_do_execute.
# pvenkatr 04/29/13 - LRG 8953219 - reverted previous change, added
# more traces.
# diguzman 04/18/13 - add functions for slave processes management
# pvenkatr 04/16/13 - Bug #16670094 - added check in validate_path_recurse
# for non-existing dir in cd.
# pvenkatr 04/01/13 - Bug 16578074, LRG 8953219 - fixed hang/signal 9
# issue.
# pvenkatr 02/01/13 - consolidated all _execute_xxx to _execute_tool.
# shmubeen 01/09/13 - Bug# 16384206/Lrg #8710957 - make sure background
# process in wait mode to receive signal
# sanselva 12/05/12 - add support for checking visible flag
# moreddy 11/22/12 - 14142633: asmcmd needs to reconnect for flex asm
# pvenkatr 11/10/12 - #15848691 - consolidated executing kfod
# shmubeen 10/16/12 - Fix for bug# 14184571.
# skiyer 05/28/12 - 13952494:set exit return status for asmcmd commands
# pvenkatr 05/10/12 - #14014928 - Using ReadKey instead of POSIX::Term
# adileepk 11/25/11 - Fix for bug-12661648. ASMCMD should have exit status
# as -1 when a command fails in non-interactive mode.
# adileepk 10/31/11 - Fix for lrg-5705736.
# adileepk 10/10/11 - Extending the upper bound for error message numbers
# to accommodate new error messages for Shared
# Password commands.Transaction:
# adileepk_sharedpassword
# pvenkatr 09/08/11 - Added noinstance, wildcard to XML & processing.
# adileepk 08/23/11 - Fix for lrg 5840040. Solution for race condition in
# Connection Pooling.
# adileepk 06/20/11 - Connection Pooling.
# moreddy 03/09/11 - lrg 4808166 fix clsecho issues on NT
# pvenkatr 03/02/11 - Bug 11829123. Character validation for password
# input.
# adileepk 12/28/10 - Adding code for parsing the new xml help file,
# asmcommanddiag.xml
# canuprem 01/09/10 - lrg 4808166. Fix for clsecho quotes on NT
# moreddy 05/06/10 - bug 8667038 NLS for error messages
# mchimang 05/26/10 - bug 8846724 added group_number check to
# usergroupnumber2name function.
# amitroy 04/29/10 - BUG 8933243 - USE DIFFERENT CHARACTER IN ASMCMD TO
# "HIDE COLUMN DETAILS" INSTEAD OF -H
# pvenkatr 03/31/10 - Syntax, description example all from xml file
# moreddy 03/22/10 - Adding more tracing
# moreddy 01/19/10 - Adding tracing infrastructure
# moreddy 12/17/09 - bug 9209614 ls --permission throws script error
# mproddut 11/19/09 - bug 9104887 uninitialised value with "du"
# mproddut 10/13/09 - bug 8874854 lsdg -g throws error
# pvenkatr 09/03/09 - Help message from xml file.
# danagara 08/06/09 - Cntrl + C handled properly bug-8646861
# sanselva 06/26/09 - 'ls -L --permission' issue, include diskgroup_number
# in usergroupnumber2name
# sanselva 06/03/09 - add asmcmdglobal_deprecated_options
# sanselva 05/13/09 - corrected invalid column in usergroupnumber2name
# heyuen 09/19/08 - fix signal handler for infinite commands
# heyuen 08/26/08 - add voting file
# heyuen 07/28/08 - add commands array, fix time format for debug
# heyuen 05/22/08 - add get_ugnum_from_ugname
# heyuen 04/15/08 - move unix_os table
# heyuen 03/30/08 - enable ls -p
# heyuen 11/08/07 - add do_construct_select
# heyuen 09/12/07 - flush print line
# heyuen 08/10/07 - refresh from main
# hqian 06/04/07 - Fix three stragglers that are still using
# v$asm_diskgroup (should use _stat)
# heyuen 05/25/07 - add return codes for errors
# dfriedma 05/24/07 - Remove unbalanced column
# heyuen 04/17/07 - untaint the file to log debugging info
# hqian 03/05/07 - Add asmcmdshare_version_cmp; update version checks
# hqian 03/02/07 - modify asmcmdshare_get_dg to include _stat and gv$
# options
# hqian 07/20/06 - #5397026: new asmcmdglobal_no_instance_callbacks
# hqian 06/15/06 - Move asmcmdbase_ls_calc_min_col_wid to this module
# hqian 01/25/06 - Split off asmcmdshare.pm from asmcmdshare.pm
# 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 <type>].
# hqian 06/30/04 - Make code that uses BigInt work for both Perl 5.6.1
# and Perl 5.8.3; take out -c <connect_string>
# functionality.
# hqian 06/29/04 - Fix 10gR1 compatibility issues; fix alias name
# case-sensitive bug, should be case insensitive
# but case retentive.
# hqian 06/25/04 - Rename the main program from asmcmd to asmcmd0, so
# that we can name the wrapper script asmcmd; rename
# global constants, global variables, and function
# names so that they are prefixed by 'asmcmd0_',
# as per coding style standards; fix ORA-15128 bug.
# hqian 06/23/04 - Inaccurate error message bug: add error message
# 8004, do not print error when '*' matches nothing;
# fix rm -rf * double error message bug; fix find
# diskgroup bug; fix print header in empty directory
# bug; fix space in alias name bug.
# hqian 06/22/04 - Give the option to turn off the functionality of
# the -c flag; add constants for better code design.
# hqian 06/09/04 - Fix bugs; improve comments.
# hqian 06/07/04 - Organize code for better maintenance; code review
# changes (suggested by Dave Friedman).
# hqian 06/01/04 - Fix some bugs.
# hqian 05/24/04 - Implement rm [-rf] and rm *; some code review fixes.
# hqian 05/20/04 - Implement help, instance_type security check,
# - connection error checks, and debug mode.
# hqian 05/18/04 - Implement ls flags and lsct.
# hqian 05/14/04 - hqian_asmcmd_13306
# hqian 04/21/04 - Creation
#
#
#
#############################################################################
#
############################ Functions List#################################
#
# Parameter Parsing Routines
# asmcmdshare_is_wildcard_cmd
#
# Error Routines
# asmcmdshare_error_msg
# asmcmdshare_signal_exception
# asmcmdshare_assert
# asmcmdshare_signal_handler
#
# Normalization Routines
# asmcmdshare_normalize_path
# asmcmdshare_validate_path
# asmcmdshare_validate_path_recurse
# asmcmdshare_validate_dir
# asmcmdshare_validate_dg
# asmcmdshare_make_absolute
# asmcmdshare_cdup_update_ids
#
# Misc Routines
# asmcmdshare_ls_calc_min_col_wid
# asmcmdshare_version_cmp
# asmcmdshare_get_asm_version
# asmcmdshare_get_host_name
# asmcmdshare_runcmd
# asmcmdshare_is_dev_env
# asmcmdshare_set_instance
#
# SQL Routines
# asmcmdshare_cur_julian_date
# asmcmdshare_get_insttype
# asmcmdshare_get_file
# asmcmdshare_get_subdirs
# asmcmdshare_get_subdir_simple
# asmcmdshare_run_find_sql
# asmcmdshare_get_par_id
# asmcmdshare_get_dg
# asmcmdshare_get_redund
# asmcmdshare_get_gnum_from_gname
# asmcmdshare_get_gname_from_gnum
# asmcmdshare_do_select
# asmcmdshare_fetch
# asmcmdshare_finish
# asmcmdshare_do_stmt
# asmcmdshare_do_prepare
# asmcmdshare_do_validate_sth
# asmcmdshare_do_execute
# asmcmdshare_get_instance_name
# asmcmdshare_init_disk_attr
# asmcmdshare_get_dsk
#
# XML Parsing Routines
# asmcmdshare_xml_start_handler
# asmcmdshare_xml_char_handler
# asmcmdshare_xml_end_handler
#
# Strng Trimming Routines
# asmcmdshare_trim_str
#############################################################################
package asmcmdshare;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(asmcmdshare_is_wildcard_cmd
asmcmdshare_is_no_instance_cmd
asmcmdshare_error_msg
asmcmdshare_signal_exception
asmcmdshare_assert
asmcmdshare_signal_handler
asmcmdshare_normalize_path
asmcmdshare_make_absolute
asmcmdshare_cur_julian_date
asmcmdshare_ls_calc_min_col_wid
asmcmdshare_version_cmp
asmcmdshare_get_asm_version
asmcmdshare_get_host_name
asmcmdshare_runcmd
asmcmdshare_is_dev_env
asmcmdshare_set_instance
asmcmdshare_get_insttype
asmcmdshare_get_file
asmcmdshare_get_subdirs
asmcmdshare_get_subdir_simple
asmcmdshare_run_find_sql
asmcmdshare_get_par_id
asmcmdshare_get_dg
asmcmdshare_get_redund
asmcmdshare_get_gnum_from_gname
asmcmdshare_get_gname_from_gnum
asmcmdshare_do_construct_select
asmcmdshare_do_select
asmcmdshare_fetch
asmcmdshare_finish
asmcmdshare_do_stmt
asmcmdshare_do_prepare
asmcmdshare_do_validate_sth
asmcmdshare_do_execute
asmcmdshare_ziplists
asmcmdshare_humanBytes
asmcmdshare_getpswd
asmcmdshare_usergroupnumber2name
asmcmdshare_usernumber2name
asmcmdshare_filegroup_exists
asmcmdshare_xml_exists
asmcmdshare_get_ugnum_from_ugname
asmcmdshare_print_cmds
asmcmdshare_check_option_consistency
asmcmdshare_handle_deprecation
asmcmdshare_get_help_desc
asmcmdshare_get_help_syntax
asmcmdshare_get_cmd_wildcard
asmcmdshare_get_cmd_noinst
asmcmdshare_is_cmd_visible
asmcmdshare_trace
asmcmdshare_stack_trace
asmcmdshare_trace_msg
asmcmdshare_trim_str
asmcmdshare_print
asmcmdshare_printstderr
asmcmdshare_printprompt
asmcmdshare_printpipe
asmcmdshare_output
asmcmdshare_readcommands
asmcmdshare_readstdin
asmcmdshare_backup_stdio
asmcmdshare_alarm_handler
asmcmdshare_check_dbh_alive
asmcmdshare_openreadpipe
asmcmdshare_openwritepipe
asmcmdshare_closereadpipe
asmcmdshare_closewritepipe
asmcmdshare_removereadpipe
asmcmdshare_removefgpipe
asmcmdshare_removebgpipe
asmcmdshare_execute_tool
asmcmdshare_get_instance_name
asmcmdshare_filter_invisible_cmds
asmcmdshare_setstate
asmcmdshare_signalhandler
asmcmdshare_wait
asmcmdshare_waitslaves
asmcmdshare_packmsg
asmcmdshare_unpackmsg
asmcmdshare_logdata
asmcmdshare_signal
asmcmdshare_killslave
asmcmdshare_mkfifo
asmcmdshare_connect
asmcmdshare_sendmsg
asmcmdshare_recvmsg
asmcmdshare_fork_slaves
asmcmdshare_check_reqd_priv
asmcmdshare_init_disk_attr
asmcmdshare_get_dsk
%asmcmdshare_unix_os
%asmcmdshare_trace_levels
@asmcmdshare_commandlist
$asmcmdshare_term_string
$asmcmdshare_reconnect_string
$asmcmdshare_disablecp_string
$asmcmdshare_foreground_retries
$asmcmdshare_started_daemon
$asmcmdshare_writehandle
$asmcmdshare_readhandle
$asmcmdshare_pipetowrite
$asmcmdshare_pipetoread
$asmcmdshare_logheader
$asmcmdshare_daemonidlewait
$asmcmdshare_openpipewait
$asmcmdshare_readpipewait
$asmcmdshare_process_status
$asmcmdshare_siguser1
$asmcmdshare_pipehome
$asmcmdshare_wait4pipeCrt
);
use strict;
use English;
use DBI qw(:sql_types);
use Getopt::Long qw(:config no_ignore_case bundling no_getopt_compat);
use POSIX;
use Term::ReadKey;
use Config;
##Commenting IPC::SysV module as it doesnt exist on Windows.
#use IPC::SysV;
use Data::Dumper;
use asmcmdexceptions;
use asmcmdglobal qw(%asmcmdglobal_hash
%asmcmdglobal_options
%asmcmdglobal_deprecated_options
@asmcmdglobal_is_wildcard_callbacks
@asmcmdglobal_syntax_error_callbacks
@asmcmdglobal_no_instance_callbacks
@asmcmdglobal_error_message_callbacks
@asmcmdglobal_signal_exception_callbacks
$ASMCMDGLOBAL_WCARD_CHARS
$ASMCMDGLOBAL_VER_10gR1
$ASMCMDGLOBAL_VER_10gR2
$ASMCMDGLOBAL_VER_11gR1
$ASMCMDGLOBAL_VER_11gR2
$ASMCMDGLOBAL_DEFAULT
$ASMCMDGLOBAL_EXTERNAL
$ASMCMDGLOBAL_NORMAL
$ASMCMDGLOBAL_HIGH
$ASMCMDGLOBAL_FLEX
$ASMCMDGLOBAL_EXTENDED
$asmcmdglobal_trace_path
$asmcmdglobal_clsecho
$asmcmdglobal_product
$asmcmdglobal_facility
);
use POSIX qw(:termios_h);
############################ Global Constants ###############################
my ($ASMCMDSHARE_CSET) = '[\w .\-#$]'; # C-set for alphanumeric, '_', ' ', #
# '.', '-', '#', '$' . #
my ($ASMCMDSHARE_N_CSET) = '[^\w .\-#$]'; # Char-set for all chars except #
# those in $ASMCMDSHARE_CSET. #
my ($ASMCMDSHARE_CSET_W_WCARD) = '[\w %*.\-#$]'; # Char-set for all #
# $ASMCMDSHARE_CSET chars plus $WCARD_CHARS. #
my ($ASMCMDSHARE_N_CSET_W_WCARD) = '[^\w %*.\-#$]'; # C-set for all except #
# $ASMCMDSHARE_CSET_W_WCARD. #
my ($ASMCMDSHARE_MAXPASSWD) = 256; # Max length of user passwd input #
# bug19065962: ASMCMD internally runs few commands (e.g srvctl, crsctl) and #
# parses its output. It expects the output should be in English. If ASMCMD #
# env has different language setting, it affects these commands too and #
# ASMCMD fails to parse the output. So, run such commands with English as #
# NLS_LANG. #
my ($ASMCMDSHARE_SHENV_NLSLANG) = 'AMERICAN_AMERICA.us7ascii';
my ($ASMCMDSHARE_SHENV_LANG) = 'en_US.UTF-8';
# List of possible platforms.
our (%asmcmdshare_unix_os) = ( aix => 'aix',
bsdos => 'bsdos',
dgux => 'dgux',
dynixptx => 'dynixptx',
freebsd => 'freebsd',
linux => 'linux',
hpux => 'hpux',
irix => 'irix',
openbsd => 'openbsd',
dec_osf => 'dec_osf',
sco_sv => 'sco_sv',
svr4 => 'svr4',
unicos => 'unicos',
unicosmk => 'unicosmk',
solaris => 'solaris',
sunos => 'sunos',
);
our (%asmcmdshare_trace_levels) = ( errors => '1',
warnings=> '2',
normal => '3',
info => '4',
debug => '5',
);
######
# Global variables used in connection pooling
######
our (@asmcmdshare_readbuffer) = ();
our (@asmcmdshare_commandlist) = ();
our ($asmcmdshare_term_string) = "&&&&&\n";
our ($asmcmdshare_reconnect_string) = "Reconnect to ASM. dbh is stale.";
our ($asmcmdshare_disablecp_string) = "Disable Connection Pooling. Unable " .
"to connect to ASM.";
our ($asmcmdshare_writehandle);
our ($asmcmdshare_readhandle);
our ($asmcmdshare_pipetowrite); # Reference to the name of the pipe
# to be used for writing. Would be
# different for Foreground and
# Background
our ($asmcmdshare_pipetoread); # Reference to the name of the pipe
# to be used for reading. Would be
# different for Foreground and
# Background
our ($asmcmdshare_logheader); # A header string to be prepended to
# logs. Different for Foreground and
# Background
our ($asmcmdshare_wait4pipeCrt) = 10; # Time to wait(in secs) for the pipes
# to appear
our ($asmcmdshare_daemonidlewait) = 300;# Idle wait time for the ASMCMD
# Background process, in secs
our ($asmcmdshare_openpipewait) = 30; # Number of secs to wait for the open
# pipe operation to complete.
our ($asmcmdshare_readpipewait) = 300;# Time in secs to wait on a pipe for
# data
our ($asmcmdshare_process_status); # Status of this process, relevant
# only in connection pooling context.
our ($asmcmdshare_siguser1) = 0; # Signal to invoke BG process waiting
# IO
######
# Globals used for xml parsing.
######
my ($xmlparser) ="";
my ($xmlfile)="";
my ($xmlparsed) = 0 ; # to parse xml file only once.
my ($cmdName)="";
my ($text)= "" ;
my ($helpcmd) = "";
my ($cmdNode) = "command";
my ($inCmdNode) = "FALSE";
my ($isWildcard) = "false" ;
my ($isNoinstance) = "false" ;
my ($isVisible) = "true" ;
my ($privReqd) = "sysasm";
# hash table of data for each commands
my (%hCmdDesc) = ();
my (%hSyntax) = () ;
my (%hExample) = () ;
my (%hOptDesc) = ();
my (%hSeeAlso) = () ;
my (%hWildcard) = () ;
my (%hNoInst) = () ;
my (%hisvisible) = () ;
my (%hPrivSysAsm) = ();
my (%hPrivSysDba) = ();
# Help msg topics
my ($synopStr) = "Synopsis";
my ($descStr) = "Description";
my ($exampStr) = "Examples";
my ($excepStr) = "Exceptions" ;
my ($enhanStr) = "Enhancements" ;
my ($verStr) = "Version" ;
my ($seeAlsoStr) = "See Also";
# Globals used for master-slave communication
my ($asmcmdshare_pipehome);
my ($asmcmdshare_state);
my ($asmcmdshare_wait_flag) = 1;
my ($asmcmdshare_missing);
my ($exit) = 0;
######################### Parameter Parsing Routines #########################
########
# NAME
# asmcmdshare_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
# 1 if $arg is a command that can take wildcards as part of its argument,
# 0 otherwise.
#
# NOTES
# This routine calls the callbacks from each module to check if $arg
# supports the use of wildcards. It asserts that the command is found
# in only one module.
########
sub asmcmdshare_is_wildcard_cmd
{
my ($arg) = shift;
my ($module);
my ($use_wildcard) = 0;
my ($count) = 0;
my (@eargs);
foreach $module (@asmcmdglobal_is_wildcard_callbacks)
{
if ($module->($arg))
{
$use_wildcard = 1;
$count++;
}
}
####
# Assert here that 0 <= count <= 1.
# @eargs = ("asmcmdshare_is_wildcard_cmd_05", $arg, $count);
# asmcmdshare_assert( (($count >= 0) && ($count <= 1)), \@eargs);
return $use_wildcard;
}
########
# NAME
# asmcmdshare_is_no_instance_cmd
#
# DESCRIPTION
# This routine is shared/general routine that determines if a command
# can run without an ASM instance.
#
# PARAMETERS
# arg (IN) - user-entered command name string.
#
# RETURNS
# 1 if $arg is a command that can run without an ASM instance or it does not
# belong to this module
# 0 if $arg is a command that needs to connect to an ASM instance
# -1 if $arg is a command that may use an ASM instance.
#
# NOTES
# This routine calls the callbacks from each module to check if $arg
# can run without an ASM instance. It asserts that the command is found
# in only one module.
########
sub asmcmdshare_is_no_instance_cmd
{
my ($arg) = shift;
my ($module);
my ($no_instance_necessary) = 1;
my ($count) = 0;
my (@eargs);
foreach $module (@asmcmdglobal_no_instance_callbacks)
{
my $rc = $module->($arg);
if ($rc != 1)
{
$no_instance_necessary = $rc;
last;
}
}
return $no_instance_necessary;
}
############################# Error Routines #################################
########
# NAME
# asmcmdshare_error_msg
#
# DESCRIPTION
# This function provides the main interface for recorded errors. All
# modules must call this function to record an error.
# This routine prints error and exception messages to STDERR using clsecho.
#
# PARAMETERS
# err_num (IN) - ASMCMD internal error number.
# args_ref (IN) - (Optional) Reference to array of error arguments
#
# RETURNS
# Null.
########
sub asmcmdshare_error_msg
{
my ($err_num, $args_ref) = @_;
my ($module);
my (@eargs); # Array of error arguments. #
my ($argument, $buf);
my ($clsecho_mesg) = '';
my ($exit) = 0; # Whether to exit ASMCMD at the end of this function. #
# if clsecho utility does not exist or no execute permission, bail out.
if (!-x $asmcmdglobal_clsecho)
{
return;
}
# Assert that $err_num is within 8001-9550, inclusive.
@eargs = ("asmcmdshare_error_msg_05", $err_num);
asmcmdshare_assert( (($err_num >= 8001) && ($err_num <= 9550)) , \@eargs);
if ($err_num == 8103)
{
if (defined($args_ref) && defined($args_ref->[0]))
{
$clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
."$asmcmdglobal_facility -m $err_num '$args_ref->[0]'";
}
else
{
$clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
."$asmcmdglobal_facility -m $err_num undefined";
}
}
# 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
elsif (($err_num == 8100) || ($err_num == 8101) || ($err_num == 8200))
{
$buf = "$DBI::errstr";
# Bug-5007830: exit if we have ORA-03113 or ORA-03114.
if (($DBI::errstr =~ m,311[34],) || ($DBI::errstr =~ m,3135,))
{
$exit = 1;
}
}
# 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)
{
$clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
."$asmcmdglobal_facility -m $err_num '";
foreach $argument (@{$args_ref})
{
$clsecho_mesg .= "[$argument] ";
}
$clsecho_mesg .= "'";
}
else
{
$clsecho_mesg ="$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
."$asmcmdglobal_facility -m $err_num";
foreach $argument (@{$args_ref})
{
if($^O =~ /win/i)
{
$argument =~ s/\"/\"\"/g;
$clsecho_mesg .= " \"$argument\"";
}
else
{
$clsecho_mesg .= " '$argument'";
}
}
}
if ($clsecho_mesg ne '')
{
eval
{
$buf = `$clsecho_mesg`;
};
if ($@ ne '')
{
chomp $@;
asmcmdshare_print("$@\n");
return;
}
elsif (defined($buf) && ($buf ne ''))
{
#removing the new line char in $buf
chomp($buf);
# Error 8020: could not write into trace file. Hence, print it only on
# console.
if ($err_num == 8020)
{
chomp $buf;
asmcmdshare_printstderr ("$buf\n");
}
# 20732199: error 9489 "no Client Clusters configured" is informational,
# so print it to stdout, rather than stderr
elsif ($err_num == 9489)
{
asmcmdshare_print("$buf\n");
}
else
{
asmcmdshare_trace(1,"$buf", 'y', 'y');
asmcmdshare_stack_trace();
}
}
}
# 20732199: command should succeed when displaying error 9489
# 26036398: ASMCMD-9526 is informative, should not return error code
if (($asmcmdglobal_hash{'mode'} eq 'n') &&
($err_num != 9489 && $err_num != 9526))
{
$asmcmdglobal_hash{'e'} = -1;
$|++;
}
if ($exit)
{
exit 1;
}
return;
}
########
# NAME
# asmcmdshare_signal_exception
#
# DESCRIPTION
# This function provides the main interface for signaled exceptions.
# All modules must call this function to record an error.
#
# PARAMETERS
# exception_num (IN) - ASMCMD internal error/exception number.
# args_ref (IN) - (Optional) Reference to array of error arguments
#
# RETURNS
# Never returns; always exits 1.
#
# NOTES
# Only call this routine for exceptions. This routine always exits 1.
#
# Usually, each error type has a fixed number of error arguments that are
# displayed, if the error is an external error. If the error is internal,
# then arbitrary number of arguments can be included.
########
sub asmcmdshare_signal_exception
{
my ($exception_num, $args_ref) = @_;
my ($module);
# Assert that $exception_num is within 8000-9530, inclusive.
if (($exception_num < 8000) || ($exception_num > 9530))
{
asmcmdshare_trace(1, "asmcmd: 8202: [asmcmdshare_signal_exception_05] "
."[$exception_num] ", 'y', 'y');
die "\n";
}
asmcmdshare_error_msg($exception_num, $args_ref);
# All exceptions end session.
exit 1;
}
########
# NAME
# asmcmdshare_assert
#
# DESCRIPTION
# This function assert that first argument is true, or signals exception.
#
# PARAMETERS
# is_true (IN) - assert that this argument is TRUE.
# args_ref (IN) - (Optional) Reference to array of assert arguments
#
# RETURNS
# Null if is_true is TRUE; signals internal error otherwise.
#
# NOTES
# The assert error arguments get displayed when the error is signaled. The
# following is the convention:
# argument 0 - the function name plus a number, e.g. asmcmdshare_assert_05
# argument 1 and onward - values of variables that are being evaluated
# for truth.
########
sub asmcmdshare_assert
{
my ($is_true, $args_ref) = @_;
# Assert that this is true or signal exception.
if (!$is_true)
{
my @eargs = @{$args_ref};
asmcmdshare_signal_exception(8202, \@eargs);
}
return;
}
########
# NAME
# asmcmdshare_alarm_handler
#
# DESCRIPTION
# This routine handles OS ALRM signal and other scenarios.
# Logs the particular situation and either returns or raises and exception
#
# RETURNS
# Null.
#
# NOTES
# NONE.
########
sub asmcmdshare_alarm_handler
{
my $exception = $@;
if($asmcmdglobal_hash{'isdaemon'})
{
if($asmcmdshare_process_status =~ "READCOMMANDS")
{
if($exception =~ "ALARM")
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Exiting after " .
"$asmcmdshare_daemonidlewait secs idle wait.",
'y', 'n');
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
"$$asmcmdshare_pipetoread. Exiting.",
'y', 'n');
}
asmcmdshare_removebgpipe();
exit(0);
}
elsif($asmcmdshare_process_status =~ "USERINPUT")
{
if($exception =~ "ALARM")
{
asmcmdshare_trace(3, "$asmcmdshare_logheader User input timed " .
"out. Skipping.", 'y', 'n');
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
"$$asmcmdshare_pipetoread. Skipping user " .
"input.",
'y', 'n');
}
return;
}
elsif($asmcmdshare_process_status =~ "OPENREADPIPE" ||
$asmcmdshare_process_status =~ "OPENWRITEPIPE")
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
"$$asmcmdshare_pipetoread. Exiting.", 'y', 'n');
asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
exit(0);
}
}
else
{
if($exception =~ "ALARM")
{
if($asmcmdshare_process_status =~ "RECEIVINGOUTPUT")
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Output from ASMCMD " .
"Background process timed out. Disabling " .
"connection pooling and retrying command.",
'y', 'n');
$asmcmdshare_process_status = "RUNNING";
return;
}
}
if($asmcmdshare_process_status =~ "OPENREADPIPE" ||
$asmcmdshare_process_status =~ "OPENWRITEPIPE")
{
if($asmcmdshare_process_status =~ "OPENREADPIPE")
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
"$$asmcmdshare_pipetoread. Connection Pooling " .
"failed. Retrying.", 'y', 'y');
}
elsif($asmcmdshare_process_status =~ "OPENWRITEPIPE")
{
# bug#21204705: do not trace msg to console with OPENWRITEPIPE
# If the last arg is 'y', the process dies in trace().
# Thus unlink is not executed.
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not open pipe " .
"$$asmcmdshare_pipetowrite. Connection Pooling " .
"failed. Retrying.", 'y', 'n');
asmcmdshare_closewritepipe();
if(-p $$asmcmdshare_pipetowrite)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ".
"Removed pipe $$asmcmdshare_pipetowrite: ".
"unresponsive pipe, the BG could have exited ".
"without removing the pipe.", 'y', 'n');
unlink $$asmcmdshare_pipetowrite; # Remove the unresponsive pipe.
# The ASMCMD background process
# could have exited without
# removing the pipe.
asmcmdshare_removebgpipe();
}
}
elsif($asmcmdshare_process_status =~ "OPENREADPIPE")
{
asmcmdshare_closereadpipe();
if(-p $$asmcmdshare_pipetoread)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ".
"Removed pipe $$asmcmdshare_pipetoread: ".
"unresponsive pipe.", 'y', 'n');
unlink $$asmcmdshare_pipetoread; # Remove the unresponsive pipe.
}
}
$asmcmdshare_process_status = "RUNNING";
die;
}
}
}
########
# NAME
# asmcmdshare_signal_handler
#
# DESCRIPTION
# This routine catches and handles OS signals.
#
# PARAMETERS
# sigtype (IN) - string: type of signal caught.
#
# RETURNS
# Null.
#
# NOTES
# Currently, this routine catches SIGINT and SIGUSR1.
########
sub asmcmdshare_signal_handler
{
my ($sigtype) = shift;
asmcmdshare_trace(1, "Proc: [$$] SIG: [$sigtype]", 'y', 'n');
if($sigtype eq 'INT')
{
if($asmcmdglobal_hash{'ispooled'})
{
if($asmcmdglobal_hash{'isdaemon'})
{
if(defined ($asmcmdglobal_hash{'sth'}))
{
$asmcmdglobal_hash{'sth'}->cancel;
asmcmdshare_finish($asmcmdglobal_hash{'sth'});
$asmcmdglobal_hash{'sth'} = undef;
}
if(-p $asmcmdglobal_hash{'fgpipe'})
{
asmcmdshare_print($asmcmdshare_disablecp_string);
}
asmcmdshare_trace(3, "$asmcmdshare_logheader Received an INT " .
"signal. Exiting.",
'y', 'n');
asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
exit(0);
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Received an INT " .
"signal.", 'y', 'n');
asmcmdshare_closereadpipe();
asmcmdshare_removebgpipe() if ($$ eq $asmcmdglobal_hash{'bgpid'});
exit(0);
}
}
else # If it is not a connection pooled scenario
{
if ($asmcmdglobal_hash{'running'} == 1)
{
$asmcmdglobal_hash{'running'} = 0;
return;
}
if(defined ($asmcmdglobal_hash{'sth'}))
{
$asmcmdglobal_hash{'sth'}->cancel;
asmcmdshare_finish($asmcmdglobal_hash{'sth'});
$asmcmdglobal_hash{'sth'} = undef;
}
print STDERR "\n";
die;
}
}
elsif ($sigtype eq 'USR1')
{
if (defined($asmcmdglobal_hash{'ismaster'}))
{ # Is the master of a master-slaves scenario
if ($asmcmdshare_state eq 'CONNECTED') {
$asmcmdshare_missing--;
asmcmdshare_trace(1, "Waiting for slaves: [$asmcmdshare_missing]",
'y', 'n');
}
elsif($asmcmdshare_state eq 'WAITING')
{
$asmcmdshare_wait_flag = 0;
}
}
elsif (defined($asmcmdglobal_hash{'isslave'}))
{ # Is a slave of a master-slaves scenario
if ($asmcmdshare_state eq 'RUNNING') {
$exit = 1;
}
elsif( $asmcmdshare_state eq 'WAITING')
{
$asmcmdshare_wait_flag = 0;
}
# TODO: implement signal handle for slave
}
}
elsif ($sigtype eq 'QUIT')
{
asmcmdshare_trace(1, "Received signal 'quit'", 'y', 'n');
if (defined($asmcmdglobal_hash{'isslave'}))
{
asmcmdshare_trace(1, "Slave $$ killed by master process", 'y', 'n');
exit 1;
}
}
}
##############################################################################
########################### Normalization Routines ###########################
# NOTE:
# ASMCMD defines the parent and reference indices for the directory
# '+' to be both -1. ASMCMD defines the reference index of a diskgroup
# to be (group_number * 2^24) and its parent index to be -1.
#
########
# NAME
# asmcmdshare_normalize_path
#
# DESCRIPTION
# This routine is the top-level normalization routine. It calls
# asmcmdshare_make_absolute() to get an absolute path to $path, if it's not
# already absolute. It then calls asmcmdshare_validate_path() to check if
# every directory level of the path exists.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# path (IN) - the path to be normalized.
# spprserr (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
# ret_ref (OUT) - reference to return flag: 0 if path normalized; -1 if
# path failed to normalize.
#
# RETURNS
# A hash of references to four parallel arrays:
# $ret{'path'} - reference to array of normalized paths.
# $ret{'ref_id'} - reference to array of reference indices for the files
# or directories specified by the paths.
# $ret{'par_id'} - reference to array of parent indices for the files or
# directories specified by the paths.
# $ret{'gnum'} - reference to array of group numbers for the files or
# directories specified by the paths.
#
# NOTES
# ASMCMD defines the reference index, the parent index, and the group
# number of '+' to be -1. ASMCMD also defines the reference index of a
# diskgroup to be (group_number * 2^24).
########
sub asmcmdshare_normalize_path
{
my ($dbh, $path, $spprserr, $ret_ref) = @_;
my ($full_path); # Absolute path. #
my (%ret); # Hash of references to parallel arrays; see RETURN above. #
my (@paths); # Array of normalized path(s), plural if using wildcards. #
my (@ref_ids); # Parallel array of reference indices. #
my (@par_ids); # Parallel array of parent indices, parallel to @paths. #
my (@dg_nums); # Parallel array of diskgroup numbers for the paths. #
# Replace DOS/Windows style '\' to Unix style '/'
if ($path =~ m/\\/)
{
$path =~ s/\\/\//g ; # replace all occurecte(s) DOS '\' to Unix '/' #
}
$full_path = asmcmdshare_make_absolute($path);
$full_path =~ s/\/{2,}/\//g; # Remove all duplicate forward slashes. #
$full_path =~ s,^\+/,\+,; # '+/' should become just '+'. #
$$ret_ref = 0; # Zero by default. #
# If the full path is '+', then there is no need to call
# asmcmdshare_validate_path(). ASMCMD defines the reference and parent
# indices of '+' to be -1.
if ($full_path eq '+')
{
push (@paths, '+');
push (@ref_ids, -1);
push (@par_ids, -1);
push (@dg_nums, -1);
$ret{'path'} = \@paths;
$ret{'ref_id'} = \@ref_ids;
$ret{'par_id'} = \@par_ids;
$ret{'gnum'} = \@dg_nums;
return %ret;
}
# Validate the path.
%ret = asmcmdshare_validate_path ($dbh, $full_path, $spprserr);
# Nothing returned; then set -1. #
$$ret_ref = -1 if (! defined ($ret{'ref_id'}) ||
(@{ $ret{'ref_id'} } == 0));
return %ret;
}
########
# NAME
# asmcmdshare_validate_path
#
# DESCRIPTION
# This routine is a wrapper routine that calls
# asmcmdshare_validate_path_recurse(). It pre-processes the parameters
# before calling asmcmdshare_validate_path_recurse().
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# work_path (IN) - path to be validated; must be absolute path.
# spprserr (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
# A hash of references to four parallel arrays:
# $ret{'path'} - reference to array of validated paths.
# $ret{'ref_id'} - reference to array of reference indices for the files
# or directories specified by the paths.
# $ret{'par_id'} - reference to array of parent indices for the files or
# directories specified by the paths.
# $ret{'gnum'} - reference to array of group numbers for the files or
# directories specified by the paths.
#
# NOTES
#
########
sub asmcmdshare_validate_path
{
my ($dbh, $work_path, $spprserr) = @_;
my (%ret); # Hash of references to parallel arrays; see RETURN above. #
my ($cont) = 0; # Whether to continue loop after #
# recursive call. #
my ($wpath_upd) = ''; # Used to update $work_path in a lower #
# level from a higher level recursive #
# call when the call completes before #
# $work_path is fully parsed, i.e.
# $cont == 1. #
my ($i);
$work_path =~ s,^\+,,; # Remove '+' for asmcmdshare_validate_path_recurse. #
$asmcmdglobal_hash{'nfnd'} = 0; # Must reset not found flag at start. #
%ret = asmcmdshare_validate_path_recurse ($dbh, $work_path, \$cont,
\$wpath_upd, -1, -1, 0, $spprserr);
# Now we put the '+' back for every path that's returned.
if (defined ($ret{'path'}))
{
for ($i = 0; $i < @{ $ret{'path'} }; $i++)
{
$ret{'path'}->[$i] = '+' . $ret{'path'}->[$i]
unless ($ret{'path'}->[$i] =~ m,^\+,);
}
asmcmdshare_trace(4, "NOTE: Completed validating the path $work_path",
'n', 'n');
}
return %ret;
}
########
# NAME
# asmcmdshare_validate_path_recurse
#
# DESCRIPTION
# This recursive routine parses every level of $work_path to check if it
# is a valid entry in v$asm_alias.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# work_path (IN) - abolute path to be validated; no '+' prefix.
# cont_ref (IN/OUT) - internal state for recursive function: 1 means
# to continue while loop after recursive call
# returns; 0 means to terminate loop after its
# return.
# wpath_upd_ref (OUT) - the unparsed portion of $work_path passed back
# to the calling function by the recursively
# called function; this scenario can happen when
# in the scenario of having '*/..' in a path;
# $cont_ref must be set to 1 in conjunction, and
# the calling function will finish parsing
# $work_path.
# cur_ref_id (IN) - the reference index of the level of directory
# that is currently processed
# cur_par_id (IN) - the parent index of the level of directory
# that is currently processed
# level (IN) - the level of recursive call; top level starts
# at 0; $level increments by 1 for each new level
# of recursive call.
# spprserr (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
# A hash of references to four parallel arrays:
# $ret{'path'} - reference to array of validated paths.
# $ret{'ref_id'} - reference to array of reference indices for the files
# or directories specified by the paths.
# $ret{'par_id'} - reference to array of parent indices for the files or
# directories specified by the paths.
# $ret{'gnum'} - reference to array of group numbers for the files or
# directories specified by the paths.
#
# NOTES
# This routine does not call itself recursively, but calls
# asmcmdshare_validate_dir() and asmcmdshare_validate_dg(), which call
# asmcmdshare_validate_path_recurse() recursively. If a level of directory is
# a diskgroup, we call asmcmdshare_validate_dg(); if a level of directory is
# an alias directory, we call asmcmdshare_validate_dir().
#
# This routine handles all special cases that a path can have, including
# the pseudo-directories '.' and '..', and the wildcard '*'. An interesting
# and special scenario may be '+dgroup/dir/*/../../dir', which should
# normalize to '+dgroup/dir' if '+dgroup/dir' exists and if at least one
# sub-directory exists under '+dgroup/dir'.
########
sub asmcmdshare_validate_path_recurse
{
my ($dbh, $work_path, $cont_ref, $wpath_upd_ref,
$cur_ref_id, $cur_par_id, $level, $spprserr) = @_;
my (@subdirs); # Array of tokenized levels of directories, obtained from #
# $work_path by separating by the '/'. #
my ($dir); # One level of directory, taken from @subdirs. #
my (%ret); # Hash of references to parallel arrays; see RETURN above. #
my (%empty); # Empty hash equivalent of %ret, used when validation fails. #
my (@vld_path_a); # Array of validated directory tokens from $vld_path. #
my ($vld_path) = ''; # String for the subset of $work_path that's #
# validated so far. #
my ($wpath_cpy) = '+'. $work_path; # Copy of $work_path that has '+' #
# prefix added. #
my ($wpath_slash); # Copy of $work_path that has '/' #
# prefix added. #
my ($vld_path_slash); # Copy of $vld_path with '/' suffix added. #
my (@eargs); # Array of error arguments. #
my ($subdirExists); # subdirectory exists or not #
$subdirExists = 'true' ; # assume each subdir component exists #
@subdirs = split (/\//, $work_path); # Tokenize $work_path by '/'. #
# We validate the path one directory level at a time, starting from the top
# level directory.
while (defined ($dir = shift(@subdirs)))
{
$work_path = join ('/', @subdirs); # $work_path is not stripped of $dir. #
next if ($dir eq '.');
if ($dir eq '..')
{
if (@vld_path_a > 0)
{
if ($subdirExists eq 'true')
{
# pop out the last subdir - only if exists.
pop (@vld_path_a);
}
$vld_path = join ('/', @vld_path_a);
# Call asmcmdshare_cdup_update_ids() to shift the indices up a level.
($cur_ref_id, $cur_par_id) = asmcmdshare_cdup_update_ids($dbh,
$cur_par_id);
# Update indices here.
$ret{'path'}->[0] = $vld_path;
$ret{'ref_id'}->[0] = $cur_ref_id;
$ret{'par_id'}->[0] = $cur_par_id;
$ret{'gnum'}->[0] = $asmcmdglobal_hash{'gnum'};
next;
}
else
{ # @vld_path_a <= 0
# @vld_path_a has 0 elements here, so either we're in '+' or we'll
# in a recursive call right after parsing a '*/..'.
if ($level > 0)
{ # If we're here that means this scenario:
# cdup in case of +dgName/dir/*/../ . . .
# Here is the special case where a recursive call can terminate prior
# to $work_path being entirely parsed. Thus, the calling function
# needs to continue the loop after recursion returns. Set OUT
# values below so the caller knows to continue to parse the
# remainder of $work_path that's not finished here.
$$cont_ref = 1;
$$wpath_upd_ref = $work_path;
return %empty;
}
# Already in '+'; cd '..' in '+' is still '+'.
next;
}
}
elsif ($dir =~ /$ASMCMDSHARE_N_CSET_W_WCARD/)
{
# Bad charset, no match.
if ($level == 0)
{
# Top level, if no match, then none found.
$asmcmdglobal_hash{'nfnd'} = 1;
# Print error only if none found.
if (@vld_path_a == 0)
{
@eargs = ($dir);
asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
}
else
{ # @vld_path_a != 0 #
@eargs = ($dir);
asmcmdshare_error_msg(8004, \@eargs) unless ($spprserr);
}
}
return %empty; # Non-found. #
}
elsif ($dir =~ $ASMCMDGLOBAL_WCARD_CHARS)
{
# It's the $ASMCMDSHARE_CSET_W_WCARD.
if (asmcmdshare_is_wildcard_cmd($asmcmdglobal_hash{'cmd'}))
{
# Wildcard supported; call recursively.
if (($level == 0) && (@vld_path_a == 0))
{
# $dir must be diskgroup name.
%ret = asmcmdshare_validate_dg ($dbh, $dir, $work_path, $cont_ref,
$wpath_upd_ref, $level, 1, $spprserr);
}
else
{ # $level != 0 || @vld_path_a != 0 #
# $dir is a normal directory within a diskgroup.
%ret = asmcmdshare_validate_dir ($dbh, $dir, $work_path, $cont_ref,
$wpath_upd_ref, $vld_path, $cur_ref_id,
$level, 1, $spprserr);
}
# Normally, the loop should terminate after a return from a recursive
# call, as the recursive function should have finished parsing the
# rest of $work_path. However, the one exception is when the
# recursive call encounters a '..' after a '*', e.g. '*/..', or
# even '*/blah/../..', which simplifies to '*/..', assuming 'blah'
# exists. In this exception, the $cont reference is set to 1.
if ($$cont_ref == 1)
{
$$cont_ref = 0; # Reset. #
@subdirs = split (/\//, $$wpath_upd_ref); # Get updated work_path. #
$ret{'path'}->[0] = $vld_path;
$ret{'ref_id'}->[0] = $cur_ref_id;
$ret{'par_id'}->[0] = $cur_par_id;
$ret{'gnum'}->[0] = $asmcmdglobal_hash{'gnum'};
# So do not stop loop, hence no return statement.
}
else
{ # $$cont_ref != 1 #
if ( ( @{ $ret{'path'} } == 0 ) && ($level == 0) )
{
# Since we're on top level, an empty path array means we've
# exhausted all possible matches for the wildcard and still no
# match. Thus, print 'not found' message.
$asmcmdglobal_hash{'nfnd'} = 1;
$wpath_slash = '';
$wpath_slash = '/' . $work_path if ($work_path ne '');
$vld_path_slash = '';
$vld_path_slash = $vld_path . '/' if ($vld_path ne '');
if (($vld_path ne '') || ($work_path ne ''))
{ # Trying to match an alias; use 8002. #
@eargs = ($dir . $wpath_slash, '+' . $vld_path_slash);
asmcmdshare_error_msg(8002, \@eargs) unless ($spprserr);
}
else
{ # Trying to match a diskgroup; use 8001. #
@eargs = ($dir . $wpath_slash);
asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
}
}
# Since the recursive function call has finished parsing $work_path,
# the caller can terminte its loop and return.
return %ret;
}
}
else
{ # asmcmdshare_is_wildcard_cmd($asmcmdglobal_hash{'cmd'}) == FALSE #
# Wildcard not supported; so the fact we see a wildcard in $work_path
# means that the path fails to validate for sure, as there is no
# real alias name that contains a '*'. Thus no match.
if ($level == 0)
{
# Top level, if no match, then validate fails.
$asmcmdglobal_hash{'nfnd'} = 1;
if (@vld_path_a == 0)
{ # Invalid diskgroup name with a '*'. #
@eargs = ($dir);
asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
}
else
{ # Invalid alias name with a '*'. #
@eargs = ($dir);
asmcmdshare_error_msg(8004, \@eargs) unless ($spprserr);
}
}
return %empty; # No match, so return empty hash. #
}
}
else
{ # $dir !~ /\*/ #
# Normal $ASMCMDSHARE_CSET; process as normal directory.
if (($level == 0) && (@vld_path_a == 0))
{
# $dir must be diskgroup name.
%ret = asmcmdshare_validate_dg ($dbh, $dir, undef, undef, undef,
$level, 0, $spprserr);
}
else
{ # $level != 0 || @vld_path_a != 0 #
# $dir is a normal directory within a diskgroup.
%ret = asmcmdshare_validate_dir ($dbh, $dir, undef, undef, undef,
$vld_path, $cur_ref_id, $level, 0,
$spprserr);
}
if ( (defined ($ret{'path'})) && ( @{ $ret{'path'} } == 1 ) )
{
# Found match.
# Update indices.
$cur_ref_id = $ret{'ref_id'}->[0];
$cur_par_id = $ret{'par_id'}->[0];
# Update validate path and its tokens array.
@vld_path_a = split (/\//, $ret{'path'}->[0]);
$vld_path = join ('/', @vld_path_a);
}
else
{
# Dir not found, so stop loop.
# Bug # 16670094 In case of directory deleted by "rm -rf *", "cd .."
# will not find last parent directory - in case of sys alias -.
# Ignore this case and continue. $vld_XXX will valid components
if ( ($asmcmdglobal_hash{'cmd'} eq 'cd' ) && ($work_path eq ".."))
{
# subdir does not exist.
$subdirExists = 'false';
next;
}
if ($level == 0)
{
# Since we're on top level, an empty path array means we've
# exhausted all possible matches for the wildcard and still no
# match. Thus, print 'not found' message.
$asmcmdglobal_hash{'nfnd'} = 1;
if (@vld_path_a == 0)
{
@eargs = ($dir);
asmcmdshare_error_msg(8001, \@eargs) unless ($spprserr);
}
else
{
@eargs = ($dir, '+' . $vld_path . '/');
asmcmdshare_error_msg(8002, \@eargs) unless ($spprserr);
}
}
last; # No need to finish parsing $work_path, since no match. #
}
}
} # while() #
if (($level == 0) && (! $asmcmdglobal_hash{'nfnd'}))
{
if (@vld_path_a == 0)
{
# This area should not be reached unless the path simplifies to
# '+' after a series of '..', and no 'not found' occurred at $level
# equal to 0. If that's the case, then $work_path has normalized to
# simply '+'. So update return hash to hold the values for '+'.
$ret{'path'}->[0] = '+';
$ret{'ref_id'}->[0] = -1;
$ret{'par_id'}->[0] = -1;
$ret{'gnum'}->[0] = -1;
}
}
return %ret;
}
########
# NAME
# asmcmdshare_validate_dir
#
# DESCRIPTION
# This routine calls the appropriate functions to validate the existence
# of $dir in v$asm_alias.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# dir (IN) - the directory alias to be validated.
# work_path (IN) - remainder of the path to be validated in case $dir
# contains wildcard and need to call
# asmcmdshare_validate_path_recurse() recursively.
# cont_ref (OUT) - internal state for recursive function: 1 means
# to continue while loop after recursive call
# returns; 0 means to terminate loop after its
# return.
# wpath_upd_ref (OUT) - the unparsed portion of $work_path passed back
# to the calling function by the recursively
# called function; this scenario can happen when
# in the scenario of having '*/..' in a path;
# $cont_ref must be set to 1 in conjunction, and
# the calling function will finish parsing
# $work_path.
# vld_path (IN) - String for the subset of $work_path that's validated
# so far.
# old_ref_id (IN) - the parent index of $dir.
# level (IN) - the level of recursive call; top level starts
# at 0; $level increments by 1 for each new level
# of recursive call.
# wildcard (IN) - boolean: true iff $dir contains wildcard '*'.
# spprserr (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
# A hash of references to four parallel arrays:
# $ret{'path'} - reference to array of validated paths.
# $ret{'ref_id'} - reference to array of reference indices for the files
# or directories specified by the paths.
# $ret{'par_id'} - reference to array of parent indices for the files or
# directories specified by the paths.
# $ret{'gnum'} - reference to array of group numbers for the files or
# directories specified by the paths.
#
# NOTES
# This routine calls asmcmdshare_get_subdir_simple() and get_subdir() to
# validate the existence of $dir. If $dir contains a wildcard, then this
# routine calls asmcmdshare_validate_path_recurse() recursively on every
# match of $dir returned by asmcmdshare_get_subdirs().
########
sub asmcmdshare_validate_dir
{
my ($dbh, $dir, $work_path, $cont_ref, $wpath_upd_ref, $vld_path,
$old_ref_id, $level, $wildcard, $spprserr) = @_;
my (%results); # Hash of refs to parallel arrays returned by #
# asmcmdshare_validate_path_recurse(). #
my (%ret); # Hash of references to parallel arrays; see RETURN above. #
my (@vld_dirs); # Array of validated paths. #
my (@vld_ref_ids); # Array of reference indices for the file or directory #
# specified by the paths. #
my (@vld_par_ids); # Array of parent indices for the file or directory #
# specified by the paths. #
my (@vld_gnums); # Array of group numbers for the file or directory #
# specified by the paths. #
my (@subdirs); # Array of alias names that match wildcard expression $dir. #
my ($new_ref_id); # Reference index of $dir, $wildcard is FALSE. #
my ($tmp); # Temp string to hold a path from @{ $results{'path'} }. #
my ($iter);
my ($i);
if (! $wildcard)
{ # $dir contains no wildcard; process it as a normal alias. #
# Obtain reference index from parent index and alias name.
$new_ref_id = asmcmdshare_get_subdir_simple($dbh, $old_ref_id, $dir);
# If $dir exists, i.e. reference index is defined, then save them as
# a validated entry.
if (defined ($new_ref_id))
{
push (@vld_dirs, $vld_path . '/' . $dir);
push (@vld_ref_ids, $new_ref_id);
push (@vld_par_ids, $old_ref_id);
push (@vld_gnums, $asmcmdglobal_hash{'gnum'});
}
}
else
{ # $dir contains wildcards. #
# Obtain all entries with alias names that match the wildcard expression
# $dir.
asmcmdshare_get_subdirs($dbh, \@subdirs, undef, $asmcmdglobal_hash{'gnum'},
undef, $old_ref_id, $dir, undef, 0, 0);
if (@subdirs > 0)
{ # If there is one or more matches. #
foreach $iter (@subdirs) # Process each match. #
{
if ($work_path ne '') # Call recursively only if more path left to #
{ # validate. #
# Call recursively to parse the remainder of $work_path; e.g. if
# $dir matchs, 'a', 'b', and 'c'; and $work_path is 'foo', we
# we need to validate 'a/foo', 'b/foo', and 'c/foo'.
%results = asmcmdshare_validate_path_recurse ($dbh, $work_path,
$cont_ref, $wpath_upd_ref, $iter->{'reference_index'},
$iter->{'parent_index'}, $level + 1, $spprserr);
# If it's a '*/..', then there's no need to loop through all
# matches of '*' here.
last if ($$cont_ref == 1);
# Make sure that the hash has defined elements before attempting
# to do any dereferencing.
if (defined($results{'path'}))
{
# Add all the results from %results to the @vld* arrays.
for ($i = 0; $i < @{ $results{'path'} }; $i++)
{
$tmp = $results{'path'}->[$i];
# We need to add each path from %results to $vld_path to get
# full validated paths. e.g. we have $vld_path as
# '+dgroup/new', and $iter->{'name'} as 'a', and %results as
# 'foo/bar', 'foo/candy', then we need to add the paths
# '+dgroup/new/a/foo/candy' and
# '+dgroup/new/a/foo/bar to @vld_dirs.
# This if clause is present only to prevent the concatenation
# of a possibly null string $tmp, which would result in a
# Perl warning if using perl -w.
if ($tmp ne '')
{
$tmp =~ s,^/,,; # Remove leading '/' if any. #
$tmp = $vld_path . '/' . $iter->{'name'} . '/' . $tmp;
}
else
{
$tmp = $vld_path . '/' . $iter->{'name'};
}
# Now save each entry.
push (@vld_dirs, $tmp);
push (@vld_ref_ids, $results{'ref_id'}->[$i]);
push (@vld_par_ids, $results{'par_id'}->[$i]);
push (@vld_gnums, $results{'gnum'}->[$i]);
}
}
}
else
{ # $workpath eq '' #
# No need to call asmcmdshare_validate_path_recurse(); simply append
# each match of $dir to $vld_path and add the resulting string to
# @vld_dirs.
$tmp = $vld_path . '/' . $iter->{'name'};
push (@vld_dirs, $tmp);
push (@vld_ref_ids, $iter->{'reference_index'});
push (@vld_par_ids, $iter->{'parent_index'});
push (@vld_gnums, $asmcmdglobal_hash{'gnum'});
}
}
}
}
# Prepare return hash.
$ret{'path'} = \@vld_dirs;
$ret{'ref_id'} = \@vld_ref_ids;
$ret{'par_id'} = \@vld_par_ids;
$ret{'gnum'} = \@vld_gnums;
return %ret;
}
########
# NAME
# asmcmdshare_validate_dg
#
# DESCRIPTION
# This routine calls the appropriate functions to validate the existence
# of $gname in v$asm_diskgroup.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# work_path (IN) - remainder of the path to be validated in case $gname
# contains wildcard and need to call
# asmcmdshare_validate_path_recurse() recursively.
# cont_ref (OUT) - internal state for recursive function: 1 means
# to continue while loop after recursive call
# returns; 0 means to terminate loop after its
# return.
# wpath_upd_ref (OUT) - the unparsed portion of $work_path passed back
# to the calling function by the recursively
# called function; this scenario can happen when
# in the scenario of having '*/..' in a path;
# $cont_ref must be set to 1 in conjunction, and
# the calling function will finish parsing
# $work_path.
# level (IN) - the level of recursive call; top level starts
# at 0; $level increments by 1 for each new level
# of recursive call.
# wildcard (IN) - boolean: true iff $gname contains wildcard '*'.
# spprserr (IN) - SuPPReSs ERRor: 1 shows no errors, 0 shows error.
#
# RETURNS
# A hash of references to four parallel arrays:
# $ret{'path'} - reference to array of validated paths.
# $ret{'ref_id'} - reference to array of reference indices for the file
# or directory specified by the paths.
# $ret{'par_id'} - reference to array of parent indices for the file or
# directory specified by the paths.
# $ret{'gnum'} - reference to array of group numbers for the file or
# directory specified by the paths.
#
# NOTES
# If $gname contains a wildcard, then this routine calls
# asmcmdshare_validate_path_recurse() recursively on every match of $gname
# returned by asmcmdshare_get_dg().
########
sub asmcmdshare_validate_dg
{
my ($dbh, $gname, $work_path, $cont_ref, $wpath_upd_ref,
$level, $wildcard, $spprserr) = @_;
my (@dg_list); # Array of diskgroup hashes returned by asmcmdshare_get_dg. #
my (%results); # Hash of refs to parallel arrays returned by #
# asmcmdshare_validate_path_recurse(). #
my (%ret); # Hash of references to parallel arrays; see RETURN above. #
my (@vld_dirs); # Array of validated paths. #
my (@vld_ref_ids); # Array of reference indices for the paths. #
my (@vld_par_ids); # Array of parent indices for the paths. #
my (@vld_gnums); # Array of group numbers for the paths. #
my ($gnum); # The group number for $gname, if $wildcard is FALSE. #
my ($tmp); # Temp string to hold a path from @{ $results{'path'} }. #
my ($iter);
my ($i);
if (! $wildcard)
{ # No wildcard, process normally. #
$gnum = asmcmdshare_get_gnum_from_gname($dbh, $gname);
# If group number exists for the group name, then the group name must be
# valid.
if (defined ($gnum))
{
# Save validated diskgroup information.
push (@vld_dirs, $gname);
push (@vld_ref_ids, $gnum << 24);
push (@vld_par_ids, -1);
push (@vld_gnums, $gnum);
# Update globals for default diskgroup name and number.
$asmcmdglobal_hash{'gname'} = $gname;
$asmcmdglobal_hash{'gnum'} = $gnum;
}
}
else
{ # Wildcard, call recursively. #
@dg_list = asmcmdshare_get_dg($dbh, $gname, 0, 0);
# Get matching diskgroups. #
# the wildcard expression $gname. #
if (@dg_list > 0)
{ # At least one dg found. #
foreach $iter (@dg_list) # Process one match at a time. #
{
# Update global current diskgroup information.
$asmcmdglobal_hash{'gname'} = $iter->{'name'};
$asmcmdglobal_hash{'gnum'} = $iter->{'group_number'};
if ($work_path ne '') # Call recursively only if more path left to #
{ # validate. #
# Call recursively to parse the remainder of $work_path.
%results = asmcmdshare_validate_path_recurse ($dbh, $work_path,
$cont_ref, $wpath_upd_ref, $iter->{'group_number'} << 24,
-1, $level + 1, $spprserr);
# If it's a '*/..', then there's no need to loop through all
# matches of '*' here.
last if ($$cont_ref == 1);
# Make sure that the hash has defined elements before attempting
# to do any dereferencing.
if (defined($results{'path'}))
{
# Add all the results from %results to the @vld* arrays.
for ($i = 0; $i < @{ $results{'path'} }; $i++)
{
# Build fully validated paths.
$tmp = $results{'path'}->[$i];
$tmp =~ s,^/,,; # Remove leading '/' if any. #
$tmp = $iter->{'name'} . '/' . $tmp;
# Now save each entry.
push (@vld_dirs, $tmp);
push (@vld_ref_ids, $results{'ref_id'}->[$i]);
push (@vld_par_ids, $results{'par_id'}->[$i]);
push (@vld_gnums, $results{'gnum'}->[$i]);
}
}
}
else # $work_path eq '' #
{
# No need to call asmcmdshare_validate_path_recurse(); simply add
# each matching group name to @vld_dirs.
push (@vld_dirs, $iter->{'name'});
push (@vld_ref_ids, $iter->{'group_number'} << 24);
push (@vld_par_ids, -1);
push (@vld_gnums, $iter->{'group_number'});
}
}
}
}
# Prepare return hash.
$ret{'path'} = \@vld_dirs;
$ret{'ref_id'} = \@vld_ref_ids;
$ret{'par_id'} = \@vld_par_ids;
$ret{'gnum'} = \@vld_gnums;
return %ret;
}
########
# NAME
# asmcmdshare_make_absolute
#
# DESCRIPTION
# This routine checks to see if $path is an absolute path; if not, it
# attaches the current directory to the front of $path and returns the
# full path.
#
# PARAMETERS
# path (IN) - user-entered relative or absoluate path.
#
# RETURNS
# $path if it is absolute; $asmcmdglobal_hash{'cwdnm'} . '/'. $path
# otherwise.
########
sub asmcmdshare_make_absolute
{
my ($path) = shift;
my ($full_path) = $path;
if ($path !~ m,^\+,) # An absolute path always starts with '+'. #
{ # If not, attach current directory to the front of $path. #
$full_path = $asmcmdglobal_hash{'cwdnm'} . '/'. $path;
asmcmdshare_trace(5, "asmcmdshare_make_absolute(): Converted to absolute"
." path $path", 'n', 'n');
}
return $full_path;
}
########
# NAME
# asmcmdshare_cdup_update_ids
#
# DESCRIPTION
# Given a parent index of an alias, this function returns the reference and
# parent indices that belong the parent of that alias.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# par_id (IN) - the parent index of the child alias.
#
# RETURNS
# Two-element array:
# Element 1: reference index of parent alias.
# Element 2: parent index of parent alias.
########
sub asmcmdshare_cdup_update_ids
{
my ($dbh, $par_id) = @_;
my ($ref_id) = $par_id; # Ref id always becomes par id. #
if ($par_id == -1)
{ # cdup from '+' or '+dgName'. #
return ($ref_id, $par_id);
}
elsif ($par_id == ($asmcmdglobal_hash{'gnum'} << 24))
{ # cdup from '+dgName/dir1'. #
return ($ref_id, -1);
}
else
{ # All other cases, i.e. 2+ levels of subdirs. #
$par_id = asmcmdshare_get_par_id($dbh, $par_id);
return ($ref_id, $par_id);
}
}
##############################################################################
############################## MISC Routines #################################
########
# NAME
# asmcmdshare_ls_calc_min_col_wid
#
# DESCRIPTION
# This formatting routine calculates the miminum column width of each
# displayed attribute based on the values from v$asm_alias and v$asm_file.
#
# PARAMETERS
# list_ref (IN) - reference to list of hashes, each hash holding
# values for attributes of one alias entry.
# min_col_wid_ref (IN/OUT) - reference to hash of minimum column width.
#
# RETURNS
# Null.
#
# NOTES
# The hashes in @{$list_ref} must be populated with values from v$asm_alias
# and v$asm_file if the entry is a file. Thus, asmcmdshare_get_subdirs()
# must be called first. Important: these hashes contains keys that are not
# attribute names. These extra keys are internal to ASMCMD and do not
# correspond to a column name in v$asm_alias or v$asm_file. One example
# is 'name_print', which is used to store the value to be printed under
# the name column, which can be the same as the value for the 'name' key,
# but it doesn't have to be.
#
# %{$min_col_wid_ref} must be initialized with the default minimum column
# widths before calling this routine. Thus, asmcmdbase_ls_init_col_wid() or
# asmcmdbase_lsdg_init_col_wid() must be called first. All keys in this
# hash are attribute names from v$asm_alias and/or v$asm_file.
#
# Important note: the hashes in @{$list_ref} can contain keys that are not
# in %{$min_col_wid_ref} and vice versa. Do not assume that if a key exists
# in one then it must in the other. Always use defined() to check, or else
# perl -w will complain.
########
sub asmcmdshare_ls_calc_min_col_wid
{
my ($list_ref, $min_col_wid_ref) = @_;
my ($entry); # Reference to one hash entry of attribute values in @list. #
my ($key); # One hash key of %{$entry}, used for iteration. #
# For the values of each attribute in the list of entries, find the one with
# the longest length. That length is the minimum column width for that
# attribute.
foreach $entry (@{$list_ref})
{
foreach $key (keys %{$entry})
{
if (defined ($min_col_wid_ref->{ $key }) &&
defined ($entry->{ $key }) &&
(length($entry->{ $key }) > $min_col_wid_ref->{ $key }))
{
# Save the longest length so far.
$min_col_wid_ref->{ $key } = length($entry->{ $key });
}
}
}
return;
}
########
# NAME
# asmcmdshare_version_cmp
#
# DESCRIPTION
# This function determines if the Oracle Database version $version1 is
# less than, equal to, or greater than Oracle Database version $version2.
#
# PARAMETERS
# version1 (IN) - Oracle Database version number one
# version2 (IN) - Oracle Database version number two
#
# RETURNS
# A negative number if $version1 is smaller or earlier than $version2;
# zero of $version1 equals $version2; and a positive number if $version1
# is greater or later than $version2.
#
# NOTES
# This function asserts that the version numbers are in the format
# a.b.c.d.e .
#
# We assume this order of significance for the five sub-version numbers:
# a is most significant, then b, then c, and then d. e is least
# significant.
########
sub asmcmdshare_version_cmp
{
my ($version1, $version2) = @_;
# Major database release number, database maintenance release number,
# application server release number, component-specific release number,
# platform-specific release number.
my ($v1_maj, $v1_maint, $v1_appserv, $v1_comp, $v1_plat);
my ($v2_maj, $v2_maint, $v2_appserv, $v2_comp, $v2_plat);
# Error array.
my (@eargs);
# Split $version1 into the five sub-version parts.
($v1_maj, $v1_maint, $v1_appserv, $v1_comp, $v1_plat)
= split (/\./, $version1, 5);
# Split $version2 into the five sub-version parts.
($v2_maj, $v2_maint, $v2_appserv, $v2_comp, $v2_plat)
= split (/\./, $version2, 5);
# Assert that we have all five sub-version parts for both versions.
@eargs = ('asmcmdshare_version_cmp05');
asmcmdshare_assert(defined($v1_maj), \@eargs);
@eargs = ('asmcmdshare_version_cmp07');
asmcmdshare_assert(defined($v1_maint), \@eargs);
@eargs = ('asmcmdshare_version_cmp09');
asmcmdshare_assert(defined($v1_appserv), \@eargs);
@eargs = ('asmcmdshare_version_cmp11');
asmcmdshare_assert(defined($v1_comp), \@eargs);
@eargs = ('asmcmdshare_version_cmp13');
asmcmdshare_assert(defined($v1_plat), \@eargs);
@eargs = ('asmcmdshare_version_cmp15');
asmcmdshare_assert(defined($v2_maj), \@eargs);
@eargs = ('asmcmdshare_version_cmp17');
asmcmdshare_assert(defined($v2_maint), \@eargs);
@eargs = ('asmcmdshare_version_cmp19');
asmcmdshare_assert(defined($v2_appserv), \@eargs);
@eargs = ('asmcmdshare_version_cmp21');
asmcmdshare_assert(defined($v2_comp), \@eargs);
@eargs = ('asmcmdshare_version_cmp23');
asmcmdshare_assert(defined($v2_plat), \@eargs);
# Compare major release number.
if ($v1_maj != $v2_maj)
{
return ($v1_maj - $v2_maj);
}
# Compare maintenance release number.
if ($v1_maint != $v2_maint)
{
return ($v1_maint - $v2_maint);
}
# Compare application server release number.
if ($v1_appserv != $v2_appserv)
{
return ($v1_appserv - $v2_appserv);
}
# Compare component release number.
if ($v1_comp != $v2_comp)
{
return ($v1_comp - $v2_comp);
}
# Compare platform release number.
return ($v1_plat - $v2_plat);
}
########
# NAME
# asmcmdshare_get_asm_version
#
# DESCRIPTION
# This function retrieves the ASM instance software version number from
# the ASM instance.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
#
# RETURNS
# The ASM instance sofware version number.
########
sub asmcmdshare_get_asm_version
{
my ($dbh) = shift; # Database handle. #
my ($sth); # SQL statement handle. #
my ($version); # ASM instance version. #
my ($query); # SQL query statement. #
my ($row); # Row of query results. #
# SQL for getting the ASM instance software version.
$query = 'select version from v$instance';
# Execute the SQL.
$sth = asmcmdshare_do_select($dbh, $query);
# Fetch the only row of results.
$row = asmcmdshare_fetch($sth);
# Get the version number.
$version = $row->{'VERSION'};
# Close the statement handle.
asmcmdshare_finish($sth);
return $version;
}
########
# NAME
# asmcmdshare_get_host_name
#
# DESCRIPTION
# This function obtains the current computer name
#
# PARAMETERS
# -none-
#
# RETURNS
# $ret - current computer name, undef if any errors.
#
# NOTES:
# $ENV{'HOSTNAME'} is not available in all Unix platforms, using "uname -n"
# On Windows, %COMPUTERNAME% environment variable will be defined always.
# If the domain part is contained in the hostname, it will be filtered out.
########
sub asmcmdshare_get_host_name
{
my ($hostname) = undef;
my ($ret) = undef; # return value with domain filtered out #
if ($^O =~ /win/i)
{
# windows - COMPUTERNAME environment will have the host name.
if (defined ($ENV{'COMPUTERNAME'}))
{
$hostname = $ENV{'COMPUTERNAME'};
asmcmdshare_trace (3, "Computer name $hostname", 'y', 'n');
}
else
{
asmcmdshare_trace (3, "Unable to obtain computer name", 'y', 'n');
}
}
else
{
# unix flavors
eval
{
$hostname = `uname -n`;
};
if ($@ ne '')
{
asmcmdshare_trace (3, "Computer name $hostname", 'y', 'n');
}
else
{
asmcmdshare_trace (3, "Unable to obtain computer name", 'y', 'n');
}
}
# untaint hostname value if exists.
if (defined($hostname))
{
$hostname =~ /([^\n^\r^\t]+)/;
$hostname = $1;
# Bug19561559: sometimes the host name we obtain may contain the
# domain (i.e. <hostname.domain>). In such a case, the
# domain name should be filtered out and only the node name should
# be returned.
($ret) = split(/\./, $hostname);
}
return $ret;
}
########
# NAME
# asmcmdshare_runcmd
#
# DESCRIPTION
# This function runs a command in OS shell and stores the output in the
# passed-in buffer.
#
# PARAMETERS
# cmd (IN) The command to be run
# buf_ref (OUT) Reference of the buffer to store command output
# lang (IN) Flag to specify if the command should be run with
# LANG=en_US.UTF-8
# redirect (IN) redirect stderr to stdin i.e 2>&1
#
# RETURNS
# 0 success
# non-zero failure
#
# NOTES
# If LANG env is set to non-english, ASMCMD doesn't know to parse it. This
# function sets LANG environment variable so that output can be parsed
# by other ASMCMD functions.
# CRS commands honours LANG only. To be transparent and usable to all
# commands, let us set both env variables LANG and NLS_LANG to English before
# running the command.
#
########
sub asmcmdshare_runcmd
{
my ($cmd, $buf_ref, $lang, $redirect) = @_;
my $ret = 0;
my $erc = 0; # program return code
my ($nlslang_sav) = ""; # current setting of env var NLS_LANG #
my ($lang_sav) = ""; # current setting of env var _LANG #
my @eargs;
@eargs = ('asmcmdshare_runcmd01');
asmcmdshare_assert((ref($buf_ref) eq "ARRAY"), \@eargs);
# Store current NLS_LANG/LANG value and set it to readable through code. #
if ($lang)
{
if (defined($ENV{'NLS_LANG'}) && $ENV{'NLS_LANG'})
{
$nlslang_sav = $ENV{'NLS_LANG'}
}
if (defined($ENV{'LANG'}) && $ENV{'LANG'})
{
$lang_sav = $ENV{'LANG'}
}
$ENV{'NLS_LANG'} = $ASMCMDSHARE_SHENV_NLSLANG;
$ENV{'LANG'} = $ASMCMDSHARE_SHENV_LANG;
}
# run the command
eval
{
# 20434627: if SIG{CHLD} is IGNORED, perl always set $? to -1 and $! to 10
# (no child process). It might be because when signal IGNORED, it seems
# somehow the child status is lost.
local $SIG{CHLD} = 'DEFAULT';
if ($^O =~ /win/i)
{
#no need to redirect output in Windows
my $line = `$cmd`;
@{$buf_ref} = split(/\n/, $line);
}
else
{
$cmd = $cmd . " 2>&1" if $redirect;
@{$buf_ref} = `$cmd`;
}
$erc = $CHILD_ERROR >> 8; # get program return code
};
if ($@ ne '')
{
# if there was a signal or coredump during command execution
$ret = 1;
asmcmdshare_trace(3, "NOTE: command ($cmd) execution signalled : $@",
'y', 'n');
# in failure, store the error in buffer.
@{$buf_ref} = $@;
}
else
{
# after command exeuction return the program return code
$ret = $erc;
if ($ret != 0)
{
asmcmdshare_trace(3, "NOTE: command execution ($cmd) returned : $ret",
'y', 'n');
}
}
# restore the NLS_LANG value. set NLS_LANG only if it was set previously
if ($lang)
{
$ENV{'NLS_LANG'} = ($nlslang_sav) ? $nlslang_sav : undef;
$ENV{'LANG'} = ($lang_sav) ? $lang_sav : undef;
asmcmdshare_trace(3, "reverted LANG/NLS_LANG to $lang_sav/$nlslang_sav ",
'y', 'n');
}
return $ret;
}
########
# NAME
# asmcmdshare_is_dev_env
#
# DESCRIPTION
# This function checks whether it is being executed in a development
# environment.
#
# PARAMETERS
# None
#
# RETURNS
# 1 if in development environment
# 0 otherwise
#
# NOTES
# Whether we are in a development environment is determined by whether
# certain environment variables are set.
#
########
sub asmcmdshare_is_dev_env
{
my $ret = 0;
if ((defined($ENV{'_SCLS_DEVELOPMENT'}) &&
uc($ENV{'_SCLS_DEVELOPMENT'}) eq "TRUE") ||
(defined($ENV{'HAS_DEVELOPMENT_ENVIRONMENT'}) &&
uc($ENV{'HAS_DEVELOPMENT_ENVIRONMENT'}) eq "TRUE"))
{
$ret = 1;
}
return $ret;
}
########
# NAME
# asmcmdshare_set_instance
#
# DESCRIPTION
# This function looks for the SID value for the specified type of instance
#
# PARAMETERS
# tgt (IN) - type of the target instance, it could be ASM, IOS or APX
#
# RETURNS
# The target's SID on success
# empty string when the target's SID could not be fetched.
# undef on error
#
# NOTES
#
########
sub asmcmdshare_set_instance
{
my $tgt = shift;
my $sid = undef;
my @procs;
my @pmons;
my $pmon;
my $sth;
my $inst;
my @what;
my @from;
return undef unless ($tgt =~ m/^(ASM|IOS|APX)$/);
if ($^O =~ m/win/i) # Windows #
{
@procs = `tasklist`;
}
else # UNIX default #
{
@procs = `ps -ef`;
}
@pmons = grep(/\w\w\w_pmon_\S+/, @procs);
foreach $pmon (@pmons)
{
if ($pmon =~ /.*(\w{3})_pmon_(\S+)/)
{
my $inst = uc $1; # Instance type
my $insn = $2; # Instance name
if ($inst =~ /$tgt/)
{
$sid = $insn;
last;
}
}
}
if (!defined($sid))
{
asmcmdshare_trace(1, "Could not identify instance name for target ".
"$tgt", 'y', 'y');
$sid = '';
}
return $sid;
}
########
# NAME
# asmcmdshare_trace
#
# DESCRIPTION
# This function prints the messages either in alert logs or trace levels
# depending on the trace level associated with the message. clsecho is a
# tool which is used to write the messages in files. The default level of
# tracing is set to 'normal'.
#
# USAGE
# asmcmd -v [ errors| warnings| normal| info| debug ]
#
# PARAMETERS
# msg_level (IN) - The trace level associated with the message.
# msg (IN) - The message to be printed into files.
# timestamp (IN) - The timestamp is printed along with msg if set to 'y'
# The timestamp is not printed along with msg if set to 'n'
# console (IN) - If set to 'y' the message is printed to console as well.
#
# RETURNS
# Null.
#
# NOTES
# There are 5 levels of tracing.
# Level 1 - ERRORS - Tracing errors in execution path.
# Level 2 - WARNINGS - Tracing warnings in execution path.
# Level 3 - NORMAL - Tracing normal messages such as success statements and
# sql queries.
# Level 4 - INFO - Tracing decision statements and loops.
# Level 5 - DEBUG - Tracing for debugging purposes.
#
# The messages in logfile are differentiated using NOTE, SUCCESS, ERROR and
# WARNING. For error messages and sql statements, timestamps are also recorded
# in the logfiles along with the message. For each message printed in the trace
# file i.e for levels INFO and DEBUG, the function name precedes the message.
########
sub asmcmdshare_trace
{
my($msg_level, $msg, $timestamp, $console ) = @_;
my($clsecho_write);
my($buf, $set_level);
# if clsecho utility does not exist or no execute permission bail out.
if (!-x $asmcmdglobal_clsecho)
{
return;
}
$set_level = $asmcmdshare_trace_levels{$asmcmdglobal_hash{'verbose'}};
# Nothing to do if the given msg_level is greater than set_level
if ($set_level < $msg_level)
{
return;
}
# Check if timestamp is printed along with the trace messages
if ($timestamp eq 'y')
{
$timestamp = '-t';
}
else
{
$timestamp = ' ';
}
# Print the trace messages
if ($console eq 'y')
{
asmcmdshare_printstderr("$msg\n");
}
$msg=~ s/\"/\"'\"'\"/g;
if($set_level <= 3)
{
if (defined $asmcmdglobal_hash{'isslave'})
{ # Each slave process writes on its own trace file
my $slave_id = $asmcmdglobal_hash{'slaveid'};
$clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
."$asmcmdglobal_trace_path/trace/asmcmd_slave_$$.trc \"$msg\"";
}
else
{
$clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
."$asmcmdglobal_trace_path/alert/alert.log \"$msg\"";
}
}
else
{
if (defined $asmcmdglobal_hash{'isslave'})
{ # Each slave process writes on its own trace file
my $slave_id = $asmcmdglobal_hash{'slaveid'};
$clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
."$asmcmdglobal_trace_path/trace/asmcmd_slave_$$.trc \"$msg\"";
}
else
{
$clsecho_write = "$asmcmdglobal_clsecho $timestamp -o "
."$asmcmdglobal_trace_path/trace/trace.trc \"$msg\"";
}
}
eval
{
if($asmcmdglobal_hash{'isdaemon'})
{
my $clsecho_stderr_file = $asmcmdglobal_hash{'tempdir'} .
"/clsecho_stderr_file.txt";
$buf = `$clsecho_write 2> $clsecho_stderr_file`;
# Open the file handle, if failed then raise an exception.
open (MYSTDERR, "<$clsecho_stderr_file") or
die "Could not open intermediate file $clsecho_stderr_file when " .
"in Connection pooled mode.";
my $string;
while($string = <MYSTDERR>)
{
asmcmdshare_printstderr($string);
}
close MYSTDERR;
unlink $clsecho_stderr_file;
}
else
{
$buf=`$clsecho_write`;
}
};
if ($@ ne '')
{
if($@ =~ "Could not open intermediate file")
{
$buf=`$clsecho_write`;
}
asmcmdshare_error_msg(8020, undef);
}
}
########
# NAME
# asmcmdshare_stack_trace
#
# DESCRIPTION
# This function prints the call stack with each funcion's arguments if verbose
# mode is lower than 'debug' this function does nothing.
#
# USAGE
#
# PARAMETERS
#
# RETURNS
# Null.
#
########
sub asmcmdshare_stack_trace
{
if ($asmcmdglobal_hash{'verbose'} eq 'debug')
{
my $stack;
$stack = Carp::longmess("The stack trace");
$stack =~ s/\t//g;
asmcmdshare_trace(5, "$stack", 'y', 'n');
}
}
########
# NAME
# asmcmdshare_trace_msg
#
# DESCRIPTION
# This function fetches message string from msg file (localized) and adds
# it to trace file using asmcmdshare_trace().
#
# PARAMETERS
# level (IN) - trace level
# err_num (IN) - error number.
#
# RETURNS
# Nothing
#
# NOTES: Only Trace level is passed as parameter, no replacement string(s).
# This only goes to trace file and NOT displayed to user, Timestamp
# added as default.
#
##########
sub asmcmdshare_trace_msg
{
my ($level, $err_num) = @_;
my ($clsecho_mesg);
my ($buf);
# if clsecho utility does not exist or no execute permission bail out.
if (!-x $asmcmdglobal_clsecho)
{
return;
}
$clsecho_mesg = "$asmcmdglobal_clsecho -p $asmcmdglobal_product -f "
."$asmcmdglobal_facility -m $err_num undefined";
if ($clsecho_mesg ne '')
{
eval
{
$buf = `$clsecho_mesg` ;
};
if ($@ ne '')
{
chomp $@;
asmcmdshare_print ("$@\n");
return ;
}
elsif (defined($buf) && ($buf ne ''))
{
asmcmdshare_trace ($level, $buf, 'y', 'n');
}
}
}
##############################################################################
############################## SQL Routines ##################################
########
# NAME
# asmcmdshare_cur_julian_date
#
# DESCRIPTION
# This routine constructs the SQL used to retrieve the current Julian Date.
# It calls asmcmdshare_do_select() to execute it.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
#
# RETURNS
# The current Julian Date as a number in string format.
########
sub asmcmdshare_cur_julian_date
{
my ($dbh) = shift;
my ($sth, $qry, $row);
my ($jul_date); # Return string for the current Julian Date: an integer. #
$qry = 'select to_char(current_date, \'J\') "JULIAN_DATE" from dual';
$sth = asmcmdshare_do_select($dbh, $qry);
$row = asmcmdshare_fetch($sth);
$jul_date = $row->{'JULIAN_DATE'};
asmcmdshare_finish($sth);
return $jul_date;
}
########
# NAME
# asmcmdshare_get_insttype
#
# DESCRIPTION
# This routine constructs the SQL used to get the instance type of the
# connected Oracle instance. It calls asmcmdshare_do_select() to execute it.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
#
# RETURNS
# The instance type as a string: either 'ASM' or 'RDBMS'.
########
sub asmcmdshare_get_insttype
{
my ($dbh) = shift;
my ($sth, $qry, $row);
my ($insttype); # Return string that holds the instance type. #
$qry = 'select value from v$parameter where name = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,'instance_type',SQL_VARCHAR);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$insttype = $row->{'VALUE'};
asmcmdshare_finish($sth);
return $insttype;
}
########
# NAME
# asmcmdshare_get_file
#
# DESCRIPTION
# This routine constructs the SQL used to retrieve all the attributes
# in v$asm_file for a file, given its group number and file number. It
# calls asmcmdshare_do_select() to execute it.
#
# 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.
# time_format (IN) - format of the timedate for file in question.
# file_info_ref (IN/OUT) - reference to hash of file information to be
# returned.
#
# RETURNS
# 1 if the file is valid
# 0 if the file was removed
#
# NOTES
# Not all hash keys in %file_info are attributes from v$asm_file. The keys
# MOD_TIME, MOD_DATE, JULIAN_DATE, and JULIAN_TIME are different time
# formats of the attribute 'modification_date'.
#
# When a file has been removed and we query using the compound_index, the
# modification_date will be NULL. We can use that column to see if the file
# is still valid.
########
sub asmcmdshare_get_file
{
my ($dbh, $gnum, $fnum, $time_format, $file_info_ref) = @_;
my $sth;
my $qry;
my $row;
my $cmpd_ind;
my $printError;
# If no time format has been specified, use the default (MON DD HH24:MI:SS)
if (!defined $time_format)
{
$time_format = 'MON DD HH24:MI:SS';
$printError = 0;
}
else
{
# In case the time format written by the user contains errors, enable
# error tracing.
$printError = 1;
}
$cmpd_ind = $gnum * (1 << 24) + $fnum;
$qry = 'select group_number, file_number, incarnation, block_size, ' .
'blocks, bytes, space, type, redundancy, striped, creation_date, ' .
'user_number, usergroup_number, permissions, ' .
'to_char(modification_date, ?) "MOD_TIME", ' .
'to_char(modification_date, \'MON DD YYYY\') "MOD_DATE", ' .
'to_char(modification_date, \'J HH24 MI SS\') "JULIAN_TIME", ' .
'to_char(modification_date, \'J\') "JULIAN_DATE" ' .
'from v$asm_file where compound_index = ? ORDER BY ' .
'GROUP_NUMBER, FILE_NUMBER, USER_NUMBER, USERGROUP_NUMBER '
;
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1, $time_format, SQL_VARCHAR);
$sth->bind_param(2, $cmpd_ind, SQL_INTEGER);
asmcmdshare_do_execute($sth, $printError);
$row = asmcmdshare_fetch($sth);
# Copy values into hash.
$file_info_ref->{'group_number'} = $row->{'GROUP_NUMBER'};
$file_info_ref->{'file_number'} = $row->{'FILE_NUMBER'};
$file_info_ref->{'incarnation'} = $row->{'INCARNATION'};
$file_info_ref->{'block_size'} = $row->{'BLOCK_SIZE'};
$file_info_ref->{'blocks'} = $row->{'BLOCKS'};
$file_info_ref->{'bytes'} = $row->{'BYTES'};
$file_info_ref->{'space'} = $row->{'SPACE'};
$file_info_ref->{'type'} = $row->{'TYPE'};
$file_info_ref->{'redundancy'} = $row->{'REDUNDANCY'};
$file_info_ref->{'striped'} = $row->{'STRIPED'};
$file_info_ref->{'creation_date'} = $row->{'CREATION_DATE'};
$file_info_ref->{'mod_time'} = $row->{'MOD_TIME'};
$file_info_ref->{'mod_date'} = $row->{'MOD_DATE'};
$file_info_ref->{'julian_time'} = $row->{'JULIAN_TIME'};
$file_info_ref->{'julian_date'} = $row->{'JULIAN_DATE'};
$file_info_ref->{'user_number'} = $row->{'USER_NUMBER'};
$file_info_ref->{'usergroup_number'} = $row->{'USERGROUP_NUMBER'};
if (defined($row->{'USER_NUMBER'}))
{
$file_info_ref->{'user'} = asmcmdshare_usernumber2name($dbh,
$row->{'USER_NUMBER'},
$row->{'GROUP_NUMBER'});
}
else
{
$file_info_ref->{'user'} = '';
}
if (defined($row->{'USERGROUP_NUMBER'}) && defined($row->{'GROUP_NUMBER'}))
{
$file_info_ref->{'group'} = asmcmdshare_usergroupnumber2name($dbh,
$row->{'USERGROUP_NUMBER'},
$row->{'GROUP_NUMBER'});
}
else
{
$file_info_ref->{'group'} = '';
}
if (defined($row->{'PERMISSIONS'}))
{
$file_info_ref->{'perm'} = $row->{'PERMISSIONS'};
}
else
{
$file_info_ref->{'perm'} = '';
}
# Combine date and time into one sortable number by removing the spaces.
if (defined($row->{'JULIAN_TIME'}))
{
$file_info_ref->{'julian_time'} =~ s,\s+,,g;
}
else
{
$file_info_ref->{'julian_time'} = '';
}
asmcmdshare_finish($sth);
# If the file was removed before calling this function, the
# modification_date will be NULL. We can use the julian_date column since it
# is derived from modification_date. Return 1 if it is valid, 0 if invalid.
return defined($file_info_ref->{'julian_date'});
}
########
# NAME
# asmcmdshare_get_subdirs
#
# DESCRIPTION
# This routine constructs the SQL used to select one or more rows from
# v$asm_alias.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# list_ref (IN/OUT) - reference to array of hashes, each representing
# a single row of results.
# time_format (IN) - format in which the datetime will be displayed.
# gnum (IN) - optional: limit select by this group number.
# ref_id (IN) - optional: limit select by this reference index.
# par_id (IN) - optional: limit select by this parent index.
# name (IN) - optional: limit select by this alias name.
# type (IN) - optional: limit select by this file type.
# sys_non_dir_only (IN) - boolean: limit select by 1) only system-created
# aliases and 2) only non-directories, iff this
# boolean is true.
# get_file_info (IN) - boolean: query v$asm_file iff true.
#
# RETURNS
# Undefined if the following requirement in NOTES is not met; otherwise
# an array of hashes, each containing the values of attributes for one
# row of v$asm_alias, returned by the query.
#
# NOTES
# You must specify at least one of $gnum or $name. Note also that we do
# not select every element in v$asm_alias.
########
sub asmcmdshare_get_subdirs
{
my ($dbh, $list_ref, $time_format, $gnum, $ref_id, $par_id, $name, $type,
$sys_non_dir_only, $get_file_info) = @_;
my $sth;
my $qry_alias;
my $row;
my $hash_key;
my %tmphash;
my @what;
my @from;
my @where;
my @order;
my @binds;
# Substitute all '*' with '%', as the latter is the wildcard character in
# SQL.
$name =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g if (defined ($name));
# Construct query against v$asm_alias.
push (@what, 'name');
push (@what, 'group_number');
push (@what, 'file_number');
push (@what, 'reference_index');
push (@what, 'parent_index');
push (@what, 'alias_directory');
push (@what, 'system_created');
push (@from, 'v$asm_alias');
# Add conditions to the SQL statement, based on which parameter is present.
# Note that either group number or the name is required.
if (defined ($gnum))
{
push (@where, 'group_number = ?');
push (@binds, [$gnum, SQL_INTEGER]);
}
if (defined ($name))
{
push (@where, 'upper(name) like ?');
push (@binds, [uc($name), SQL_VARCHAR]);
}
if (defined ($ref_id))
{
push (@where, 'reference_index = ?');
push (@binds, [$ref_id, SQL_INTEGER]);
}
if (defined ($par_id))
{
push (@where, 'parent_index = ?');
push (@binds, [$par_id, SQL_INTEGER]);
}
if ($sys_non_dir_only)
{ # We want only system-created aliases to only files here. #
push (@where, "alias_directory = 'N'");
push (@where, "system_created = 'Y'");
}
$sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where,
\@order, \@binds);
# Fetch results row by row and store each row in %entry_info which will be
# stored in a temporary hash.
while (defined ($row = asmcmdshare_fetch($sth)))
{
my (%entry_info); # Allocate fresh hash for next row. #
# v$asm_alias entries
# If any field is undefined, the file might have been removed so skip it.
$entry_info{'group_number'} = $row->{'GROUP_NUMBER'};
next if !defined($entry_info{'group_number'});
$entry_info{'file_number'} = $row->{'FILE_NUMBER'};
next if !defined($entry_info{'file_number'});
$entry_info{'reference_index'} = $row->{'REFERENCE_INDEX'};
next if !defined($entry_info{'reference_index'});
$entry_info{'parent_index'} = $row->{'PARENT_INDEX'};
next if !defined($entry_info{'parent_index'});
$entry_info{'alias_directory'} = $row->{'ALIAS_DIRECTORY'};
next if !defined($entry_info{'alias_directory'});
$entry_info{'system_created'} = $row->{'SYSTEM_CREATED'};
next if !defined($entry_info{'system_created'});
$entry_info{'name'} = $row->{'NAME'};
next if !defined($entry_info{'name'});
# Append to v$asm_file query
if ($get_file_info && $entry_info{'alias_directory'} eq 'N')
{
# Hash key consists of group_number, file_number, and
# the system_created flag.
$hash_key = $entry_info{'group_number'} * (1 << 24) +
$entry_info{'file_number'} .
$entry_info{'system_created'};
# Save the index for the current element in @$list_ref
$tmphash{$hash_key} = scalar @$list_ref;
}
push (@$list_ref, \%entry_info);
}
asmcmdshare_finish($sth);
# Here we query v$asm_file once for each file. This method may seem
# excessive, but it is actually the fastest way. There are two
# alternatives:
# A) Build a single query to query all files.
# B) Join v$asm_file against a filtered v$asm_alias after a subquery.
#
# A) is not good because such a query can be too long and may require
# a lot of memory on the server side.
# B) is not good because it runs in O(N) time if querying a single
# file entry, versus O(1) time as implemented here.
#
# The file could have been removed by another process. If so, undefine the
# whole row to filter out the invalid files.
foreach (keys (%tmphash))
{
my $i = $tmphash{$_};
my $valid = asmcmdshare_get_file($dbh, $$list_ref[$i]->{'group_number'},
$$list_ref[$i]->{'file_number'},
$time_format, $$list_ref[$i]);
undef $$list_ref[$i] if !$valid;
}
# Filter out all undefined rows
@$list_ref = grep{ defined }@$list_ref;
###############################
return;
}
########
# NAME
# asmcmdshare_get_subdir_simple
#
# DESCRIPTION
# This routine constructs the SQL used to fetch the reference index for the
# alias that has the parent index $par_id, the name $name, and the group
# number $asmcmdglobal_hash{'gnum'}.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# par_id (IN) - limit select by this parent index.
# name (IN) - limit select by this alias name.
#
# RETURNS
# The reference index as a string.
########
sub asmcmdshare_get_subdir_simple
{
my ($dbh, $par_id, $name) = @_;
my ($sth, $qry, $row);
my ($ref_id); # The return string for the reference index. #
$qry = 'select reference_index from v$asm_alias where group_number = ?' .
' and parent_index = ?' .
' and upper(name) = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$asmcmdglobal_hash{'gnum'});
$sth->bind_param(2,$par_id);
$sth->bind_param(3,uc($name),SQL_VARCHAR);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$ref_id = $row->{'REFERENCE_INDEX'};
asmcmdshare_finish($sth);
return $ref_id;
}
########
# NAME
# asmcmdshare_run_find_sql
#
# DESCRIPTION
# This routine constructs and executes SQL to find all the aliases
# in v$asm_alias that match the pattern given by $name. $par_id
# and $type can further limit the results.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# list_ref (IN/OUT) - reference to array of hashes, each representing
# a single row of results.
# par_id (IN) - optional: limit select by this parent index.
# name (IN) - limit select by this alias name.
# type (IN) - optional: limit select by this file type.
# sys_non_dir_only (IN) - boolean: limit select by 1) only system-created
# aliases and 2) only non-directories, iff this
# boolean is true.
#
# NOTES
# asmcmdshare_find_int() uses this SQL routine, which optimizes for
# searches, while asmcmdshare_get_subdirs() optimizes for ls.
########
sub asmcmdshare_run_find_sql
{
my ($dbh, $list_ref, $par_id, $name, $type, $sys_non_dir_only) = @_;
my ($sth, $qry, $qry2, $row, $cmpd_ind, $entry);
my (@tmparray);
# Set wildcard as '%' and contruct v$asm_alias query.
$name =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g if (defined ($name));
$qry = 'select name,
group_number,
file_number,
reference_index,
parent_index,
alias_directory,
system_created
from v$asm_alias
where upper(name) like ?';
if (defined ($type))
{
$qry .= " and alias_directory = 'N'";
}
if (defined ($par_id))
{
$qry .= ' and parent_index = ?';
}
if ($sys_non_dir_only)
{ # We want only system-created aliases to only files here. #
$qry .= " and alias_directory = 'N' and system_created = 'Y'";
}
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,uc($name),SQL_VARCHAR);
if (defined ($par_id))
{
$sth->bind_param(2,$par_id,SQL_INTEGER);
}
asmcmdshare_do_execute($sth);
while (defined ($row = asmcmdshare_fetch($sth)))
{
my (%entry_info); # Allocate fresh hash for next row. #
# v$asm_alias entries
$entry_info{'group_number'} = $row->{'GROUP_NUMBER'};
$entry_info{'file_number'} = $row->{'FILE_NUMBER'};
$entry_info{'reference_index'} = $row->{'REFERENCE_INDEX'};
$entry_info{'parent_index'} = $row->{'PARENT_INDEX'};
$entry_info{'alias_directory'} = $row->{'ALIAS_DIRECTORY'};
$entry_info{'system_created'} = $row->{'SYSTEM_CREATED'};
$entry_info{'name'} = $row->{'NAME'};
push (@tmparray, \%entry_info);
}
asmcmdshare_finish($sth);
foreach $entry (@tmparray)
{
# If we're qualifying by file type, then we need to query v$asm_file.
if (defined ($type))
{
$cmpd_ind = $entry->{'group_number'} * (1 << 24) +
$entry->{'file_number'};
$qry2 = 'select compound_index from v$asm_file where
compound_index = ? and upper(type) = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry2);
$sth->bind_param(1,$cmpd_ind);
$sth->bind_param(2,uc($type),SQL_VARCHAR);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
# Add entry to results only if there is a match.
if (defined ($row))
{
push (@{$list_ref}, $entry);
}
asmcmdshare_finish($sth);
}
else # If we're not searching by type, then always return the entry #
{
push (@{$list_ref}, $entry);
}
}
}
########
# NAME
# asmcmdshare_get_par_id
#
# DESCRIPTION
# This routine constructs the SQL used to fetch the parent index of the
# alias that has the group number $asmcmdglobal_hash{'gnum'} and
# reference_index $ref_id.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# ref_id (IN) - limit select by this reference index.
#
# RETURNS
# The parent index as a string.
#
# NOTES
# The reference index specified by $ref_id must be unique; it must not be
# the reference index of a file alias, as those are not unique.
########
sub asmcmdshare_get_par_id
{
my ($dbh, $ref_id) = @_;
my ($sth, $qry, $row);
my ($par_id); # The return string holding the parent index. #
$qry = 'select parent_index from v$asm_alias where group_number=?' .
' and reference_index=?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$asmcmdglobal_hash{'gnum'});
$sth->bind_param(2,$ref_id);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$par_id = $row->{'PARENT_INDEX'};
asmcmdshare_finish($sth);
return $par_id;
}
########
# NAME
# asmcmdshare_get_dg
#
# DESCRIPTION
# This routine constructs the SQL used to fetch a list of row(s) from
# v$asm_diskgroup.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# gname (IN) - optional: limit select by this group number, can contain
# the wildcard '*'.
#
# RETURNS
# An array containing zero or one or more rows from v$asm_diskgroup. The
# values of attributes for each row are stored in a hash, the reference to
# which is indexed in the array.
########
sub asmcmdshare_get_dg
{
my ($dbh, $gname, $discovery, $global) = @_;
my ($sth, $qry, $row);
my (@dg_list); # The return array of hashes; see RETURNS above. #
my ($view); # The ASM view to querry. #
# If Oracle Database version is less than 10gR2, then always do
# discovery, because the *_stat views are not available in 10gR1.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) < 0 )
{
$discovery = 1;
}
if ($discovery && $global)
{
$view = 'gv$asm_diskgroup';
}
elsif ($discovery && !$global)
{
$view = 'v$asm_diskgroup';
}
elsif (!$discovery && $global)
{
$view = 'gv$asm_diskgroup_stat';
}
else
{
$view = 'v$asm_diskgroup_stat';
}
$qry = 'select * from ' . $view;
# Narrow select if $gname is specified.
if (defined ($gname))
{
$gname =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g;
$qry = $qry . ' where name like ?';
}
$sth = asmcmdshare_do_prepare($dbh, $qry);
if (defined ($gname))
{
$sth->bind_param(1,uc($gname),SQL_VARCHAR);
}
asmcmdshare_do_execute($sth);
# Fetch results row by row and store each row in %dg_info, and reference
# each %dg_info in @dg_list.
while (defined ($row = asmcmdshare_fetch($sth)))
{
my (%dg_info); # Allocate fresh hash for next row. #
$dg_info{'inst_id'} = $row->{'INST_ID'};
$dg_info{'group_number'} = $row->{'GROUP_NUMBER'};
$dg_info{'name'} = $row->{'NAME'};
$dg_info{'path'} = $row->{'NAME'}; # For a diskgroup, path is equal to
# name. This is needed so that
# asmcmdbase_name_forward doesn't error
# out saying that path is
# uninitialized.
$dg_info{'sector_size'} = $row->{'SECTOR_SIZE'};
$dg_info{'logical_sector_size'} = $row->{'LOGICAL_SECTOR_SIZE'};
$dg_info{'block_size'} = $row->{'BLOCK_SIZE'};
$dg_info{'allocation_unit_size'} = $row->{'ALLOCATION_UNIT_SIZE'};
$dg_info{'state'} = $row->{'STATE'};
$dg_info{'type'} = $row->{'TYPE'};
$dg_info{'total_mb'} = $row->{'TOTAL_MB'};
$dg_info{'free_mb'} = $row->{'FREE_MB'};
# This attribute is available only in 10gR2 and after.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) >= 0 )
{
$dg_info{'required_mirror_free_mb'} = $row->{'REQUIRED_MIRROR_FREE_MB'};
$dg_info{'usable_file_mb'} = $row->{'USABLE_FILE_MB'};
$dg_info{'offline_disks'} = $row->{'OFFLINE_DISKS'};
}
# This attribute is available only in 11gR2 and after.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_11gR2) >= 0 )
{
$dg_info{'voting_files'} = $row->{'VOTING_FILES'};
}
# It is possible that type is undefined if diskgroup is unmounted. In
# that case we assign it an empty still so that we won't run into problems
# of trying to concatenate undefined strings later.
$dg_info{'type'} = '' unless (defined ($dg_info{'type'}));
push (@dg_list, \%dg_info);
}
asmcmdshare_finish($sth);
return (@dg_list);
}
########
# NAME
# asmcmdshare_get_redund
#
# DESCRIPTION
# This routine constructs the SQL used to the redundancy information for
# diskgroup $gnum (type attribute in v$asm_diskgroup).
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# gnum (IN) - group number for the diskgroup in question.
#
# RETURNS
# TYPE RETURNS
# --------- --------------------
# DEFAULT ASMCMDGLOBAL_DEFAULT
# EXTERNAL ASMCMDGLOBAL_EXTERNAL
# NORMAL ASMCMDGLOBAL_NORMAL
# HIGH ASMCMDGLOBAL_HIGH
# FLEX ASMCMDGLOBAL_FLEX
# EXTENDED ASMCMDGLOBAL_EXTENDED
#
########
sub asmcmdshare_get_redund
{
my ($dbh, $gnum) = @_;
my ($sth, $qry, $row);
my ($redund); # Return string for redundancy; see RETURNS above. #
my ($view); # The view to query. #
# If Oracle Database version is less than 10gR2, then always do
# discovery, because the *_stat views are not available in 10gR1.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) < 0 )
{
$view = 'v$asm_diskgroup';
}
else
{
$view = 'v$asm_diskgroup_stat';
}
# Get diskgroup redundancy from group number.
$qry = 'select type from ' . $view . ' where group_number=?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$gnum);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$redund = $row->{'TYPE'};
asmcmdshare_finish($sth);
return $ASMCMDGLOBAL_EXTERNAL if ($redund eq 'EXTERN');
return $ASMCMDGLOBAL_NORMAL if ($redund eq 'NORMAL');
return $ASMCMDGLOBAL_HIGH if ($redund eq 'HIGH');
return $ASMCMDGLOBAL_FLEX if ($redund eq 'FLEX');
return $ASMCMDGLOBAL_EXTENDED if ($redund eq 'EXTEND');
return $ASMCMDGLOBAL_DEFAULT;
}
########
# NAME
# asmcmdshare_get_gnum_from_gname
#
# DESCRIPTION
# This routine constructs the SQL used to fetch the group number of the
# diskgroup that has the name $gname, iff it is mounted.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# gname (IN) - the name for the diskgroup for which we need the group
# number.
#
# RETURNS
# The group number as a string if the diskgroup is mounted; undefined
# otherwise.
########
sub asmcmdshare_get_gnum_from_gname
{
my ($dbh, $gname) = @_;
my ($sth, $qry, $row);
my ($gnum); # Group number return value; see RETURNS above. #
my ($state); # The state attribute from v$asm_diskgroup. #
my ($view); # The view to query. #
# If Oracle Database version is less than 10gR2, then always do
# discovery, because the *_stat views are not available in 10gR1.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) < 0 )
{
$view = 'v$asm_diskgroup';
}
else
{
$view = 'v$asm_diskgroup_stat';
}
# Get diskgroup number from group name.
$qry = 'select group_number, state from ' . $view . ' where name = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,uc($gname),SQL_VARCHAR);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$gnum = $row->{'GROUP_NUMBER'};
$state = $row->{'STATE'};
asmcmdshare_finish($sth);
# Return undefined if dismounted, so that caller knows not to 'cd' to this
# diskgroup.
return undef if (defined ($state) && ($state eq 'DISMOUNTED'));
return $gnum;
}
########
# NAME
# asmcmdshare_get_gname_from_gnum
#
# DESCRIPTION
# This routine constructs the SQL used to fetch the name of the diskgroup
# that has the group number $gnum.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# gnum (IN) - the group number for the diskgroup for which we need the
# name.
#
# RETURNS
# The diskgroup name as a string.
########
sub asmcmdshare_get_gname_from_gnum
{
my ($dbh, $gnum) = @_;
my ($sth, $qry, $row);
my ($gname); # Group name return value; see RETURNS above. #
my ($view); # The view to query. #
# If Oracle Database version is less than 10gR2, then always do
# discovery, because the *_stat views are not available in 10gR1.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) < 0 )
{
$view = 'v$asm_diskgroup';
}
else
{
$view = 'v$asm_diskgroup_stat';
}
# Get diskgroup name from group number.
$qry = 'select name from ' . $view . ' where group_number=?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$gnum);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$gname = $row->{'NAME'};
asmcmdshare_finish($sth);
return $gname;
}
########
# NAME
# asmcmdshare_filegroup_exists
#
# DESCRIPTION
# Checks if the given file group name exists.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# fgname (IN) - the name for the file group for which we need to confirm
# its existence
#
# RETURNS
# 1 if the file group exists; undefined otherwise.
########
sub asmcmdshare_filegroup_exists
{
my ($dbh) = shift;
my ($fgname) = shift;
my ($rc) = undef;
my ($sth, $qry, $row);
# Get file group number from file group name.
$fgname = uc $fgname;
$qry = 'select filegroup_number from v$asm_filegroup ' .
"where name = '$fgname'";
# Execute the SQL.
$sth = asmcmdshare_do_select($dbh, $qry);
# Fetch the only row of results.
$row = asmcmdshare_fetch($sth);
# Close the statement handle.
asmcmdshare_finish($sth);
$rc = 1 if (defined($row));
return $rc;
}
########
# NAME
# asmcmdshare_xml_exists
#
# DESCRIPTION
# This routine checks if the given XML file name exists and is readable
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# dgnum (IN) - disk group number.
# ugname(IN) - user group name.
#
# RETURNS
# 1 if the files exists and is readable. undef otherwise with the proper
# error number signaled.
########
sub asmcmdshare_xml_exists
{
my ($file) = shift;
my (@eargs) = ();
my ($err);
if (-e $file)
{
return 1 if (-r $file);
# ASMCMD-8611 "cannot read '%s', permission denied."
$err = 8611;
}
else
{
# ASMCMD-8610: "cannot access '%s', no such XML file."
$err = 8610;
}
push @eargs, $file;
asmcmdshare_error_msg($err, \@eargs);
return undef;
}
########
# NAME
# asmcmdshare_get_ugnum_from_ugname
#
# DESCRIPTION
# This routine gets the usergroup number from the usergroup name, diskgroup
# number
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# dgnum (IN) - disk group number.
# ugname(IN) - user group name.
#
# RETURNS
# The usergroup number.
########
sub asmcmdshare_get_ugnum_from_ugname
{
my ($dbh, $dgnum, $ugname) = @_;
my ($sth, $qry, $row);
my ($ugnum); # Group name return value; see RETURNS above. #
# Get usergroup name from usergroup number.
$qry = 'select usergroup_number from v$asm_usergroup' .
' where group_number = ?' .
' and name = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$dgnum);
$sth->bind_param(2,$ugname,SQL_VARCHAR);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$ugnum = $row->{'USERGROUP_NUMBER'};
asmcmdshare_finish($sth);
return $ugnum;
}
########
# NAME
# asmcmdshare_do_construct_select
#
# DESCRIPTION
# This routine executes select SQL queries specified by $qry.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# what (IN) - array of columns to select
# from (IN) - array of tables to select from
# where (IN) - array of conditions
# order (IN) - array of order by
# bind (IN) - array of bind variables
#
# RETURNS
# The initialized SQL statement handle.
#
# NOTES
# This routine constructs the statement and calls asmcmdshare_do_execute
########
sub asmcmdshare_do_construct_select
{
my ($dbh, $what, $from, $where, $order, $bind) = @_;
my ($stmt);
my ($sth);
my ($numarry);
my ($id);
my (@eargs);
if (!@$what || !@$from)
{
asmcmdshare_error_msg(8100, undef);
}
$stmt = "SELECT " . join(', ', @$what);
$stmt .= " FROM " . join(', ', @$from);
if (defined($where) && @$where)
{
$stmt .= " WHERE " . join(' AND ', @$where);
}
if (defined($order) && @$order)
{
$stmt .= " ORDER BY " . join(', ', @$order);
}
# For diagnostic
asmcmdshare_trace(5, "asmcmdshare_do_construct_select: $stmt",'y', 'n');
$sth = asmcmdshare_do_prepare($dbh, $stmt);
# Calls bind_param based on the array bind
if (defined($bind) && @{$bind})
{
# get number of indexes
$numarry = scalar(@{$bind});
for ($id = 0; $id < $numarry; $id++)
{
# For diagnostic
asmcmdshare_trace(5, "asmcmdshare_do_construct_select: idx=$id" .
" val=$bind->[$id][0] type=$bind->[$id][1]", 'y', 'n');
# Bind the variables based on the data type
$sth->bind_param($id+1, $bind->[$id][0], $bind->[$id][1]);
}
}
asmcmdshare_do_execute($sth);
return ($sth);
}
########
# NAME
# asmcmdshare_check_db_alive
#
# DESCRIPTION
# This routine tries to prepare a sql statement handle using the dbh.
# If it fails, the DB Handle is stale and invalid. The ASM
# instance has gone down in the meantime
#
# PARAMETERS
# dbh (IN) - initialized database handle, MUST BE non-null.
#
# RETURNS
# 0 - If the preparation of statement failed, indicating stale DB Handle
# 1 - If the attempt was successful. DB Handle is valid.
#
########
sub asmcmdshare_check_dbh_alive
{
my ($dbh) = @_;
my ($sth, $qry);
$qry = 'select version from v$instance';
$qry = '/* ASMCMD */ ' . $qry; # Add ASMCMD comment. #
$sth = $dbh->prepare($qry);
if (!defined ($sth))
{
return 0;
}
return 1;
}
########
# NAME
# asmcmdshare_do_select
#
# DESCRIPTION
# This routine executes select SQL queries specified by $qry.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# qry (IN) - the SQL query to be executed.
#
# RETURNS
# The initialized SQL statement handle.
#
########
sub asmcmdshare_do_select
{
my ($dbh, $qry) = @_;
my ($sth, $rv);
$qry = '/* ASMCMD */ ' . $qry; # Add ASMCMD comment. #
$sth = $dbh->prepare($qry);
asmcmdshare_do_execute($sth);
return $sth;
}
########
# NAME
# asmcmdshare_fetch
#
# DESCRIPTION
# This routine routine returns one row of results from an already executed
# SQL query.
#
# PARAMETERS
# sth (IN) - initialized SQL statement handle.
#
# RETURNS
# A hash with attribute values as its values and attribute names as its
# keys, for one row of results; undefined if $sth is undefined.
########
sub asmcmdshare_fetch
{
my $sth = shift;
return undef unless(defined $sth);
return $sth->fetchrow_hashref;
}
########
# NAME
# asmcmdshare_finish
#
# DESCRIPTION
# This routine closes the SQL statement handle $sth.
#
# PARAMETERS
# sth (IN) - initialized SQL statement handle.
#
# RETURNS
# Undefined if $sth is undefined; otherwise returns the return value of
# $sth->finish().
########
sub asmcmdshare_finish
{
my $sth = shift;
return undef unless(defined $sth);
return $sth->finish;
}
########
# NAME
# asmcmdshare_do_stmt
#
# DESCRIPTION
# This routine executes non-select SQL statements.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# stmt (IN) - SQL statement string to query.
# trim (IN) - OPTIONAL: trim DBD ERROR and statement info. contains password
# from trace (default: 0).
#
# RETURNS
# The return value of $sth->do($stmt).
#
# NOTES
# It will raise an asmcmd exception if the statement could not be executed
# successfully.
# It will either execute the statement successfully and return the statement
# handle, OR raise an exception which ought to be caught and handled
# in asmcmdcore_main()
#
########
sub asmcmdshare_do_stmt
{
my ($dbh, $stmt) = @_;
my ($trim) = defined $_[2] ? $_[2] : 0;
my ($comment) = '/* ASMCMD */';
my ($sth);
if (!defined ($dbh))
{
asmcmdshare_signal_exception(8200, undef);
}
# 20672249: avoid tracing statement info. that contains password.
# $trim would be set to 1 for three commands: "passwd", "orapwusr -add" and
# "orapwusr -modify".
if ($trim == 0)
{
asmcmdshare_trace(3, "$comment . $stmt", 'y', 'n');
}
$sth = $dbh->do($comment . $stmt);
if (!defined ($sth))
{
# trace the error
my $err = "$DBI::errstr";
if ($trim)
{
$err =~ s/\(DBD ERROR: .*//g;
}
asmcmdshare_trace(1, "$err", 'y', 'y');
# throw this exception up to catch and handle
asmcmdexceptions::throw("asmcmdexceptions");
}
# Return the statement handle if it is not null,
# ie. if the statement was executed successfully.
return $sth;
}
########
# NAME
# asmcmdshare_do_prepare
#
# DESCRIPTION
# This routine prepares select SQL queries.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# qry (IN) - the SQL query to be prepared.
#
# RETURNS
# The prepared SQL statement handle.
#
########
sub asmcmdshare_do_prepare
{
my ($dbh, $qry) = @_;
my ($sth, $rv);
$qry = '/* ASMCMD */ ' . $qry; # Add ASMCMD comment. #
$sth = $dbh->prepare($qry);
asmcmdshare_do_validate_sth($sth);
return $sth;
}
########
# NAME
# asmcmdshare_do_validate_sth
#
# DESCRIPTION
# This routine validates the SQL handle.
#
# PARAMETERS
# sth (IN) - The SQL statement handle.
#
# RETURNS
# The SQL statement handle.
#
# NOTES
# This routine call can hit two errors:
# 1) 8100: bad statement handle, probably bad SQL syntax in $qry.
# 2) 8200: lost connection to ASM instance or foreground died.
# It will raise an asmcmd exception if the statement could not be executed
# successfully.
# It will raise an asmcmd exception if the statement could not be executed
# successfully.
# It will either execute the statement successfully and return the statement
# handle, OR raise an exception which ought to be caught and handled
# in asmcmdcore_main()
#
########
sub asmcmdshare_do_validate_sth
{
my ($sth) = @_;
if (!defined ($sth))
{ # Null $sth, must be an error. #
if ($DBI::errstr =~ /ORA-03113/)
{ # ORA-03113 means connection lost of foreground died, signal #
# exception and exit. #
asmcmdshare_signal_exception (8200, undef);
}
else
{ # Connect not lost but probably SQL syntax error caused this. #
asmcmdshare_error_msg(8100, undef);
asmcmdshare_trace(5, "asmcmdshare_do_prepare(): $DBI::errstr", 'y', 'n');
}
# Throw this exception up to catch and handle, (it is a syntax error).
asmcmdexceptions::throw("asmcmdexceptions");
}
return $sth;
}
########
# NAME
# asmcmdshare_do_execute
#
# DESCRIPTION
# This routine executes select SQL queries previously
# prepared.
#
# PARAMETERS
# sth (IN) - The prepared SQL statement handle.
# trim (IN) - OPTIONAL: trim DBD ERROR and statement info. Used only for 'ls'
#
# RETURNS
# The initialized SQL statement handle.
# Raises an asmcmd exception if the statement could not be executed
# successfully.
#
########
sub asmcmdshare_do_execute
{
my ($sth) = @_;
my $trim = defined $_[1] ? $_[1] : 0;
my $rv;
asmcmdshare_do_validate_sth($sth);
$asmcmdglobal_hash{'sth'} = $sth;
$rv = $sth->execute;
$asmcmdglobal_hash{'sth'} = undef;
if (!defined ($rv))
{
my $err = "$DBI::errstr";
if ($trim)
{
$err =~ s/\(DBD ERROR: .*//g;
asmcmdshare_trace(1, $err, 'y', 'y');
}
elsif (!defined ($sth))
{
asmcmdshare_trace(1, $err, 'y', 'y');
}
# throw this exception up to catch and handle
asmcmdexceptions::throw("asmcmdexceptions");
}
return $sth;
}
##############################################################################
########
# NAME
# asmcmdshare_ziplists()
#
# DESCRIPTION
# Function to combine two lists of the same length into one in the same way
# the Python built-in zip() function does.
#
# Let A = (a0, a1, ..., an) and B = (b0, b1, ..., bn) two arrays of the same
# lenght this routine returns a new list Z = (a0, b0, a1, b1, ..., an, bn).
#
# PARAMETERS
# A (IN) - array
# B (IN) - array
#
# RETURNS
# The generated zipped array.
########
sub asmcmdshare_ziplists
{
my @args = @_; # @args list contains all the elements of A followed by
# all the elements of B
my @eargs = ("asmcmdshare_ziplists01");
my $middle;
my @zipped;
asmcmdshare_assert(@args % 2 == 0, \@eargs);
$middle = @args / 2;
@zipped = @args[ map {$_, $_ + $middle} (0 .. ($middle - 1)) ];
return @zipped;
}
########
# NAME
# asmcmdshare_humanBytes()
#
# DESCRIPTION
# This rutine provides a human readable string that represents the given
# number interpreted as a size in bytes.
#
# PARAMETERS
# bytes (IN) - The number of bytes to be represented as a string
#
########
sub asmcmdshare_humanBytes
{
my $bytes = shift;
my $hBytes;
my @keys;
my %table;
my $k;
my $v;
@keys = ('1099511627776', '1073741824', '1048576', '1024', '1');
%table = asmcmdshare_ziplists(@keys, ('T', 'G', 'M', 'K', ''));
foreach (@keys)
{
$k = $_;
$v = $table{$k};
last if ($bytes >= $k);
}
if ($k > 1)
{
$bytes /= $k;
$hBytes = sprintf("%.1f%s", $bytes, $v);
$hBytes =~ s/\.0+//;
}
else
{
$hBytes = "$bytes";
}
return $hBytes;
}
########
# NAME
# asmcmdshare_getpswd()
#
# DESCRIPTION
# This routine prompts the user for a password when the user uses a connect
# string with the -c option but does not specify the password as part of
# the connect string.
#
# PARAMETERS
# None.
#
# RETURNS
# The user entered password in a string.
#
# NOTES
########
sub asmcmdshare_getpswd
{
my ($msg) = shift;
my ($pswd) = '';
my ($chrgot);
my ($maxstringput) = $ASMCMDSHARE_MAXPASSWD;
$| = 1;
$msg = 'Enter password: ' if (!defined($msg));
print $msg;
$maxstringput = $ASMCMDSHARE_MAXPASSWD ;
while ($maxstringput--)
{
ReadMode("raw");
$chrgot = ReadKey(0);
ReadMode("restore");
# Accept All alpha-numeric, printable characters.
# Stop - LineFeed/CR - stop accepting character
if (ord($chrgot) == 13 || ord($chrgot) == 10)
{
last;
}
else
{
## No standard way to detect BACKSPACE pressed in LINUX/WINDOWS
## checks with '\b' does not work on LINUX, works good on Windows
## Also with -w it gives more warnings.
## Using ORD to keep it consistent.
## ORD-127 is for DEL, and that cannot be supported as we do not support
## <- & -> keys.
##
if (ord($chrgot) == 8 ) # BACKSPACE pressed
{
if (length($pswd) > 0)
{
$pswd = substr $pswd, 0, (length ($pswd) - 1);
print "\b \b";
## BACKSPACE pressed, one char removed. We can add one more.
$maxstringput = $maxstringput + 2;
}
}
elsif ($pswd eq '') # first char
{
$pswd = $chrgot;
print "*" ;
}
else
{
$pswd = $pswd . $chrgot;
print "*";
}
}
}
print "\n";
# 20721939: no need to validate password in asmcmd, passwords are quoted and
# sqlplus is supposed to handle the validation and asmcmd is just a wrapper
return $pswd;
}
########
# NAME
# asmcmdshare_usergroupnumber2name
# DESCRIPTION
# This routine gets a usergroup number and returns its name.
# PARAMETERS
# usergroup number, diskgroup_number
# RETURNS
# The usergroup name.
# NOTES
########
sub asmcmdshare_usergroupnumber2name
{
my ($dbh, $usergroup_number, $dg_number) = @_;
my ($usergroup_name);
my ($sth, $qry, $row);
$qry = 'select name from v$asm_usergroup' .
' where usergroup_number = ?' .
' and group_number = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$usergroup_number,SQL_INTEGER);
$sth->bind_param(2,$dg_number,SQL_INTEGER);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
#bug 7420875 corrected column to fetch user_group
$usergroup_name = $row->{'NAME'};
asmcmdshare_finish($sth);
$usergroup_name = '' if (!defined($usergroup_name));
return $usergroup_name;
}
########
# NAME
# asmcmdshare_usernumber2name
# DESCRIPTION
# This routine gets a user number, group number and returns its name.
# PARAMETERS
# user number
# group number
# RETURNS
# The user name.
# NOTES
########
sub asmcmdshare_usernumber2name
{
my ($dbh, $user_number, $group_number) = @_;
my ($user_name) = '';
my ($sth, $qry, $row);
$qry = 'select os_name, cluster_id from v$asm_user' .
' where user_number = ? and group_number = ?';
$sth = asmcmdshare_do_prepare($dbh, $qry);
$sth->bind_param(1,$user_number,SQL_INTEGER);
$sth->bind_param(2,$group_number,SQL_INTEGER);
asmcmdshare_do_execute($sth);
$row = asmcmdshare_fetch($sth);
$user_name = $row->{'OS_NAME'};
if (defined($user_name))
{
$user_name .= '@'.$row->{'CLUSTER_ID'} if (defined($row->{'CLUSTER_ID'}));
}
else
{
$user_name = '';
}
asmcmdshare_finish($sth);
return $user_name;
}
########
# NAME
# asmcmdshare_print_cmds
# DESCRIPTION
# This routine prints a list of commands in a formatted way for help.
# PARAMETERS
# An array with the commands to print.
# RETURNS
# A string with the formatted commands.
# NOTES
########
sub asmcmdshare_print_cmds
{
my (@cmds) = @_;
my ($cmd, $t);
my (@line);
my ($str) = '';
my ($header) = ' ';
foreach $cmd(@cmds)
{
push (@line, $cmd);
$t = $header . join(', ', @line);
if (length ($t) >= 60)
{
@line = ();
$str = $str . $t . "\n";
}
}
$str = $str . $t . "\n" if (@line);
return ($str);
}
########
# NAME
# asmcmdshare_check_option_consistency
# DESCRIPTION
#
# PARAMETERS
# cmd(IN) - current command being processed
# $args_ref(IN) - reference to GetOptions result hash
#
# RETURNS
# None
#
# NOTES
########
sub asmcmdshare_check_option_consistency
{
my(%module_cmds) = @_;
my ($opt);
my ($cmd);
my $return_val = 1;
######################################################
# If option not present in global hash
# - Add it to global hash
# Else if duplicate (k,V) pair
# - ignore and move to next option
# Else if the value is differnt for same options (key with diff values)
# - Error out and exit(check Failed)
#######################################################
foreach $cmd(sort(keys %module_cmds))
{
foreach $opt (sort(keys %{$module_cmds{$cmd}{flags}}))
{
my $k = $opt;
#remove the '=' in the options which take values
$opt =~ s/=.//;
if($asmcmdglobal_options{$opt})
{
#handle duplicate options,error out if inconsistent
if($asmcmdglobal_options{$opt} ne $module_cmds{ $cmd }{ flags}{$k})
{
asmcmdshare_trace(3, "Option '$opt' inconsistent while processing "
."command:$cmd, correct the same to continue\n", 'n', 'y');
$return_val=0;
goto done;
}
next;
}
else
{
#if not already present add the current option to global options Hash
$asmcmdglobal_options{$opt} = $module_cmds{ $cmd }{ flags}{$k};
}
}
}
done:
return $return_val;
}
########
# NAME
# asmcmdshare_handle_deprecation
# DESCRIPTION
# This function checks whether the options for a command is deprecated
# If yes then print out a warning and set the new option for processing.
#
# PARAMETERS
# cmd(IN) - current command being processed
# $args_ref(IN) - reference to GetOptions result hash
#
# RETURNS
# None
#
# NOTES
# This function is called only for commands which have deprecated
# options ie. find,ls,lsdsk,lsdg,md_restore,md_backup
########
sub asmcmdshare_handle_deprecation
{
my ($cmd,$args_ref) = @_;
my $iter;
my @string;
my (%args_depr);
my $depr_opt = \%{$asmcmdglobal_deprecated_options{$cmd}};
my @common_keys = grep { exists $depr_opt->{$_} } keys( %{ $args_ref } );
#If there are deprecated options used.
if($#common_keys >= 0)
{
foreach $_(@common_keys)
{
#Fetch the new option for current deprecated option
my $option = $asmcmdglobal_deprecated_options{$cmd}{$_}[1];
# Set the new option if corresponding deprecated option was set
# Special checks for 'lsdsk' and 'md_restore' since options expand
if($cmd eq 'lsdsk' && $_ eq 'm')
{
$$args_ref{'member'} = '1' if($$args_ref{$_} eq 'm');
$$args_ref{'candidate'} ='1' if($$args_ref{$_} eq 'c');
}
elsif($cmd eq 'md_restore' && $_ eq 't')
{
$$args_ref{'full'} ='1' if($$args_ref{$_} eq 'full');
$$args_ref{'nodg'} =1 if($$args_ref{$_} eq 'nodg');
$$args_ref{'newdg'} =1 if($$args_ref{$_} eq 'newdg');
}
else
{
$$args_ref{$option} = $$args_ref{$_};
}
# Note: There are corner cases where both new option and the
# corresponding deprecated options is used together in a
# command the value that appears later in the order takes
# precedence.
# for eg : ASMCMD> md_backup -G DG1 -g DG2
# ASMCMD> md_backup -G DG1 -G DG2
# ASMCMD> md_backup -g DG1 -G DG2
# In the above examples md_backup will backup DG2 not DG1
asmcmdshare_trace(2, "WARNING:option '$_' is deprecated for '$cmd'",
'n', 'y');
asmcmdshare_trace(2, "please use '$option'\n", 'n',
'y') if($option ne 'NULL') ;
}
}
return;
}
##############
# NAME
# asmcmdshare_parse_xml
#
# DESCRIPTION
# This function parses the asmcommand.xml
#
# PARAMETERS
# -None-
#
# RETURNS
# None
##############
sub asmcmdshare_parse_xml
{
# The file asmcommanddiag.xml is parsed only if it exists.
my @xmlfiles = ("$ENV{'ORACLE_HOME'}/lib/asmcommand.xml" );
# set the handlers
# start_handler -> will be called whenever a new node is started.
# end_handler -> will be called whenever a node ends.
# char_hander -> will be called for the text within the node.
# This function will be called for each line
# on multi-line case. This callback is used to
# compose the description for option.
$xmlparser = XML::Parser->new(Handlers=>
{
Start=> \&asmcmdshare_xml_start_handler,
Char=>\&asmcmdshare_xml_char_handler,
End=>\&asmcmdshare_xml_end_handler
});
#parse the xml files
foreach (@xmlfiles)
{
$xmlparser->parsefile ($_) if (-r $_);
}
}
##############
# NAME
# asmcmdshare_xmlStartNodeHandler
#
# DESCRIPTION
# This function is a callback - called whenever a start-node occurs
#
# PARAMETERS
# -None-
#
# RETURNS
# None
##############
sub asmcmdshare_xmlStartNodeHandler
{
my ($expat, $element, %attrs)=@_;
# only interested in <command> nodes
if ($element eq $cmdNode)
{
# need to have cmdName attribute
if (defined($attrs{'cmdName'}))
{
$cmdName = $attrs{'cmdName'};
$inCmdNode = "TRUE";
$text = "";
if (defined($attrs{'wildcard'}) && ($attrs{'wildcard'} eq "true"))
{
$isWildcard = "true";
}
else
{
$isWildcard = "false";
}
if (defined($attrs{'noinstance'}))
{
if ($attrs{'noinstance'} eq "true")
{
$isNoinstance = "true";
}
elsif ($attrs{'noinstance'} eq "undef")
{
# 19654070: this is a special case in which the command needs to
# connect to the instance, but not for all its options. ASMCMD will
# try to connect to ASM but will not signal errors if connection
# fails.
$isNoinstance = "undef";
}
else
{
$isNoinstance = "false";
}
}
else
{
$isNoinstance = "false";
}
if (defined($attrs{'visible'}))
{
$isVisible = $attrs{'visible'};
}
# privileged required for this command. SYSASM is assumed for all cmds.
# if SYSDBA is specifed, SYSDBA connections can also run this command.
if (defined($attrs{'priv'}))
{
$privReqd = $attrs{'priv'};
}
}
}
}
##############
# NAME
# asmcmdshare_xmlTextNodeHandler
#
# DESCRIPTION
# This function is a callback - called on text node data .
#
# PARAMETERS
# -None-
#
# RETURNS
# None
##############
sub asmcmdshare_xmlTextNodeHandler
{
my($e, $string)=@_;
# in case of multi-line, this callback is called for each line,
# append and collect all lines together.
$text .= $string ;
}
##############
# NAME
# asmcmdshare_xmlEndNodeHandler
#
# DESCRIPTION
# This function is a callback - called on endnode.
#
# PARAMETERS
# -None-
#
# RETURNS
# None
##############
sub asmcmdshare_xmlEndNodeHandler
{
my ($expat, $element, %attrs)=@_;
# synopsis node
if ($element eq "synopsis")
{
$hSyntax{$cmdName} = $text ;
}
# description for command node.
if ($element eq "description")
{
$hCmdDesc{$cmdName} = $text;
}
#option-description node
if ($element eq "optDescription")
{
$hOptDesc{$cmdName} = $text ;
}
# example node
if ($element eq "example")
{
$hExample{$cmdName} = $text;
}
# seeAlso node
if ($element eq "seeAlso" )
{
$hSeeAlso{$cmdName} = $text ;
}
$text ="";
#
# Attributes for the command
#
if ($element eq $cmdNode)
{
# wildcard
if ($isWildcard eq "true")
{
$hWildcard{$cmdName} = "true";
}
else
{
$hWildcard{$cmdName} = "false";
}
# noinstance
if ($isNoinstance eq "true")
{
$hNoInst{$cmdName} = "true";
}
elsif ($isNoinstance eq "undef")
{
# 19654070: this is a special case in which the command needs to connect
# to the instance, but not for all its options. ASMCMD will try to connect
# to ASM but will not signal errors if connection fails.
$hNoInst{$cmdName} = "undef";
}
else
{
$hNoInst{$cmdName} = "false";
}
# visible
if ($isVisible eq "false")
{
$hisvisible{$cmdName} = "false";
}
# Privilege required. All Commands can be executed in SYSASM connections
# Commands which can be executed are listed here.
$hPrivSysAsm{$cmdName} = "True";
if ($privReqd eq "sysdba")
{
$hPrivSysDba{$cmdName} = "True";
}
}
}
##############
# NAME
# asmcmdshare_parse_xml_help
#
# DESCRIPTION
# This function parses the asmcommand.xml and asmcommnaddiag.xml files
#
# PARAMETERS
# -None-
#
# RETURNS
# None
##############
sub asmcmdshare_parse_xml_help
{
$text = "" ; # clear the text.
# The file asmcommanddiag.xml is parsed only if it exists.
my @xmlfiles = ("$ENV{'ORACLE_HOME'}/lib/asmcommand.xml");
#set the handlers
# xmlStartNodeHandler -> will be called whenever a new node is started
# xmlTextNodeHandler -> will be called whenever text node (multi line)
# xmlEndNodeHandler -> will be called whenever a node ends.
$xmlparser = XML::Parser->new( Handlers => {
Start => \&asmcmdshare_xmlStartNodeHandler,
Char => \&asmcmdshare_xmlTextNodeHandler,
End => \&asmcmdshare_xmlEndNodeHandler } ) ;
# parse the xml files
foreach (@xmlfiles)
{
$xmlparser->parsefile ($_) if (-r $_);
}
$xmlparsed = 1 ; # to avoid parsing xml file repeatedly.
}
########
# NAME
# asmcmdshare_get_help_syntax
#
# DESCRIPTION
# This function obtains the synopsis for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# syntax - syntax of the given command
########
sub asmcmdshare_get_help_syntax
{
$helpcmd = shift; # given command
# Syntax can be requested to display while invalid parameters are provided.
# Load the help from xml file if not already done.
if (!$xmlparsed)
{
asmcmdshare_parse_xml_help () ;
}
return $hSyntax{$helpcmd} ;
}
########
# NAME
# asmcmdshare_get_cmd_wildcard
#
# DESCRIPTOIN
# This function obtains flag - whether the give command supports wild card
#
# PARAMETERS
# cmd(IN) - current command.
#
# RETURNS
# True - if wild card is supported
# False - otherwise
#
########
sub asmcmdshare_get_cmd_wildcard
{
my $cmd = shift ; # given command.
my $ret = "false" ;
if (!$xmlparsed)
{
asmcmdshare_parse_xml_help () ;
}
if(defined $hWildcard{$cmd} && ($hWildcard{$cmd} eq "true" ) )
{
$ret = "true" ;
}
return $ret ;
}
########
# NAME
# asmcmdshare_get_cmd_noinst
#
# DESCRIPTOIN
# This function obtains flag - whether the give command needs ASM Instance or
# not.
#
# PARAMETERS
# cmd(IN) - current command.
#
# RETURNS
# true - if instance is not required
# false - otherwise
#
########
sub asmcmdshare_get_cmd_noinst
{
my ($cmd) = shift; # given command.
my ($ret) = "false";
asmcmdshare_parse_xml_help() if (!$xmlparsed);
$ret = $hNoInst{$cmd} if (defined($hNoInst{$cmd}));
return $ret;
}
########
# NAME
# asmcmdshare_is_cmd_visible
#
# DESCRIPTOIN
# This function checks if the given command is visible or hidden
#
# PARAMETERS
# cmd(IN) - current command.
#
# RETURNS
# 1 - if marked as visible
# 0 - otherwise
#
########
sub asmcmdshare_is_cmd_visible
{
my $cmd = shift ; # given command.
my $ret = 1 ;
if (!$xmlparsed)
{
asmcmdshare_parse_xml_help () ;
}
if(defined $hisvisible{$cmd} && $hisvisible{$cmd} ne "true" )
{
$ret = 0 ;
}
return $ret ;
}
# NAME
# asmcmdshare_get_help_cmdDesc
#
# DESCRIPTION
# This function obtains the description of the command
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# syntax - syntax of the given command
########
sub asmcmdshare_get_help_cmdDesc
{
$helpcmd = shift ; #given command
return $hCmdDesc{$helpcmd} ;
}
########
# NAME
# asmcmdshare_get_help_optDesc
#
# DESCRIPTION
# This function obtains the description for the option.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# description of the option for the command
########
sub asmcmdshare_get_help_optDesc
{
$helpcmd = shift ; #given command
# print "Opt Desc for $helpcmd \n $hOptDesc{$helpcmd}\n" ;
return $hOptDesc{$helpcmd};
}
########
# NAME
# asmcmdshare_get_help_syntax
#
# DESCRIPTION
# This function obtains the synopsis for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# example - example for the command.
########
sub asmcmdshare_get_help_example
{
$helpcmd = shift ; #given command
# print "Example for $helpcmd : $hExample{$helpcmd}\n";
return $hExample{$helpcmd};
}
########
# NAME
# asmcmdshare_get_help_seeAlso
#
# DESCRIPTION
# This function obtains the seeAlso for the specified command.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# seeAlso - seeAlso for the command.
########
sub asmcmdshare_get_help_seeAlso
{
$helpcmd = shift ; #given command
return $hSeeAlso{$helpcmd};
}
########
# NAME
# asmcmdshare_trim_str
#
# DESCRIPTION
# This function trims the given string
#
# PARAMETERS
# string (IN) - given string
#
# RETURNS
# string - trimmed string
########
sub asmcmdshare_trim_str
{
my $string = shift ; # given string to trim.
if (defined($string))
{
$string =~s/^\s+//; # remove leading spaces
$string =~s/\s+$//; # remove trailing spaces.
}
else
{
$string ="";
}
return $string ;
}
########
# NAME
# asmcmdshare_get_help_desc
# DESCRIPTION
# This function is the entry point to the parse help string and return.
#
# PARAMETERS
# cmd(IN) - current command being processed
#
# RETURNS
# helpstr - help description for the given command
########
sub asmcmdshare_get_help_desc
{
my $helpcmd = shift; # given command for help.
my $helpstr = "" ;
# Load the help from xml file if not already done.
if (!$xmlparsed)
{
asmcmdshare_parse_xml_help () ;
}
# description of the command
my $cmddesc = asmcmdshare_get_help_cmdDesc ($helpcmd);
$cmddesc = " ".asmcmdshare_trim_str ($cmddesc) ;
$helpstr .= $helpcmd."\n$cmddesc\n" ;
# synopsis of command
my $synopsis = asmcmdshare_get_help_syntax ( $helpcmd ) ;
$synopsis = "$synopStr\n ".asmcmdshare_trim_str ($synopsis);
$helpstr .= "\n$synopsis\n\n" ;
# options description
# options description could be empty (like in pwd).
my $optDesc = asmcmdshare_trim_str (asmcmdshare_get_help_optDesc ($helpcmd));
if ( "" ne $optDesc )
{
$optDesc = "$descStr\n ". $optDesc;
$helpstr .= "$optDesc\n\n" ;
}
# example
my $example = asmcmdshare_trim_str (asmcmdshare_get_help_example ($helpcmd));
if ($example ne "" )
{
$example = "$exampStr\n ". $example ;
$helpstr .= "$example\n\n" ;
}
#see Also
my $seeAlso = asmcmdshare_trim_str (asmcmdshare_get_help_seeAlso ($helpcmd));
if ($seeAlso ne "" )
{
$seeAlso = "$seeAlsoStr\n ". $seeAlso;
$helpstr .= "$seeAlso\n" ;
}
return $helpstr ;
}
########
# NAME
# asmcmdshare_print
#
# DESCRIPTION
# This function is a wrapper which calls asmcmdshare_print_internal with
# correct parameters.
#
# PARAMETERS
# string (IN) - The string to be printed.
#
# RETURNS
# Nothing.
#
# NOTES
# Ideally, each line being printed to the pipe should be terminated with a
# new line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_print
{
my $string = shift;
asmcmdshare_print_internal(1, $string);
}
########
# NAME
# asmcmdshare_printstderr
#
# DESCRIPTION
# This function is a wrapper which calls asmcmdshare_print_internal with
# correct parameters to print to STDERR.
#
# PARAMETERS
# string (IN) - The string to be printed.
#
# RETURNS
# Nothing.
#
# NOTES
# Ideally, each line being printed to the pipe should be terminated with a new
# line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_printstderr
{
my $string = shift;
asmcmdshare_print_internal(2, $string);
}
########
# NAME
# asmcmdshare_printprompt
#
# DESCRIPTION
# This function is a wrapper which calls asmcmdshare_print_internal with
# correct parameters. It is used to print a prompt (for confirmation from
# the user or requesting more information from the user.)
# The string which will be printed to the pipe will be prepended with
# "STDIN:" so that the foreground process prompts the user and accepts
# further user input. The foreground process then provides this data back to
# the daemon.
#
# PARAMETERS
# string (IN) - The string to be printed.
#
# RETURNS
# Nothing.
#
# NOTES
# Ideally, each line being printed to the pipe should be terminated with a new
# line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_printprompt
{
my $string = shift;
asmcmdshare_print_internal(0, $string);
}
########
# NAME
# asmcmdshare_print_internal
#
# DESCRIPTION
# This function prints the string passed as argument to the pipe or to the
# appropriate STD outputs depending on the filehandle type and the nature of
# the connection (pooled or otherwise).
# If it is to be printed to the pipe, (as in the case of a pooled connection)
# it prepends appropriate markers eg. "STDERR:" or "STDIN:" in case the string
# is to be printed to STDERR, or is to be printed as a prompt respectively.
#
# PARAMETERS
# $filehandletype (IN) - 0, for PROMPT
# - 1, for STDOUT
# - 2, for STDERR
# string (IN) - The string to be printed.
#
# RETURNS
# Nothing.
#
# NOTES
# Ideally, each line being printed to the pipe should be terminated with a new
# line, "\n", i.e, $string should be terminatted by newline.
#
########
sub asmcmdshare_print_internal
{
my $filehandle = shift;
my $string = shift;
# If the connection is not pooled, then print to either STDOUT or STDERR
# depending on the filehandle type
if(!$asmcmdglobal_hash{'ispooled'})
{
print $string if($filehandle <= 1);
print STDERR $string if($filehandle == 2);
return;
}
if($asmcmdglobal_hash{'isdaemon'})
{
if(!$asmcmdglobal_hash{'fgpipe'})
{
return;
}
# If the filehandle
# is 0, then this string is to be printed to the STDOUT by the foreground
# process as a prompt for further input/ a confirmation from the user.
if($filehandle == 0)
{
asmcmdshare_printpipe("$$:PROMPT:$string\n");
asmcmdshare_trace(3, "$asmcmdshare_logheader Requesting further " .
"user input for " . $asmcmdglobal_hash{'cmd'} .
" command.\n", 'y', 'n');
return;
}
elsif($filehandle >= 1)
{
my @buffer = split(/\n/, $string);
push(@buffer, "") if($string =~ m/\n\n$/ || $string eq "\n");
foreach(@buffer)
{
# If the filehandle
# is 1, then this string is to be printed to the STDOUT by the
# foreground process.
asmcmdshare_printpipe("$$:STDOUT:$_\n") if($filehandle == 1);
# If the filedescriptor is 2 then string should be printed to the
# STDERR.
asmcmdshare_printpipe("$$:STDERR:$_\n") if($filehandle == 2);
}
return;
}
}
# This block will be executed only if this function was called by the
# foreground process to pass further user input to the asmcmd daemon.
else
{
asmcmdshare_printpipe("$$:STDIN:$string");
return;
}
}
########
# NAME
# asmcmdshare_print_pipe
# DESCRIPTION
# This function prints the string to the pipe specified as argument
#
# PARAMETERS
# pipename (IN) - The pathname of the pipe to be written to.
# string (IN) - The string to be printed.
# flush (IN) - Whether to immediately print or buffer it.
#
# RETURNS
# Nothing.
########
sub asmcmdshare_printpipe
{
my $string;
my $fileno = fileno($asmcmdshare_writehandle);
$string = shift;
if (!defined($fileno))
{
asmcmdshare_trace(1, "$asmcmdshare_logheader Invalid file handle for " .
"pipe $$asmcmdshare_pipetowrite", 'y', 'n');
die;
}
if($asmcmdglobal_hash{'isdaemon'})
{
# Check whether the foreground process is alive, if it is not, then die
# Also check whether the pipe is stil present.
if((defined $asmcmdglobal_hash{'fgpid'} &&
!kill(SIGCHLD, $asmcmdglobal_hash{'fgpid'})) ||
!-p $asmcmdglobal_hash{'fgpipe'})
{
# Highly unlikely events.
# 1. Foreground process dies out after sending the command to daemon
# 2. Pipe to the foreground (created by the daemon) suddenly vanishes
if(-p $asmcmdglobal_hash{'fgpipe'})
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ".
"Removed pipe $asmcmdglobal_hash{'fgpipe'}: ".
"FG died after sending the command, ".
"or pipe to FG suddenly vanished.", 'y', 'n');
unlink $asmcmdglobal_hash{'fgpipe'};
}
asmcmdshare_removebgpipe();
exit(0);
}
}
else
{
# Check whether the background process is alive, if it is not, then die
# Also check whether the pipe is still present.
if((defined $asmcmdglobal_hash{'bgpid'} &&
!kill(SIGCHLD, $asmcmdglobal_hash{'bgpid'})) ||
!-p $asmcmdglobal_hash{'bgpipe'})
{
# Highly unlikely events.
# 1. Background process dies out after receiving the command
# 2. Pipe to the background (created by this foreground or a previous
# foreground) suddenly vanishes
if(-p $$asmcmdshare_pipetowrite)
{
asmcmdshare_closewritepipe();
asmcmdshare_trace(3, "$asmcmdshare_logheader ".
"Removed pipe $$asmcmdshare_pipetowrite: ".
"BG died after receiving command, ".
"or pipe to BG suddenly vanished.", 'y', 'n');
unlink $$asmcmdshare_pipetowrite;
}
die;
}
}
print $asmcmdshare_writehandle $string;
if ($asmcmdglobal_hash{'running'} == 1)
{
#NOTE: need to close/reopen pipe handle in case of iostat command.
close $asmcmdshare_writehandle;
open($asmcmdshare_writehandle, "> $$asmcmdshare_pipetowrite");
}
return 1;
}
########
# NAME
# asmcmdshare_readcommands
# DESCRIPTION
# This function reads a list of commands from the pipe used by foreground
# processes to send commands to the asmcmd daemon, if it's a connection
# pooled session. It also reads any user input that is available in the pipe
#
# PARAMETERS
# None.
#
# RETURNS
# None.
########
sub asmcmdshare_readcommands
{
my $string;
my $stdin_string;
my $waittime;
my $success = 0;
my $bgwtfilename;
if ($asmcmdshare_process_status eq "USERINPUT")
{
$waittime = $asmcmdshare_readpipewait;
}
else
{
$asmcmdshare_process_status = "READCOMMANDS";
$waittime = $asmcmdshare_daemonidlewait;
}
if ($asmcmdglobal_hash{'isdaemon'})
{
eval
{
alarm $waittime;
# asmcmd_bg_wait_<BGPID> is created to ensure that background process
# is in waiting mode to receive signal from foreground. This file will
# as soon as background process receives signal. Foreground
# process sends SIGUSR1 only if the file exists.
# 16875041: SIGUSR1 is no longer sent, it now waits until something is
# written in the pipe. asmcmd_bg_wait_<BGPID> is not used either.
asmcmdshare_trace(3, "$asmcmdshare_logheader Waiting for new " .
"commands/user input. Waiting for $waittime " .
"secs. Status = $asmcmdshare_process_status",
'y', 'n');
$string = asmcmdshare_readpipe();
# If this line is a user input rather than a command,
# then do not push it to the array, but return the data as a string
if ($string =~ /^(\d*):STDIN:(.*)$/)
{
if ($1 == $asmcmdglobal_hash{'fgpid'})
{
$stdin_string = $2;
chomp $stdin_string;
asmcmdshare_trace(3,"$asmcmdshare_logheader received user " .
"input for " . $asmcmdglobal_hash{'cmd'} .
" command.\n", 'y', 'n');
$success = 1 if($asmcmdshare_process_status eq "USERINPUT");
next;
}
}
if ($string =~ /^!!!!!(\d+#[^!]*)!!!!!!!!!!(\d+#[^!]*)!!!!!$/)
{
$string = $2;
$success = 1 if($asmcmdshare_process_status eq "READCOMMANDS");
}
elsif ($string =~ /^!!!!!(\d+#[^!]*)!!!!!$/)
{
$string = $1;
$success = 1 if($asmcmdshare_process_status eq "READCOMMANDS");
}
push (@asmcmdshare_commandlist, $string) if ($string =~ /^\d+#.*/);
alarm 0;
};
if ($@)
{
asmcmdshare_alarm_handler();
$stdin_string = "";
}
}
$asmcmdshare_process_status = "RUNNING";
if ($success == 0)
{
return '';
}
return $stdin_string;
}
########
# NAME
# asmcmdshare_readpipe
#
# DESCRIPTION
# This function reads a line of data from the pipe which has been
# designated as the read pipe.
#
# PARAMETERS
# None.
#
# RETURNS
# String that is read from the pipe
#
# NOTES
# Waits for the pipe to appear indefinitely, hence should be surrounded by
# an alarm. Also waits indefinitely on the opened pipe till a non-null string
# is read.
# Opens the pipe if the read filehandle is not open.
# Throws an exception if pipe could not be opened.
#
########
sub asmcmdshare_readpipe
{
my $string;
my $pipename;
asmcmdshare_chkpipeexst($$asmcmdshare_pipetoread);
# 19445832: Background can hog the CPU in a read spin. To avoid this, open
# the pipe and read it entirely to a buffer and then close it. If there are
# still lines in the buffer, there is no need to read the pipe. The pipe
# read will wait as expected only after the first open.
if ($asmcmdglobal_hash{'isdaemon'})
{
if (scalar(@asmcmdshare_readbuffer) == 0)
{
# Buffer is empty, read from pipe
my $pipe_buffer = '';
asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
"$$asmcmdshare_pipetoread to read data", 'y', 'n');
open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
or die "Could not open the pipe $$asmcmdshare_pipetoread";
asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
"pipe $$asmcmdshare_pipetoread in read mode", 'y', 'n');
@asmcmdshare_readbuffer = <$asmcmdshare_readhandle>;
map chomp, @asmcmdshare_readbuffer;
close $asmcmdshare_readhandle;
asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
"$$asmcmdshare_pipetoread.", 'y', 'n');
}
# Extract data from the buffer
$string = shift @asmcmdshare_readbuffer;
}
else # Foreground
{
if (!defined(fileno($asmcmdshare_readhandle)))
{
open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
or die "PIPE: Could not open the pipe $$asmcmdshare_pipetoread";
}
my $success = 0;
while (!$success)
{
$string = <$asmcmdshare_readhandle>;
$success = 1 if (defined $string);
}
}
return $string;
}
########
# NAME
# asmcmdshare_output
# DESCRIPTION
# This function reads whatever output is present in the pipe, and prints it.
#
# PARAMETERS
# None.
#
# RETURNS
# status (OUT) - 0 if end of output is reached
# - 1 if more output is expected, but pipe is empty for now.
#
# NOTES
# This funtion should be called only by the asmcmd foreground process, since
# it assumes the presence of the pipe used by the asmcmd daemon to communicate
# with the foreground process in a connection pooled session.
########
sub asmcmdshare_output
{
my $string;
my $ret = 1;
my ($close_readhandle, $close_writehandle);
while(1)
{
$string = asmcmdshare_readpipe();
# If marked with STDOUT print to STDOUT
if($string =~ /^(\d+):STDOUT:/)
{
$asmcmdglobal_hash{'bgpid'} = $1;
# if the string contains the terminating string.
if($string =~ $asmcmdshare_term_string)
{
# read the exit status from the terminating string
# if no esit status found, then assume '0'
if($string =~ m/^(\d+):STDOUT:(\d)/ ||
$string =~ m/^(\d+):STDOUT:(-\d)/)
{
$asmcmdglobal_hash{'bgpid'} = $1;
$asmcmdglobal_hash{'e'} = int($2);
}
$ret = 0; # Marks the end of output.
# Last line to be read from the pipe, hence close and remove it.
asmcmdshare_closereadpipe();
last;
}
# if string contains a request to reconnect to ASM.
elsif($string =~ $asmcmdshare_reconnect_string)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ASMCMD Daemon lost " .
"connection to ASM. Foreground process " .
"(PID = $$) will restart the ASMCMD Daemon " .
"to reconnect to ASM", 'y', 'n');
# Clear the command list as we are going to execute the command
# from the scratch after spawning the asmcmd dameon.
undef(@asmcmdshare_commandlist);
asmcmdshare_closereadpipe();
asmcmdshare_closewritepipe();
asmcmdshare_removereadpipe();
die $asmcmdshare_reconnect_string;
}
elsif($string =~ $asmcmdshare_disablecp_string)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ASMCMD Background " .
"process is unable to connect to ASM. " .
"Retrying the command without connection " .
"pooling.", 'y', 'n');
$asmcmdglobal_hash{'nocp'} = 1;
asmcmdshare_closereadpipe();
asmcmdshare_closewritepipe();
asmcmdshare_removereadpipe();
$asmcmdglobal_hash{'ispooled'} = 0;
$asmcmdglobal_hash{'bgpipe'} = '';
$asmcmdglobal_hash{'fgpipe'} = '';
die $asmcmdshare_disablecp_string;
}
else
{
$string =~ s/^(\d+):STDOUT://;
print $string; # Print the line of output to the console
}
}
# If marked with STDERR print to STDERR
elsif($string =~ /^(\d+):STDERR:/)
{
$asmcmdglobal_hash{'bgpid'} = $1;
$string =~ s/^(\d+):STDERR://;
print STDERR $string;
next;
}
# If marked with PROMPT, it means, the daemon is requesting more
# user input, possibly a confirmation from the user for an action.
elsif($string =~ /^(\d+):PROMPT:/)
{
$asmcmdglobal_hash{'bgpid'} = $1;
chomp $string;
$string =~ s/^(\d+):PROMPT://;
print $string;
my $userinput = <STDIN>;
asmcmdshare_openwritepipe();
asmcmdshare_printpipe("$$:STDIN:$userinput\n");
asmcmdshare_closewritepipe();
next;
}
elsif($string =~ /^!!!!![^!]*!!!!!/)
{
$string =~ s/^!!!!![^!]*!!!!!//;
}
};
return $ret; # More output is expected, but pipe is empty for the time being.
}
########
# NAME
# asmcmdshare_removefgpipe
#
# DESCRIPTION
# Removes the foreground pipe file. It will only remove the pipe
# corresponding to the current fg process ID.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
#
########
sub asmcmdshare_removefgpipe
{
my ($fgpipe) = $asmcmdglobal_hash{'fgpipe'};
if (defined($fgpipe))
{
if (-e $fgpipe)
{
if (unlink($fgpipe))
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Removed $fgpipe.",
'y', 'n');
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not remove " .
"$fgpipe: $!", 'y', 'n');
}
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader File to remove not found: " .
"$fgpipe.", 'y', 'n');
}
}
$asmcmdglobal_hash{'fgpipe'} = undef;
$asmcmdglobal_hash{'fgpid'} = undef;
}
########
# NAME
# asmcmdshare_removebgpipe
#
# DESCRIPTION
# Removes the background pipe file. It will only remove the pipe
# corresponding to the current bg process ID.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
#
########
sub asmcmdshare_removebgpipe
{
my ($bgpipe) = $asmcmdglobal_hash{'bgpipe'};
if (defined($bgpipe))
{
if (-e $bgpipe)
{
if (unlink($bgpipe))
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Removed $bgpipe.",
'y', 'n');
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Could not remove " .
"$bgpipe: $!", 'y', 'n');
}
}
else
{
asmcmdshare_trace(3, "$asmcmdshare_logheader File to remove not found: " .
"$bgpipe.", 'y', 'n');
}
}
$asmcmdglobal_hash{'bgpipe'} = undef;
$asmcmdglobal_hash{'bgpid'} = undef;
}
########
# NAME
# asmcmdshare_closereadpipe
#
# DESCRIPTION
# This function closes the filehandle to the read pipe, if it is open.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
# None.
#
########
sub asmcmdshare_closereadpipe
{
if(defined($asmcmdshare_readhandle) &&
defined (fileno($asmcmdshare_readhandle)))
{
# Empty the read pipe
while(<$asmcmdshare_readhandle>)
{};
close $asmcmdshare_readhandle;
asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
"$$asmcmdshare_pipetoread.", 'y', 'n');
}
}
########
# NAME
# asmcmdshare_closewritepipe
#
# DESCRIPTION
# This function closes the filehandle to the write pipe, if it is open.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
# None.
#
########
sub asmcmdshare_closewritepipe
{
if(defined($asmcmdshare_writehandle))
{
my $fno = fileno ($asmcmdshare_writehandle);
if (defined($fno))
{
close $asmcmdshare_writehandle;
asmcmdshare_trace(3, "$asmcmdshare_logheader Closed pipe " .
"$$asmcmdshare_pipetowrite.", 'y', 'n');
}
else # fileno is undefined.
{
asmcmdshare_trace(3, "$asmcmdshare_logheader NOT closing pipe",
'y', 'n');
}
}
}
########
# NAME
# asmcmdshare_removereadpipe
#
# DESCRIPTION
# Removes the read pipe if it exists.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
# Should only be called after closing the read handle.
#
########
sub asmcmdshare_removereadpipe
{
if(-p $$asmcmdshare_pipetoread)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ".
"Removed pipe $$asmcmdshare_pipetoread.", 'y', 'n');
unlink $$asmcmdshare_pipetoread;
}
}
########
# NAME
# asmcmdshare_openreadpipe
#
# DESCRIPTION
# This function opens the file handle for the read pipe
#
# Steps:
# Close the read filehandle if it is open.
# Waits for the pipe to appear.
# Opens the read handle with the pipe.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
# Has an internal alarm, hence will through an exception if the
# open pipe operation times out.
#
########
sub asmcmdshare_openreadpipe
{
my $waittime = shift;
$waittime = $asmcmdshare_openpipewait if(!defined $waittime);
$asmcmdshare_process_status = "OPENREADPIPE"
if $asmcmdshare_process_status eq "RUNNING";
eval
{
asmcmdshare_chkpipeexst($$asmcmdshare_pipetoread);
alarm $waittime;
# lrg8504705: asmcmdshare_trace (i.e. daemon) forks a process to execute
# clsecho(actually grand child) to write trace messages to file.
# clsecho and asmcmdshare_trace communicate through pipe. clsecho informs
# the daemon that IO is complete by writing to a pipe. If the asmcmd
# daemon recieves a signal (SIGUSR1), while reading this status from pipe,
# the perl ASSERTS resulting in asmcmd daemon going down.
# There is a bug in solaris(sparc) where in while reading from pipe if it
# receives a signal it asserts(i.e. after handling the signal and comming
# back to read again). Hence disabling this trace. we should not add any
# additonal tracing before asmcmdshare_readcommands in dameon.
my $trc_enabled = 1;
if (!($^O =~ /solaris/i && $Config{archname} =~ /sun4-solaris/i))
{
$trc_enabled = 0;
}
if ($trc_enabled)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
"$$asmcmdshare_pipetoread in read mode", 'y', 'n');
}
open($asmcmdshare_readhandle, "< $$asmcmdshare_pipetoread")
or die "Could not open the pipe $$asmcmdshare_pipetoread";
alarm 0;
if ($trc_enabled)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
"pipe $$asmcmdshare_pipetoread in read mode", 'y', 'n');
}
};
if($@)
{
asmcmdshare_alarm_handler();
}
$asmcmdshare_process_status = "RUNNING";
return;
}
########
# NAME
# asmcmdshare_openwritepipe
#
# DESCRIPTION
# This function opens the file handle for the write pipe
#
# Steps:
# Close the write filehandle if it is open.
# Waits for the pipe to appear.
# Opens the write handle with the pipe.
#
# PARAMETERS
# None.
#
# RETURNS
# None.
#
# NOTES
# Has an internal alarm, hence will through an exception if the
# open pipe operation times out.
#
########
sub asmcmdshare_openwritepipe
{
my $waittime = shift;
$waittime = $asmcmdshare_openpipewait if(!defined $waittime);
$asmcmdshare_process_status = "OPENWRITEPIPE"
if $asmcmdshare_process_status eq "RUNNING";
eval
{
asmcmdshare_chkpipeexst($$asmcmdshare_pipetowrite);
alarm $asmcmdshare_openpipewait;
asmcmdshare_trace(3, "$asmcmdshare_logheader Opening the pipe " .
"$$asmcmdshare_pipetowrite", 'y', 'n');
open($asmcmdshare_writehandle, "> $$asmcmdshare_pipetowrite")
or die "Could not open the pipe $$asmcmdshare_pipetowrite";
alarm 0;
asmcmdshare_trace(3, "$asmcmdshare_logheader Successfully opened the " .
"pipe $$asmcmdshare_pipetowrite", 'y', 'n');
};
if($@)
{
asmcmdshare_alarm_handler();
}
$asmcmdshare_process_status = "RUNNING";
return;
}
########
# NAME
# asmcmdshare_chkpipeexst
# DESCRIPTION
# This function checks if a pipe is available or not.
#
# RETURNS
# None. If the pipe does not available, it raises ALRM signal to
# trigger a timeout on open/write/read on pipe.
########
sub asmcmdshare_chkpipeexst
{
my ($pipe) = @_;
if (! -p $pipe)
{
asmcmdshare_trace(3, "$asmcmdshare_logheader ERROR: pipe $pipe not found.",
'y', 'n');
# raise ALRM signal to trigger daemon failure handle.
kill 'ALRM', $$;
}
return;
}
########
# NAME
# asmcmdshare_readstd
# DESCRIPTION
# This function reads one line from the STDIN.
# When called in a non-connection pooled session, it reads from STDIN.
# When caleld by the asmcmd daemon in a connection pooled session, it reads
# the pipe to the daemon (usually used for reading commands from foreground
# processes) for the user input (passed from the foreground process).
#
# PARAMETERS
# None.
#
# RETURNS
# string (OUT) - The string that was read from the STDIN.
########
sub asmcmdshare_readstdin
{
my $string;
if($asmcmdglobal_hash{'isdaemon'})
{
$asmcmdshare_process_status = "USERINPUT";
$string = asmcmdshare_readcommands();
$asmcmdshare_process_status = "RUNNING";
return $string;
}
elsif($asmcmdglobal_hash{'ispooled'})
{
$string = <CPSTDIN>;
}
else
{
$string = <STDIN>;
}
return $string;
}
########
# NAME
# asmcmdshare_backup_stdio
# DESCRIPTION
# This function backs up the STD I/O filehandles
#
# PARAMETERS
# None.
#
# RETURNS
# None.
########
sub asmcmdshare_backup_stdio
{
open(CPSTDIN , ">&STDIN" );
}
#########
# NAME
# asmcmdshare_execute_tool
#
# DESCRIPTION
# To execute given tool with parameters and return the output
#
# PARAMETERS
# $cmd (IN) - command to execute
# $winext (IN) - suffix in Windows platform (EXE/BAT/...)
# $dirs (IN) - list of directories to search for.
# $param (IN) - parameter for the command.
# $redirect (IN) - OPTIONAL: redirect stderr to stdout (default: 0)
# $skiptrace(IN) - OPTIONAL: default: 0, If set to 1, then skip tracing.
#
# RETURNS
# cmd output (which by default is only stdout) or execution error
###########
sub asmcmdshare_execute_tool
{
my ($cmd, $wext, $param, $dirs) = @_ ;
my $redirect = defined $_[4] ? $_[4] : 0;
my $skiptrace = defined $_[5] ? $_[5] : 0;
my $exec;
my $dir;
my @result;
my $rc;
my $sig;
my $cmdfound = 0;
my @eargs;
$asmcmdglobal_hash{'utilsucc'} = "false" ;
foreach $dir (@{$dirs})
{
$exec = $dir.$cmd;
if ( $^O =~ /win/i)
{
$exec .= $wext;
}
if (-x $exec)
{
$cmdfound = 1;
$exec .= " $param";
# The error output of some tools actually go to stderr, but perl
# backticks only capture stdout. In some cases, we want to look for
# specific error messages in a tool's output. To do so, we redirect
# stderr to stdout.
if ($redirect)
{
$exec .= " 2>&1";
}
#untaint
$exec =~ /([^\n^\r^\t]+)/;
$exec = $1;
# 27688692: skip tracing when $skiptrace is set.
if ($skiptrace == 0)
{
asmcmdshare_trace(3,"NOTE: Executing $exec..", 'n', 'n');
}
# execute the command
eval
{
# 20434627: if SIG{CHLD} is IGNORED, perl always set $? to -1 and $! to
# (no child process). It might be because when signal IGNORED, it seems
# somehow the child status is lost.
local $SIG{CHLD} = 'DEFAULT';
@result = `$exec`;
};
if ($@ eq '')
{
# If the tool we're executing, e.g. segfaults, the return code will be
# 0, but we shouldn't consider the execution to be a success. So we
# look at the signal id as well.
$rc = $CHILD_ERROR >> 8; # get return code (high 8 bits) #
$sig = $CHILD_ERROR & 0x7F; # get signal id (low 7 bits) #
$asmcmdglobal_hash{'utilsucc'} = "true" if (!$CHILD_ERROR);
}
else
{
@result = ($@); # execution error #
}
last;
}
}
if (!$cmdfound)
{
# Something wrong. Executable not found.
@eargs = (uc($cmd), join(' ', @{$dirs}));
asmcmdshare_signal_exception(9529, \@eargs);
}
if ($asmcmdglobal_hash{'utilsucc'} ne "true")
{
asmcmdshare_trace(3, "NOTE: unable to execute $cmd... " .
"ret:$rc sig:$sig err:$!", 'y', 'n');
}
return @result;
}
########
# NAME
# asmcmdshare_get_instance_name
#
# DESCRIPTION
# This function retrieves the instance name of the ASM instance to which the
# ASMCMD is connected to.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
#
# RETURNS
# The instance name of the ASM instance.
########
sub asmcmdshare_get_instance_name
{
my ($dbh) = shift; # Database handle. #
my ($sth); # SQL statement handle. #
my ($inst_name); # ASM instance name. #
my ($query); # SQL query statement. #
my ($row); # Row of query results. #
# SQL for getting the ASM instance software version.
$query = 'select instance_name from v$instance';
# Execute the SQL.
$sth = asmcmdshare_do_select($dbh, $query);
# Fetch the only row of results.
$row = asmcmdshare_fetch($sth);
# Get the version number.
$inst_name = $row->{'INSTANCE_NAME'};
# Close the statement handle.
asmcmdshare_finish($sth);
return $inst_name;
}
##############
# NAME
# asmcmdshare_filter_invisible_cmds
#
# DESCRIPTION
# To filter invisble coomands. Only visible command Names are printed
#
# PARAMETER
# cmdlist - hash of command names from each module
#
# RETURNS
# -None-
##############
sub asmcmdshare_filter_invisible_cmds
{
my %cmdlist = @_;
foreach my $cmd (keys %cmdlist)
{
# exclude hidden commands
if(!asmcmdshare_is_cmd_visible($cmd))
{
delete($cmdlist{$cmd});
}
}
return asmcmdshare_print_cmds(sort(keys %cmdlist));
}
############### MASTER-SLAVE Support #######################################
##############
# NAME
# asmcmdshare_setstate
#
# DESCRIPTION
# Changes the state of the current process
#
# PARAMETER
# newstate - The process state might be one of the following strings:
# * 'WAITING'
# * 'RUNNNING'
# * 'CONNECTED'
# * 'UNCONNECTED'
#
# RETURNS
# -None-
##############
sub asmcmdshare_setstate
{
my ($newstate) = shift || '';
# Avoid undefined state
$asmcmdshare_state = "RUNNING" unless defined($asmcmdshare_state);
my ($logmsg) = "[WARNING]: Process state: '" . $asmcmdshare_state .
"' -> '" . $newstate . "'";
asmcmdshare_trace( 1, $logmsg, 'y', 'n');
$asmcmdshare_state = $newstate;
}
##############
# NAME
# asmcmdshare_wait
#
# DESCRIPTION
# Changes the state of the current process
#
# PARAMETER
# newstate - The process state might be one of the following strings:
# * 'WAITING'
# * 'RUNNNING'
# * 'CONNECTED'
# * 'UNCONNECTED'
#
# RETURNS
# -None-
##############
sub asmcmdshare_wait
{
my ($prevstate) = $asmcmdshare_state;
asmcmdshare_setstate "WAITING";
while ($asmcmdshare_wait_flag == 1)
{
sleep (1);
}
# reset the asmcmdshare_wait flag for the next wait
$asmcmdshare_wait_flag = 1;
asmcmdshare_setstate $prevstate;
}
##############
# NAME
# asmcmdshare_waitslaves
#
# DESCRIPTION
#
# In a master-slave context, this routine is called by the master process to
# wait until all its slaves finished their tasks. This allows the master to
# perform all the actions it needs before its slaves die.
#
# PARAMETER
# NONE
#
# RETURNS
# NONE
##############
sub asmcmdshare_waitslaves {
if (defined($asmcmdglobal_hash{'ismaster'}))
{
asmcmdshare_trace(1, "Waiting for slaves to finish", 'y', 'n');
while ($asmcmdshare_missing > 0)
{
sleep (1);
}
asmcmdshare_trace(1, "All slaves have finished", 'y', 'n');
}
else
{
asmcmdshare_trace(1,
"[ERROR]: I am not a master process. Nothing to wait", 'n', 'n');
}
}
##############
# NAME
# asmcmdshare_packmsg
#
# DESCRIPTION
#
# This function allows a process build a well formed message string before
# sending it through a pipe.
#
# This message format allows the receiver process to know what kind of data
# the message contains before reading its content.
#
# PARAMETER
#
# data (IN) - Could be a scalar value or a reference to a data structure
#
# RETURNS
# msg - String that contains a serialized form of input data.
#
# NOTES
#
# Is very important for the receiver process to know when the message is a
# data scructure or a scalar.
##############
sub asmcmdshare_packmsg {
my ($data) = shift;
my ($type) = ref $data;
my ($buf);
my ($msg);
unless(defined $data) {
asmcmdshare_trace (1, "[FAIL]: Message undefined", 'y', 'n');
return undef;
}
if ($type =~ m/ARRAY|HASH|SCALAR/)
{
asmcmdshare_trace(3,
"Message type: reference to $type. packing message...", 'y', 'n');
## Create a string representation of the structure referenced by $data
$buf = Dumper $data;
$buf =~ s/\$VAR.*=\s//g;
$buf =~ s/\n/ /g;
$buf =~ s/\s+/ /g;
$buf =~ s/;\s*$//g;
$msg = "!!!!REFERENCE:#$buf#!!!!\n";
}
elsif( $type eq '' )
{
asmcmdshare_trace(3,
"Message type: SCALAR. packing message...", 'y', 'n');
$msg = "!!!!SCALAR:#$data#!!!!\n";
}
else
{
asmcmdshare_trace(1, "[ERROR]: Bad message -- $data\n", 'y'. 'n');
$msg = undef;
}
return $msg;
}
##############
# NAME
# asmcmdshare_unpackmsg
#
# DESCRIPTION
#
# PARAMETER
# string (IN) String to convert into data.
#
# RETURNS
# -None-
##############
sub asmcmdshare_unpackmsg
{
my ($string) = shift;
my ($ref);
$string =~ m/^!!!!(.*)!!!!$/;
$string = $1;
if (defined($string))
{
if ($string =~ m/^REFERENCE:#(.*)#$/)
{
$ref = eval( $1 ); # TODO: Validate first, this is insecure!!!
}
elsif ($string =~ m/^SCALAR:#(.*)#$/)
{
$ref = $1;
}
else
{
asmcmdshare_trace(1, "[ERROR]: Invalid message [$string]", 'n', 'n');
$ref = undef;
}
}
else
{
asmcmdshare_trace(1, "[ERROR]: undefined parameter for function ".
"asmcmdshare_unpackmsg", 'n', 'n');
$ref = undef;
}
asmcmdshare_trace(3, "Message unpacked: [$ref]", 'y', 'n');
return $ref;
}
########
# NAME
# asmcmdshare_logdata
#
# DESCRIPTION
#
# This function allows to print data structures on log and trace files
#
# PARAMETERS
# level (IN) - See level parameter of asmcmdshare_trace function.
# label (IN) - String printed as prefix of all the trace lines generated
# by this function.
# data (IN) - Reference to the data to be traced.
# timestamp (IN) - See timestamp parameter of asmcmdshare_trace function.
# term (IN) - See term parameter of asmcmdshare_trace function.
#
# RETURNS
# NONE
#
########
sub asmcmdshare_logdata
{
my ($level, $label, $data, $timestamp, $term) = @_;
my ($buf, @lines);
$buf = Dumper $data;
$buf =~ s/\$.*=//;
$buf =~ s/\n/-----/g;
$buf =~ s/\s+/ /g;
$buf =~ s/-----/\n/g;
@lines = split( /\n/, $buf);
foreach (@lines) {
asmcmdshare_trace($level, "[$label]: $_", $timestamp, $term);
}
}
########
# NAME
# asmcmdshare_signal
#
# DESCRIPTION
#
#
# PARAMETERS
#
#
# RETURNS
#
########
sub asmcmdshare_signal
{
my ($pid) = shift;
my ($success) = 1;
eval {
asmcmdshare_trace(5, "Sending signal USR1 from proc[$$] to " .
"proc[$pid].", 'y','n');
kill SIGUSR1, $pid;
};
if ($@) {
# TODO: Use asmcmdshare_error with an appropriate error message instead of
# asmcmdshare_trace
asmcmdshare_trace(1, "Could not send the signal to process [$pid]: $!",
'y','n');
$success = 0;
}
return $success;
}
##############
# NAME
# asmcmdshare_killslave
#
# DESCRIPTION
#
# In case of error, the master process can use this function to send the KILL
# signal to the given slave
#
# PARAMETER
# ctx - (IN) Process context, contains the PID of the process to kill
#
# NOTES
#
# RETURNS
# -None-
##############
sub asmcmdshare_killslave
{
my ($ctx) = shift;
my ($id) = $ctx -> {'id'};
my ($pid) = $ctx -> {'pid'};
eval {
# Check if the process can be killed before try to kill it.
kill(0, $pid) && kill(9, $pid);
};
if ($@) {
asmcmdshare_trace(1, "[ERROR] Could not kill slave [$id]", 'y', 'n');
}
}
##############
# NAME
# asmcmdshare_mkfifo
#
# DESCRIPTION
# Function used to create a named pipe
#
# PARAMETER
# fifo_name - (IN) File name for the new named pipe.
#
# NOTES
#
# RETURNS
# 1 on success, 0 otherwise.
##############
sub asmcmdshare_mkfifo
{
my ($fifo_name) = shift;
my ($success) = 0;
eval
{
if (-p $fifo_name)
{
asmcmdshare_trace(4, "Pipe '$fifo_name' already exists", 'y', 'n');
unlink $fifo_name;
}
elsif (-e $fifo_name)
{
asmcmdshare_trace(2,
"[WARNING]: '$fifo_name' is not a pipe file",'y','n');
unlink $fifo_name;
}
asmcmdshare_trace(3, "Creating a new pipe on '$fifo_name'.", 'y', 'n');
mkfifo $fifo_name, 0664;
$success = 1;
};
if ($@) {
# handle failure...
asmcmdshare_trace(1, "[ERROR]: Could not create the pipe [$fifo_name]",
'y','n');
$success = 0;
}
return $success;
}
##############
# NAME
# asmcmdshare_connect
#
# DESCRIPTION
#
# This function is called by master and slaves processes to avoid dead locks
# when they create the communication channel.
#
# PARAMETER
# ctx - (IN) Process context
#
# NOTES
#
# RETURNS
# 1 on success, 0 otherwise.
##############
sub asmcmdshare_connect
{
my ($ctx) = shift;
my ($success) = 0;
my ($reader_pipe, $writer_pipe);
my ($reader, $writer);
my ($pid, $id);
my ($waittime) = $asmcmdshare_openpipewait || 5; # Wait for 5s as default
asmcmdshare_trace(3, "Context", 'y', 'n');
asmcmdshare_logdata(3, "CONTEXT", $ctx, 'y', 'n');
$reader_pipe = $ctx -> {'pipe_to_read'};
$writer_pipe = $ctx -> {'pipe_to_write'};
if (defined($asmcmdglobal_hash{'isslave'})) # Am I a slave process?
{
eval
{
asmcmdshare_trace(3, "Ready to connect, waiting for master", 'y', 'n');
asmcmdshare_trace(3, "Waiting for pipe [$reader_pipe]" , 'y', 'n');
asmcmdshare_setstate('OPENREADPIPE');
alarm $waittime;
while(!-p $reader_pipe)
{ # Wait until the pipe is created
sleep(1);
}
asmcmdshare_trace(3, "Pipe [$reader_pipe] was found", 'y', 'n');
open $reader, "< $reader_pipe";
alarm 0;
asmcmdshare_setstate('UNCONNECTED');
$ctx -> {'reader_handler'} = $reader;
asmcmdshare_trace(3, "[SUCCESS]: Process connected to receive messages ".
"from master.", 'y', 'n');
asmcmdshare_trace(3, "Creating pipe for writing [$writer_pipe]", 'y','n');
asmcmdshare_setstate('OPENWRITEPIPE');
asmcmdshare_mkfifo $writer_pipe;
alarm $waittime;
open $writer, "> $writer_pipe";
alarm 0;
asmcmdshare_setstate('UNCONNECTED');
$ctx -> {'writer_handler'} = $writer;
$success = 1;
};
if ($@)
{
asmcmdshare_trace(1,
"[ERROR]: Could not connect slave process with master.", 'y', 'n');
$success = 0;
}
}
elsif(defined($asmcmdglobal_hash{'ismaster'}))# No, so I am the master process
{
$pid = $ctx -> {'pid'};
$id = $ctx -> {'id'};
eval
{
asmcmdshare_trace(4, "Ready to connect to slave [$id]", 'y', 'n');
asmcmdshare_mkfifo $writer_pipe;
asmcmdshare_setstate('OPENWRITEPIPE');
alarm $waittime;
open $writer, "> $writer_pipe";
alarm 0;
asmcmdshare_setstate('UNCONNECTED');
$ctx -> {'writer_handler'} = $writer;
asmcmdshare_trace(4, "[SUCCESS]: Process connected to send messages to " .
"slave [$id]", 'y', 'n');
asmcmdshare_trace(4, "Ready to connect WITH slave $id for receiving " .
"messages", 'y', 'n');
asmcmdshare_setstate('OPENREADPIPE');
alarm $waittime;
while(!-p $reader_pipe)
{ # Wait until the pipe is created
sleep(1);
}
asmcmdshare_trace(4, "Pipe [$reader_pipe] was found", 'y', 'n');
open $reader, "< $reader_pipe";
alarm 0;
asmcmdshare_setstate('UNCONNECTED');
$ctx -> {'reader_handler'} = $reader;
$success = 1;
asmcmdshare_trace(1, "[SUCCESS]: Process connected to receive messages ".
"from slave $id.", 'y', 'n');
};
if ($@) {
asmcmdshare_trace(1,
"Could not connect master process with slave [$id]", 'y', 'n');
$success = 0;
}
}
asmcmdshare_trace(1, "Status: $success", 'y', 'n');
asmcmdshare_setstate ('CONNECTED') if ($success == 1);
return $success;
}
########
# NAME
# asmcmdshare_sendmsg
#
# DESCRIPTION
# This function is used to send a message from one process to another.
#
# PARAMETERS
# ctx - (IN) Process context, it contains all the information regarding
# the target process and the communication channel.
# data - (IN) Could be a scalar or a reference to any data structure. This
# is the message to send to the other process.
#
# RETURNS
# 1 on success, 0 otherwise.
#
# NOTES
#
# See the function asmcmdshare_fork_slaves for more information about the
# context structure ctx.
########
sub asmcmdshare_sendmsg
{
my ($ctx, $data) = @_;
my ($pipe_to_write) = $ctx -> {'pipe_to_write'};
my ($writer) = $ctx -> {'writer_handler'};
my ($success) = 0;
my ($fileno);
my ($type);
my ($msg);
asmcmdshare_trace(3, "Inside asmcmdshare_sendmsg function", 'y', 'n');
$fileno = fileno($writer);
if(!defined($fileno))
{
asmcmdshare_trace(3, "Opening pipe for writing [$pipe_to_write]",
'y', 'n');
open($writer, "> $pipe_to_write")
or die "[ERROR]: Could not open the pipe $pipe_to_write";
$ctx -> {'writer_handler'} = $writer;
}
# Check the type of $data, only reference types are considered safe. Scalars,
# such as numbers, strings or references.
# Packing the message, $msg will be undef in case of error.
$msg = asmcmdshare_packmsg $data;
if (defined $msg)
{
asmcmdshare_trace(3, "Sending message...", 'y', 'n');
print $writer $msg;
close $writer;
asmcmdshare_trace(3,
"Message sent from PID($$) to PID(". $ctx -> {'pid'} .")", 'y', 'n');
$success = 1;
}
else
{
asmcmdshare_trace(1, "Could not send message.", 'y', 'n');
asmcmdshare_logdata(1, "MESSAGE", $data, 'n', 'n' );
$success = 0;
}
asmcmdshare_trace(3, "Leaving asmcmdshare_sendmsg function", 'y', 'n');
return $success;
}
##############
# NAME
# asmcmdshare_recvmsg
#
# DESCRIPTION
# This function is used to receive a message from other process.
#
# PARAMETERS
# ctx - (IN) Process context, it contains all the information regarding
# the source process and the communication channel.
#
# NOTES
# See the function asmcmdshare_fork_slaves for more information about the
# context structure ctx.
#
# RETURNS
# msg - If the message is a scalar variable, msg holds the message
# itself, if the message is a data structure, msg is a
# reference to that data strcuture. If the message reception
# fails, undef value is returned.
##############
sub asmcmdshare_recvmsg
{
my ($ctx) = shift;
my ($reader) = $ctx -> {'reader_handler'};
my ($pipe_to_read) = $ctx -> {'pipe_to_read'};
my ($success) = 0;
my ($string, $msg);
my ($fileno);
asmcmdshare_trace(5, "Inside recvmsg function", 'y', 'n');
$fileno = fileno($reader);
if(!defined $fileno)
{
asmcmdshare_trace(1, "Pipe is not opened for reading [$pipe_to_read]",
'y', 'n');
open($reader, "< $pipe_to_read")
or die "PIPE: Could not open the pipe $pipe_to_read";
$ctx -> {'reader_handler'} = $reader;
}
asmcmdshare_trace(3, "Reading message [$pipe_to_read]", 'y', 'n');
while(!$success)
{
$string = <$reader>;
$success = 1 if(defined $string);
};
asmcmdshare_trace(3, "Unpacking message", 'y', 'n');
$msg = asmcmdshare_unpackmsg $string;
if (defined $msg)
{
asmcmdshare_trace(3,
"Message received from PID(". $ctx -> {'pid'} . ") successfully.",
'y', 'n');
}
else
{
asmcmdshare_trace(1,
"[FAIL]: Could not receive the message from PID(". $ctx -> {'pid'} . ")",
'y', 'n');
asmcmdshare_trace(1, "$string", 'y', 'n');
}
asmcmdshare_trace(3, "Leaving recvmsg function", 'n', 'n');
return $msg;
}
##############
# NAME
# asmcmdshare_slave_main
#
# DESCRIPTION
#
# This is the main function for each slave process it's in charge of:
# - Setup the signal manager
# - Connect with the master process and receive the job parameters from it
# - Run the specified callback
#
#
# PARAMETER
# ctx - (IN) Process context
#
# NOTES
# See the function asmcmdshare_fork_slaves for more information about the
# context structure ctx.
#
# RETURNS
# -None-
##############
sub asmcmdshare_slave_main
{
my ($ctx) = shift;
my ($string);
my ($i);
my ($continue) = 1;
my ($id) = $ctx -> {'id'};
my ($success) = 0;
my ($function);
my ($msg);
my (@arr);
my ($res);
# Connecting signal handler
# $SIG{'USR1'} = \&asmcmdshare_signal_handler;
# $SIG{'USR2'} = \&asmcmdshare_signal_handler;
# $SIG{'INT'} = \&asmcmdshare_signal_handler;
# $SIG{'QUIT'} = \&asmcmdshare_signal_handler;
# $SIG{ALRM} = sub { die "ALARM" };
$0 = "asmcmd_slave_" . $$; # change the process name
asmcmdshare_trace(3,
"Starting ASMCMD slave process [$id] (PID = $$)", 'n', 'n');
asmcmdshare_trace(3, "Master process PID: " . $ctx -> {'pid'}, 'n', 'n');
asmcmdshare_setstate("UNCONNECTED");
asmcmdshare_trace(5, "Connecting...", 'y', 'n');
$success = asmcmdshare_connect($ctx);
if ($success)
{
$msg = asmcmdshare_recvmsg($ctx);
asmcmdshare_setstate("RUNNING");
#Execute the callback
$function = $ctx -> { 'callback' };
$res = $function -> ( $ctx, $msg );
asmcmdshare_trace(3, "DONE", 'y', 'n');
}
else
{
asmcmdshare_trace(1,
"[ERROR]: Could not connect with master proccess.", 'y', 'n');
}
}
########
# NAME
# asmcmdshare_fork
#
# DESCRIPTION
# This routine forks the present undivided process, creating a child process
#
# PARAMETERS
# None.
#
# RETURNS
# $pid - It returns the pid of the child to the parent and 0 to the child..
#
# NOTES
########
sub asmcmdshare_fork
{
my ($pid);
defined($pid = fork) || die "Could not fork: $!";
return $pid if $pid;
(setsid() != -1) || die "Could not start a new session";
return $pid;
}
##############
# NAME
# asmcmdshare_fork_slaves
#
# DESCRIPTION
# This routine forks the present undivided process, creating a set of slave
# processes
#
#
# PARAMETER
# slaves - (OUT) List of slave descriptors
# num - (IN) Amount of slaves to fork.
# callbacks - (IN) List of callbacks, one for each slave.
#
# NOTES
#
# Each slave is described by a context hash, which contains information about
# the communication channel. The master process holds an array of contexts
# with as many elements as slaves had been created. Each slave holds its own
# context structure.
#
# CONTEXT STRUCTURE HELD BY MASTER
#
# ctx :
# id - Slave identifier.
# pid - Target process PID.
# pipe_to_write - Output named pipe file name.
# writer_handler - Output named pipe file descriptor.
# pipe_to_read - Input named pipe file name.
# reader_handler - Input named pipe file descriptor.
# callback - Reference to the callback function (only for slave
# processes).
#
#
# RETURNS
# This function fills the list slaves with one slave context for each process
# created.
##############
sub asmcmdshare_fork_slaves
{
my ($slaves, $num, $callbacks) = @_;
my ($pid);
my ($ctx);
my ($id);
my ($master_pid) = $$;
# #17786672: FIFO file spec
# From master to slave - <TMP>/asmcmd_pipes/<MASTER_PID>.<SLAVE_PID>
# From slave to master - <TMP>/asmcmd_pipes/<SLAVE_PID>.<MASTER_PID>
$asmcmdshare_pipehome = "$asmcmdglobal_hash{'tempdir'}/asmcmd_pipes";
unless (-d $asmcmdshare_pipehome)
{
mkdir $asmcmdshare_pipehome, 0775;
}
else
{
my ($msg) = "Could not write $asmcmdshare_pipehome directory";
die $msg unless (-w $asmcmdshare_pipehome);
}
for( $id = 0; $id < $num; $id++ )
{
$pid = asmcmdshare_fork;
if ($pid)
{
# Report where is the slave trace file
my $slave_trace = "$asmcmdglobal_trace_path/trace/asmcmd_slave_$pid.trc";
asmcmdshare_trace(3, "Starting slave process $id: asmcmd_slave_$pid ".
"(PID = $pid)\nTrace file: $slave_trace", 'y','n');
$ctx = {
'id' => $id,
'pid' => $pid,
'pipe_to_write' => "$asmcmdshare_pipehome/$master_pid.$pid",
'pipe_to_read' => "$asmcmdshare_pipehome/$pid.$master_pid",
'writer_handler' => undef,
'reader_handler' => undef
};
$slaves -> [$id] = $ctx;
asmcmdshare_trace(5, "Context for slave $id:", 'n', 'n');
asmcmdshare_logdata(5, "CTX($id)", $ctx, 'n', 'n');
}
else
{
$ctx = {
'id' => $id,
'pid' => $master_pid,
'callback' => $callbacks -> [$id],
'pipe_to_write' => "$asmcmdshare_pipehome/$$.$master_pid",
'pipe_to_read' => "$asmcmdshare_pipehome/$master_pid.$$",
'writer_handler' => undef,
'reader_handler' => undef
};
$asmcmdglobal_hash{'isslave'} = 1;
$asmcmdglobal_hash{'ismaster'} = undef;
$SIG{'USR1'} = \&asmcmdshare_signal_handler;
$SIG{'USR2'} = \&asmcmdshare_signal_handler;
$SIG{'INT'} = \&asmcmdshare_signal_handler;
$SIG{'QUIT'} = \&asmcmdshare_signal_handler;
$SIG{ALRM} = sub { die "ALARM" };
asmcmdshare_slave_main $ctx;
exit 0;
}
}
$asmcmdshare_missing = $num;
$SIG{'USR1'} = \&asmcmdshare_signal_handler;
$SIG{'USR2'} = \&asmcmdshare_signal_handler;
$SIG{'INT'} = \&asmcmdshare_signal_handler;
$SIG{ALRM} = sub { die "ALARM" };
$asmcmdglobal_hash{'isslave'} = undef;
$asmcmdglobal_hash{'ismaster'} = 1;
asmcmdshare_setstate('UNCONNECTED');
return 0;
}
########
# NAME
# asmcmdshare_check_reqd_priv
#
# DESCRIPTION
# This routine checks whether the current command can run in the privilege
# specified.
#
# PARAMETERS
# -NONE-
#
# RETURNS
# 1 - if the command can be executed.
# 0 - if the command can NOT be executed.
#
# NOTES
# All commands can be executed in SYSASM privilege. Not all commands can be
# executed in SYSDBA privilege. Commands which can be executed in SYSDBA
# privilege has an attribute "priv" set to "sysdba" in asmcommand.xml
#
########
sub asmcmdshare_check_reqd_priv
{
my ($cmd) = $asmcmdglobal_hash{'cmd'};
my ($contyp) = $asmcmdglobal_hash{'contyp'};
my ($ret) = 0 ;
# check to see if the given command can run as SYSASM, for SYSASM connections
# if ((($contyp eq "sysasm") && (defined($hPrivSysAsm{$cmd}))))
# {
# $ret = 1;
# }
# elsif (($contyp eq "sysdba") && (defined($hPrivSysDba{$cmd})))
# {
# $ret = 1;
# }
if ( (($contyp eq "sysasm") && (defined($hPrivSysAsm{$cmd}))) ||
(($contyp eq "sysdba") && (defined($hPrivSysDba{$cmd}))) )
{
$ret = 1;
}
return $ret;
}
########
# NAME
# asmcmddisk_init_disk_attr
#
# DESCRIPTION
# Declares and initializes a disk fields hash.
#
# PARAMETERS
# <none>
#
# RETURNS
# An initialized disk fields hash.
#
########
sub asmcmdshare_init_disk_attr
{
my (%dsk_info);
# Initialize elements to null strings so that they are defined and
# don't trigger Perl warnings if the fixed view does not return
# any contents for a particular column.
$dsk_info{'inst_id'} = '';
$dsk_info{'group_number'} = '';
$dsk_info{'disk_number'} = '';
$dsk_info{'incarnation'} = '';
$dsk_info{'mount_status'} = '';
$dsk_info{'header_status'} = '';
$dsk_info{'mode_status'} = '';
$dsk_info{'state'} = '';
$dsk_info{'redundancy'} = '';
$dsk_info{'library'} = '';
$dsk_info{'os_mb'} = '';
$dsk_info{'total_mb'} = '';
$dsk_info{'free_mb'} = '';
$dsk_info{'name'} = '';
$dsk_info{'failgroup'} = '';
$dsk_info{'site_name'} = '';
$dsk_info{'site_guid'} = '';
$dsk_info{'site_status'} = '';
$dsk_info{'label'} = '';
$dsk_info{'fgroup_label'} = '';
$dsk_info{'site_label'} = '';
$dsk_info{'path'} = '';
$dsk_info{'udid'} = '';
$dsk_info{'product'} = '';
$dsk_info{'create_date'} = '';
$dsk_info{'mount_date'} = '';
$dsk_info{'repair_timer'} = '';
$dsk_info{'reads'} = '';
$dsk_info{'writes'} = '';
$dsk_info{'read_errs'} = '';
$dsk_info{'write_errs'} = '';
$dsk_info{'read_time'} = '';
$dsk_info{'write_time'} = '';
$dsk_info{'bytes_read'} = '';
$dsk_info{'bytes_written'} = '';
$dsk_info{'voting_file'} = '';
$dsk_info{'group_name'} = '';
return %dsk_info;
}
########
# NAME
# asmcmdshare_get_dsk
#
# DESCRIPTION
# This function queries the (g)v$asm_disk(_stat) tables to retrieve
# disk information based on user-specified query criteria.
#
# PARAMETERS
# dbh (IN) - initialized database handle, must be non-null.
# group (IN) - optional: disk group number to limit results by group.
# disk_pattern (IN) - optional: search pattern to limit results.
# dnum (IN) - optional: disk number to limit results.
# discovery (IN) - 1 queries from non-stat view; 0 queries from _stat view.
# global (IN) - 1 queries from the gv$ global view, 0 from v$ local view.
# missing(IN) - 1 shows missing disks; 0 does not show them.
# found_hash(OUT) - hash for missing disks. Required if missing = 1.
#
# RETURNS
# An array of hashes references. Each hash reference represents one
# row of results. Each element in the hash represents the value
# for a particular column of the view.
#
# NOTES
########
sub asmcmdshare_get_dsk
{
my ($dbh, $group, $disk_pattern, $dnum, $discovery, $global, $missing,
$found_hash) = @_;
my ($sth, $row);
my (@dsk_list); # The return array of hashes; see RETURNS above. #
my ($view);
my (@what , @from, @where, @order, @binds);
# Change all allow wild card characters to '%'.
$disk_pattern =~ s,$ASMCMDGLOBAL_WCARD_CHARS,\%,g
if (defined ($disk_pattern));
# If Oracle Database version is less than 10gR2, then always do
# discovery, because the *_stat views are not available in 10gR1.
if ( asmcmdshare_version_cmp($asmcmdglobal_hash{'ver'},
$ASMCMDGLOBAL_VER_10gR2) < 0 )
{
$discovery = 1;
}
if ($missing) # for missing disks we must use gv$asm_disk
{ # and exclude member disks
push (@from, 'gv$asm_disk');
push (@where, "(gv\$asm_disk.header_status = 'CANDIDATE'
or gv\$asm_disk.header_status = 'PROVISIONED'
or gv\$asm_disk.header_status = 'FORMER')");
}
elsif ($discovery && $global)
{
push (@from, 'gv$asm_disk');
}
elsif ($discovery && !$global)
{
push(@from, 'v$asm_disk');
}
elsif (!$discovery && $global)
{
push (@from, 'gv$asm_disk_stat');
}
else
{
push (@from, 'v$asm_disk_stat');
}
if (defined($group))
{
push (@where, 'group_number = ?');
push (@binds, [$group, SQL_INTEGER]);
}
if (defined($dnum))
{
push (@where, 'disk_number = ?');
push (@binds, [$dnum, SQL_INTEGER]);
}
if (defined($disk_pattern))
{
push (@where, 'path like ?');
push (@binds, [$disk_pattern, SQL_VARCHAR]);
}
push(@what, '*'); # select all rows
$sth = asmcmdshare_do_construct_select($dbh, \@what, \@from, \@where,
\@order, \@binds);
# Fetch results row by row and storeeach row in %dsk_info, and reference
# each %dsk_info in @dsk_list.
while (defined ($row = asmcmdshare_fetch($sth)))
{
my (%dsk_info); # Allocate fresh hash for next row. #
# Initialize elements to null strings so that they are defined and
# don't trigger Perl warnings if the fixed view does not return
# any contents for a particular column.
%dsk_info = asmcmdshare_init_disk_attr();
# Assign only if defined.
$dsk_info{'inst_id'} = $row->{'INST_ID'}
if (defined ($row->{'INST_ID'}));
$dsk_info{'group_number'} = $row->{'GROUP_NUMBER'}
if (defined ($row->{'GROUP_NUMBER'}));
$dsk_info{'disk_number'} = $row->{'DISK_NUMBER'}
if (defined ($row->{'DISK_NUMBER'}));
$dsk_info{'incarnation'} = $row->{'INCARNATION'}
if (defined ($row->{'INCARNATION'}));
$dsk_info{'mount_status'} = $row->{'MOUNT_STATUS'}
if (defined ($row->{'MOUNT_STATUS'}));
$dsk_info{'header_status'} = $row->{'HEADER_STATUS'}
if (defined ($row->{'HEADER_STATUS'}));
$dsk_info{'mode_status'} = $row->{'MODE_STATUS'}
if (defined ($row->{'MODE_STATUS'}));
$dsk_info{'state'} = $row->{'STATE'}
if (defined ($row->{'STATE'}));
$dsk_info{'redundancy'} = $row->{'REDUNDANCY'}
if (defined ($row->{'REDUNDANCY'}));
$dsk_info{'library'} = $row->{'LIBRARY'}
if (defined ($row->{'LIBRARY'}));
$dsk_info{'os_mb'} = $row->{'OS_MB'}
if (defined ($row->{'OS_MB'}));
$dsk_info{'total_mb'} = $row->{'TOTAL_MB'}
if (defined ($row->{'TOTAL_MB'}));
$dsk_info{'free_mb'} = $row->{'FREE_MB'}
if (defined ($row->{'FREE_MB'}));
$dsk_info{'name'} = $row->{'NAME'}
if (defined ($row->{'NAME'}));
$dsk_info{'failgroup'} = $row->{'FAILGROUP'}
if (defined ($row->{'FAILGROUP'}));
$dsk_info{'site_name'} = $row->{'SITE_NAME'}
if (defined ($row->{'SITE_NAME'}));
$dsk_info{'site_guid'} = $row->{'SITE_GUID'}
if (defined ($row->{'SITE_GUID'}));
$dsk_info{'site_status'} = $row->{'SITE_STATUS'}
if (defined ($row->{'SITE_STATUS'}));
$dsk_info{'label'} = $row->{'LABEL'}
if (defined ($row->{'LABEL'}));
$dsk_info{'fgroup_label'} = $row->{'FAILGROUP_LABEL'}
if (defined ($row->{'FAILGROUP_LABEL'}));
$dsk_info{'site_label'} = $row->{'SITE_LABEL'}
if (defined ($row->{'SITE_LABEL'}));
$dsk_info{'path'} = $row->{'PATH'}
if (defined ($row->{'PATH'}));
$dsk_info{'udid'} = $row->{'UDID'}
if (defined ($row->{'UDID'}));
$dsk_info{'product'} = $row->{'PRODUCT'}
if (defined ($row->{'PRODUCT'}));
$dsk_info{'create_date'} = $row->{'CREATE_DATE'}
if (defined ($row->{'CREATE_DATE'}));
$dsk_info{'mount_date'} = $row->{'MOUNT_DATE'}
if (defined ($row->{'MOUNT_DATE'}));
$dsk_info{'repair_timer'} = $row->{'REPAIR_TIMER'}
if (defined ($row->{'REPAIR_TIMER'}));
$dsk_info{'reads'} = $row->{'READS'}
if (defined ($row->{'READS'}));
$dsk_info{'writes'} = $row->{'WRITES'}
if (defined ($row->{'WRITES'}));
$dsk_info{'read_errs'} = $row->{'READ_ERRS'}
if (defined ($row->{'READ_ERRS'}));
$dsk_info{'write_errs'} = $row->{'WRITE_ERRS'}
if (defined ($row->{'WRITE_ERRS'}));
$dsk_info{'read_time'} = $row->{'READ_TIME'}
if (defined ($row->{'READ_TIME'}));
$dsk_info{'write_time'} = $row->{'WRITE_TIME'}
if (defined ($row->{'WRITE_TIME'}));
$dsk_info{'bytes_read'} = $row->{'BYTES_READ'}
if (defined ($row->{'BYTES_READ'}));
$dsk_info{'bytes_written'} = $row->{'BYTES_WRITTEN'}
if (defined ($row->{'BYTES_WRITTEN'}));
$dsk_info{'voting_file'} = $row->{'VOTING_FILE'}
if (defined ($row->{'VOTING_FILE'}));
$dsk_info{'failgroup_type'} = $row->{'FAILGROUP_TYPE'}
if (defined ($row->{'FAILGROUP_TYPE'}));
push (@dsk_list, \%dsk_info);
# If missing disk list requested, we create a hash of found disks keyed
# by instance name and path. Later (in asmcmddisk_missing) we cross
# check this using the disk list and the active instances.
if ($missing)
{
my ($key);
$key = $dsk_info{'path'} . $dsk_info{'inst_id'};
$found_hash->{$key} = 1
if (!defined($found_hash->{$key}));
}
}
asmcmdshare_finish($sth);
return (@dsk_list);
}
return 1;
OHA YOOOO