#!/bin/ksh
#
#	Copyright 02/28/00 Sun Microsystems, Inc.  All Rights Reserved.
#
# @(#)haoracle.sh	1.46 00/02/28 SMI
#
# haoracle - administrative command for HA-DBMS Oracle
#	(see  haoracle_support(4), haoracle_config(4) )
#	     verifies and updates the HA-DBMS Oracle configuration files
#
#	This utility depends on the existence of the hadfconfig file.
#
# usage: haoracle [ -s ] operation [ instance [ data ...] ]
#	-s:        silent mode
#	operation: one of list, insert, delete, update, start, stop
#	instance:  name of instance (not required for list operation)
#	data ...:  set of data fields for insert or update operation
#
# Note: Some code is somewhat duplicated in haoracle_config!

TEXTDOMAIN=haoracle; export TEXTDOMAIN
TEXTDOMAINDIR=/opt/SUNWcluster/locale;  export TEXTDOMAINDIR

# remember our name
argv0=`basename $0`

# Get the BASEDIR and PRODUCTDIR settings from the installed pkgs
_basedir_etc=`pkgparam SUNWsccf BASEDIR 2>/dev/null`
_basedir=`pkgparam SUNWscor BASEDIR`
_productdir=`pkgparam SUNWscor PRODUCTDIR`
_basedir_etc=${_basedir_etc:=""}
_basedir=${_basedir:=""}
_productdir=${_productdir:="SUNWcluster"}

# locations
HA_BIN=/opt/SUNWcluster/bin
FM_PROGS=/opt/SUNWcluster/ha/oracle
CT_PROGS=/opt/SUNWcluster/ha/oracle
ETC_CLUSTER=/etc/opt/SUNWcluster
PATH=$HA_BIN:$FM_PROGS:$CT_PROGS:/usr/bin:/usr/sbin
export PATH

if [ -f ${ETC_CLUSTER}/conf/default_clustername ]; then
	HA_CLUSTER=`cat ${ETC_CLUSTER}/conf/default_clustername`
#	echo "Assuming a default cluster name of ${HA_CLUSTER}"
else
	gettext "Cannot determine cluster name.\n"
	exit 1
fi

clustm getstate $HA_CLUSTER >/dev/null 2>&1
if [ $? -ne 0 ] ; then
	echo "`gettext 'Cluster is not up; Run scadmin {startcluster|startnode} first.'`"
	exit 1
fi
 
# Include dbms utilities
. /opt/SUNWcluster/bin/dbms_utilities

# HA environment file
HA_VAR=/var/opt/SUNWscor
HA_METASETSERVE=`haget -f mastered`
HA_NOMETASETSERVE=`haget -f not_mastered`
echo "HA_METASETSERVE=\"$HA_METASETSERVE\"" > $HA_VAR/ha_env
echo "HA_NOMETASETSERVE=\"$HA_NOMETASETSERVE\"" >> $HA_VAR/ha_env

# HA-DBMS file locations
# The data for HA_DATBASES is now in the ccd file. This is kept just
# so we can get some names later.
HA_DATABASES="${HA_FILES}/haoracle_databases"
DS="oracle"
PREFIX="SUNWcluster.ha.${DS}"

# silent mode flag
silent=""

# Oracle locations
ORATAB=/var/opt/oracle/oratab

    
# bug 1206154 - haoracle should ignore control keys
TRAPSIGNALS="1 2 3 15"

#
# not_positive_numeric() -	not_positive_numeric string
#
#	Returns 1 if the string is numeric and positive, else returns 0
#
not_positive_numeric() {
	[ -z "$1" -o "`expr 0$1 : '.*'`" != "`expr 0$1 : '[0-9]*'`" -o \
		`expr 0$1 \<= 0` != "0" ]
}


#
# not_non_neg_numeric() -	not_non_neg_numeric string
#
#	Returns 1 if the string is numeric and positive or 0, else returns 0
#
not_non_neg_numeric() {
	[ -z "$1" -o "`expr 0$1 : '.*'`" != "`expr 0$1 : '[0-9]*'`" -o \
		`expr 0$1 \< 0` != "0" ]
}

# check_oratab_line instance - find and validate entry in $ORATAB, return 
# in oratab_line
check_oratab_line() {
	oratab_line=`grep "^[	 ]*$1:" $ORATAB`
	if [ "$oratab_line" = "" ] ; then
		errlog "${PREFIX}.haoracle.4000" "instance \"$1\" not in $ORATAB"
		return 1
	else
		return 0
	fi
}

parse_line() {

        db_line=$1
 
        # fields are separated by TABs, cut them apart
 
        mode=`echo "$db_line" | cut -s -f 1`
        instance=`echo "$db_line" | cut -s -f 2`
        logical_host=`echo "$db_line" | cut -s -f 3`
        poll_cycle=`echo "$db_line" | cut -s -f 4`
        connect_cycle=`echo "$db_line" | cut -s -f 5`
        timeout=`echo "$db_line" | cut -s -f 6`
        restart_delay=`echo "$db_line" | cut -s -f 7`
        db_login=`echo "$db_line" | cut -s -f 8`
        init_ora=`echo "$db_line" | cut -s -f 9`
        listener_name=`echo "$db_line" | cut -s -f 10`
 
}
		
#process_parm_file - process an Oracle parameter file $1, follow ifile
#references, and echo the value for background_dump_dest
process_parm_file() {
        typeset dump_line

        if [ -r "$1" ] ; then
		# the following [ ]'s contain one space and on tab
		line=`grep -i '^[ 	]*background_dump_dest[ 	]*=' $1`
		if [ "$line" != "" ] ; then
			# found
			set -A dump_line $(print ${line} | tr '=' ' ')
			echo ${dump_line[1]}
		else
			# check include files
			ifile_list=`grep -i '^[ 	]*ifile[ 	]*=' $1 | cut -d= -f 2`
			for ifile in $ifile_list ; do
				process_parm_file $ifile
			done
		fi
	else
		logerr "${prog}.4047"\
		 "Oracle parameter file $1 does not exist or is not readable!"
		echo ""
	fi
}



# hadb_validate_entry  - sanity check of haoracle_databases file
hadb_validate_entry() {
	typeset dumpdest_file

	have_error=0
	parse_line "$1"

        associated_hosts=`hareg -q ${DS} -H | tr '\012' ' '`
        is_member "$logical_host" "$associated_hosts"
        if [ $? -ne 0 ]; then
		log "`gettext 'Logical Host \"%s\" is not associated with service \"%s\" or not configured.'`" "$logical_host" "${DS}"
		log "`gettext 'Valid logical hostnames: \"%s\".'`" "$associated_hosts"
		have_error=1
        fi

	if not_positive_numeric $poll_cycle ; then
		log "`gettext 'Poll cycle time \"%s\" should be a numeric value greater than 0'`" "$poll_cycle"
		have_error=1
	fi

	if not_non_neg_numeric $connect_cycle ; then
		log "`gettext 'Connect cycle count \"%s\" should be a numeric value greater than or equal to 0'`" "$connect_cycle"
		have_error=1
	fi

	if not_non_neg_numeric $timeout ; then
		log "`gettext 'Time out \"%s\" should be a numeric value greater than or equal to 0'`" "$timeout"
		have_error=1
	fi
      
	if not_non_neg_numeric $restart_delay ; then
		log "`gettext 'Restart delay \"%s\" should be a numeric value greater than or equal to 0'`" "$restart_delay"
		have_error=1
	fi

	if [ `expr "//$db_login" : "//..*/"` = "0" -a $db_login != "/" ] ; then
		log "`gettext 'format of database login \"%s\" should be username/password or /'`" "$db_login"
		have_error=1
	fi
#
# Only check the init_ora file if we are the native host.
#
	if print ${HA_NATIVEHOST[*]} | /bin/egrep "$logical_host" >/dev/null 2>&1; then
		dumpdest_file=`process_parm_file $init_ora`
		if [ "$dumpdest_file" = "" ] ; then
			log "`gettext 'cannot locate background_dump_dest in %s'`" "$init_ora"
			have_error=1
		fi
	fi

    return $have_error
}


#
# haoracle_list filename - list the contents of the data file, skipping
#	comment lines
haoracle_list() {
	get_all ${DS}
}


# haoracle_insert filename instance_name logical_host poll_time connect_cyle timeout \
#	restart_delay db_login [ init_ora ]
# insert a record into filename 
#  
haoracle_insert() {
	instance_name=$1 ; shift
        if [ $# -lt 7 ] ; then
		log "`gettext 'parameters required for insert are: instance host probe_cycle_time connect_cycle_count time_out restart_delay username/password parameter_file [listener_name]'`"
		usage
	fi

	# make sure line for $instance_name doesn't already exists in file
	line=$(get_instance "${DS}" "${instance_name}")
	if [ "$line" != "" ] ; then
		log "`gettext 'instance \"%s\" already in %s:'`" "$instance_name" "$BASEFILE"
		log "--> $line"
		exit 1
	fi	

	# build the line for the new entry
	new_line="off	$instance_name	$1	$2	$3	$4	$5	$6	$7	$8"

	parse_line "$new_line"
	get_hosts_and_links "$logical_host"
	hadb_validate_entry "$new_line"
	if [ $? -ne 0 ]; then
		abort "${PREFIX}.haoracle.4010" "`gettext 'new haoracle instance line failed sanity check'`"
	fi

	#check the instance name in ORATAB file
	check_oratab_line "$instance_name" 
	if [ $? -ne 0 ]; then
		abort "${PREFIX}.haoracle.4020" "`gettext 'oratab file failed sanity check'`"
	fi
	if [ -z $listener_name ]; then
		log "`gettext 'Listener name not specified, using default listener name'`"
	fi
	add_unique_ha_instance "${DS}" "$new_line"
        [ $? -ne 0 ] && abort "${PREFIX}.haoracle.4330" \
 "`gettext 'Could not add entry to CCD for %s. Retry the operation.'`" "$instance_name"

}


# haoracle_delete filename instance_name
# delete a record for $instance_name from $filename
haoracle_delete() {
	instance_name=$1; shift
	inst_mode=$1

	line=$(get_instance "${DS}" "${instance_name}")
	if [ "$line" = "" ] ; then
		abort "${PREFIX}.haoracle.4030" "`gettext 'no entry for instance %s in %s - cannot delete'`" "$instance_name" "$BASEFILE"
	fi

	# reject if fault monitor still running, only if not stopping
	if [[ "$inst_mode" != "stop" ]]; then
		mode=`echo "$line" | cut -s -f 1`
		if [ "$mode" = "on" ] ; then
			abort "${PREFIX}.haoracle.4040" "`gettext 'cannot delete entry; stop %s fault monitor first'`" "$instance_name"
		fi
	fi

	# delete all entries with $1 in the second column from the file
	remove oracle instancename $instance_name
	[ $? -ne 0 ] && abort "${PREFIX}.haoracle.4050" "`gettext '%s could not be modified! Retry the operation.'`" "$instance_name"
}


###################################
# update an existing record in the data file 
haoracle_update() {
	instance_name=$1 ; shift
        if [ $# -lt 7 ] ; then
		log "`gettext 'parameters required for update are: instance host probe_cycle_time connect_cycle_count time_out restart_delay username/password parameter_file [listener_name]'`"
		usage
	fi

	line=$(get_instance "${DS}" "${instance_name}")
	if [ "$line" = "" ] ; then
		abort "${PREFIX}.haoracle.4300" "`gettext 'no entry for instance %s in %s - cannot update'`" "$instance_name" "$BASEFILE"
	fi
	
	old_mode=`echo "$line" | cut -s -f 1`
        if [ "$old_mode" = "on" ] ; then
            abort "${PREFIX}.haoracle.4040" "`gettext 'cannot update entry; stop %s fault monitor first'`" "$instance_name"
        fi

	new_line="$old_mode	$instance_name	$1	$2	$3	$4	$5	$6	$7	$8"

	hadb_validate_entry "$new_line"
	if [ $? -ne 0 ]; then
		abort "${PREFIX}.haoracle.4310" "`gettext 'new haoracle instance line failed sanity check'`"
	fi
	##    
	#check the instance name in ORATAB file
	check_oratab_line "$instance_name" 
	if [ $? -ne 0 ]; then
		abort "${PREFIX}.haoracle.4320" "`gettext 'oratab file failed sanity check'`"
	fi
        if [ -z $listener_name ]; then
                log "`gettext 'Listener name not specified, using default listener name'`"
        fi

	# delete the line with $instance_name in the second column from the
	# file, and then insert $new_line in its place (replacing the line)
	haoracle_delete $instance_name ""
	add_unique_ha_instance "${DS}" "$new_line"
	[ $? -ne 0 ] && abort "${PREFIX}.haoracle.4330" "`gettext '%s could not be modified!'`" "$filename"
	return 0
}


# haoracle_start_stop_run filename instance ( start | stop )
# starts/stops fault monitoring for $instance
# does not update $filename - see function haoracle_start_stop
haoracle_start_stop_run() {

	typeset return_value
	typeset -i i

	lhost=$(get_instance "${DS}" "$1" | awk '{print $3}')
	if [[ -z "$lhost" ]] ; then
		abort "${PREFIX}.haoracle.4400" \
		"`gettext 'no entry for instance %s in HA ORACLE CCD - cannot %s'`" "$1" "$2"
	fi
	set -A hostarray $(get_logical_links "${lhost}")
 
	# call start/stop locally
	#log "$2 fault monitor for $1 on localhost..."
	HA_METASETSERVE=localhost; export HA_METASETSERVE
	# Set no delay since we are manually starting fmon
        HA_FM_DBMSPROBE_DELAY=0; export HA_FM_DBMSPROBE_DELAY

	${HA_BIN}/ha_dbms_call localhost $1 $2 oracle
	return_value=$?

	# call start remotely only if the local ha_dbms_call succeeded.
	(($return_value == 0)) && \
	if (( ${#hostarray[*]} > 0 )) ; then
		i=0
		while (( $i < ${#hostarray[*]} ))
		do
			#log "$2 fault monitor for $1 on ${hostarray[i]} ... "
			(( i=i+1 ))
			${HA_BIN}/ha_dbms_call ${hostarray[i]} $1 $2 oracle
			(( i=i+1 ))
			if [ "$?" != 0 ]; then
				log "`gettext 'Failed to %s fault monitor for %s on %s'`" "$2" "$1" "${hostarray[i]}"
			fi
		done
	else
		log "`gettext 'No %s of fault monitor for %s on any remote host.'`" "$2" "$1"
	fi

	return $return_value
}

# haoracle_start_stop filename instance ( start | stop )
# changes the on/off status for $instance in $filename
# does not actually start or stop monitoring - see haoracle_start_stop_run
haoracle_start_stop() {
	# find the instance
	line=$(get_instance_dynamic "${DS}" "${1}")
	if [ "$line" = "" ] ; then
		abort "${PREFIX}.haoracle.4200" "`gettext 'no entry for instance %s in %s - cannot %s'`" "$1" "$BASEFILE" "$2"
	fi
	mode=`echo "$line" | cut -s -f 1`
	if [ "$2" = "start" ] ; then
		if [ "$mode" != "off" ] ; then
			abort "${PREFIX}.haoracle.4210" "`gettext 'instance %s is not stopped in %s!'`" "$1" "$BASEFILE"
		fi

                # check the entry for the specific instance 
                hadb_validate_entry "$line"
                if [ $? -ne 0 ]; then
			abort "${PREFIX}.haoracle.4220" "`gettext 'haoracle instance entry failed sanity check'`"
		fi

		#check the instance name in ORATAB file
		check_oratab_line $1 
		if [ $? -ne 0 ]; then
			abort "${PREFIX}.haoracle.4230" "`gettext 'oratab file failed sanity check'`"
		fi
	else
		if [ "$mode" != "on" ] ; then
			abort "${PREFIX}.haoracle.4240" "`gettext 'instance %s is not started in %s!'`" "$1" "$BASEFILE"
		fi
	fi
	
	# update the entry in the file - find the old entry, and reply
	# off with on and vice versa
	if [ "$2" = "start" ] ; then
		new_line=`echo "$line" | sed 's/^off/on/'`
	else
		new_line=`echo "$line" | sed 's/^on/off/'`
	fi
	haoracle_delete $1 $2
	add_unique_ha_instance "${DS}" "$new_line"
	if [ $? -ne 0 ] ; then
		abort "${PREFIX}.haoracle.4250" "`gettext '%s could not be modified!'`" "$filename"
	fi
	return 0
}

get_hosts_and_links() {

logical_host_name="$1"

set -A nativehosts $(haget -f mastered | tr '\012' ' ')
set -A foreignhosts $(haget -f not_mastered | tr '\012' ' ')

for i in ${nativehosts[*]}
do
        if [[ "$i" == "$logical_host_name" ]]; then
                HA_NATIVEHOST="$i"
                break
        fi
done
for i in ${foreignhosts[*]}
do
        if [[ "$i" == "$logical_host_name" ]]; then
                HA_FOREIGNHOST="$i"
                break
        fi
done

if [[ -n "$HA_NATIVEHOST" ]]; then
	res=`haget -f physical_hosts -h $HA_NATIVEHOST`
elif [[ -n "$HA_FOREIGNHOST" ]]; then
	res=`haget -f physical_hosts -h $HA_FOREIGNHOST`
else
	abort "${PREFIX}.haoracle.4140" "`gettext 'Cannot determine host members.'`"
fi

HA_NATIVEHOST="${nativehosts}"
HA_FOREIGNHOST="${foreignhosts}"
ASYMLOGICALHOST="${nativehosts} ${foreignhosts}"

}



# ignore signals
catchsig()
{
	echo "Caught signal; continuing."
}


#
# get_yes_or_no - prompt user for "y" or "n", and read answer. repeat if
#	answer is not "y" or "n". Returns the choice.
#	In silent mode, just returns "y"
#
get_yes_or_no()
{
	if [ "$silent" = "y" ] ; then
		resp="y"
	else
		read resp
		while [ "$resp" != "y" -a "$resp" != "n" ]
		do
			echo
			echo '    Please respond "y" or "n" \c'
			read resp
		done
	fi
}


# log ... - if not silent, echo all parameters
log() {
	if [ "$silent" != "y" ] ; then
		format=$1
		shift
		printf "$format\n" $*
	fi
}
# infolog ... - if not silent, echo all parameters
infolog() {
	typeset prefix

	prefix=$1
	shift
	[ "$silent" != "y" ] && log_info $prefix "$*"
}

# errlog - echo all parameters
errlog() {
	typeset prefix

	prefix=$1
	shift
	log_error $prefix "$*"
}

# abort ... - if not silent, echo all parameters. Then exit with status of 1
abort() {
	typeset prefix

	# [ "$silent" != "y" ] && print $prefix "$*"
	if [ "$silent" != "y" ] ; then
		prefix=$1
		shift
		format=$1
		shift
		# echo "$prefix  \c"
		printf "$format\n" $*
	fi
	exit 1
}

# usage - print command usage ################################################
usage() {
	log "`gettext 'usage: %s [ -s ] operation [ instance [ data ...] ]'`" "$argv0"
	log "`gettext '  -s:        silent mode'`"
        log "`gettext '  operation: one of list, insert, delete, update, start, stop'`"
	log "`gettext '  instance:  name of instance (not required for list operation)'`"
        log "`gettext '  data ...:  set of data fields for insert or update operation'`"
	exit 2
}


# check who we are - if we are not root, exit immediately
res=`id`
[ $? -ne 0 ] && abort "${PREFIX}.haoracle.4060" "`gettext 'Cannot execute id command'`"
[ `expr "$res" : "uid=0(root)"` != 11 ] && abort "${PREFIX}.haoracle.4070" "`gettext '%s must be executed as root.'`" "$argv0"

# check for -s and -f parameters
while getopts s c
do
	case $c in
		s) silent="y" ;;
		\?) usage ;;
	esac
done
shift `expr $OPTIND - 1`

export HA_DATABASES


# make sure we have a command
if [ $# -lt 1 ] ; then
	log "`gettext 'missing operation'`"
	usage
fi
command="$1"
shift
case "$command" in 
#	check|list)			 will_modify=0 ;;
	list)				 will_modify=0 ;;
	start|stop|insert|delete|update) will_modify=1 ;;
	*)				 log "`gettext 'invalid operation'`"
					 usage ;;
esac


# make sure HA is up
if [ $will_modify -eq 1 ]; then
        CMSTATE="`clustm getstate $HA_CLUSTER 2> /dev/null`"
        #make sure the host is not in reconfiguring
        if [ "$CMSTATE" != "end" ]; then
                abort "${PREFIX}.haoracle.4110" "`gettext 'This Sun Cluster is currently reconfiguring: haoracle command not allowed'`" 
        fi
fi

# make sure Oracle service is registered and the state is on
if [ $will_modify -eq 1 ]; then
	reg=`hareg | grep oracle`
else
	reg=''
fi
if [ -z "$reg"  -a $will_modify -eq 1 ] ;  then
	errlog "${PREFIX}.haoracle.4115" "Sun Clusters HA-DBMS for ORACLE service is not registered."
	abort "${PREFIX}.haoracle.4120" "`gettext 'You may run \"hareg -s -r oracle\" to register it.'`"
fi

reg_state=`echo "$reg" | cut -f2 -s`
if [ "$reg_state" != "on" -a $will_modify -eq 1 ] ; then
	errlog "${PREFIX}.haoracle.4125" "The state of Sun Clusters HA-DBMS for ORACLE service is off."
	abort "${PREFIX}.haoracle.4130" "`gettext 'You may run \"hareg -y oracle\" to turn the service on.'`"
fi

# verify that we have instance name, and can update ${HA_DATABASES}
if [ "$command" != "list" -a "$command" != "check" ] ; then
	if [ $# -lt 1 ] ; then
		log "`gettext 'instance name missing'`"
		usage
	fi
	instance=$1
	shift
fi

parms=$*

#BASEFILE=`basename ${HA_DATABASES}`
BASEFILE="CCD"
export BASEFILE


HA_LOCALHOST=`uname -n`

trap "catchsig" $TRAPSIGNALS

# run the appropriate function
case "$command" in
	list)       haoracle_list ;;
	insert)     haoracle_insert $instance $parms ;;
	delete)	    haoracle_delete $instance $command ;;
	update)     haoracle_update $instance $parms ;;
	start|stop) haoracle_start_stop $instance $command 
		    haoracle_start_stop_run $instance $command ;;
	*)	    usage ;; # really already checked further up
esac

exit 0
