#!/bin/sh
#
#	Copyright 12/01/97 Sun Microsystems, Inc.  All Rights Reserved.
#
#ident	"@(#)utilities.sh	1.54	97/12/01 SMI"
#
# Library of shell routines for High Availability scripts.
# This is included at the top of EACH cluster monitor script.
#

loglevel()
{
    # Usage: loglevel level msg ...
    # Logs a message.  The level is in the syslog sense, and is passed
    # to the logger command.  
    LOGGER=logger
    UTIL_ha_slogtag=${HA_SLOGTAG:=hadf}
    UTIL_facility=${HA_SLOGFACILITY:=local7}
    UTIL_LEVEL=$1
    UTIL_LEVELUP="`echo $UTIL_LEVEL | tr "[a-z]" "[A-Z]"`"
    if [ "$UTIL_LEVELUP" = "ERR" ]; then
	UTIL_LEVELUP="ERROR"
    fi
    shift
    UTIL_msg="`echo \"$*\" | sed s/%/%%/g | tr '\012' ' '`"
    UTIL_msg="${UTIL_LEVELUP}: `basename $0`: $UTIL_msg"
    # The logger(1) program is written with a buffer size of 120 for
    # the non-switch arguments.  We used to be friendly to it by
    # truncating our message, roughly:
    #    MAXLOGGERARGS=110
    #    UTIL_msg="`echo $UTIL_msg | fdl_headc $MAXLOGGERARGS`"
    # However, truncating loses too much information.  Instead,
    # we now use fold(1).  Strangely, logger(1) uses a larger buffer
    # of size 200 when taking input from a file, so we use 180
    # for the fold width.  We also use sed to add a continuation
    # indicator to lines other than the first.
    UTIL_foldwidth=180
    echo "$UTIL_msg" | fold -w $UTIL_foldwidth | sed -e '2,$ s/^/'${UTIL_LEVELUP}': CONT:  /' | \
	$LOGGER -p ${UTIL_facility}.${UTIL_LEVEL} -t $UTIL_ha_slogtag
    if [ $? -ne 0 ]; then
	echo "$UTIL_ha_slogtag: $UTIL_msg" >/dev/console
	if [ $? -ne 0 ]; then
	    return 1
	fi
    fi
    return 0
}

#
# log_err_file filename     Copies the entire contents of filename to
#			    syslog as an "err" using our facility and tag.
log_err_file()
{
    if [ -z "$1" ]; then
        logerr "Source code error: log_err_file called with no argument"
	return 0
    fi
    if [ ! -s $1 ]; then
	return 0
    fi
    LOGGER=logger
    UTIL_ha_slogtag=${HA_SLOGTAG:=hadf}
    UTIL_facility=${HA_SLOGFACILITY:=local7}
    sed -e 's/^/ERROR: /' < $1 | \
	$LOGGER -p ${UTIL_facility}.err -t $UTIL_ha_slogtag
    if [ $? -ne 0 ]; then
	cat $1 > /dev/console
	if [ $? -ne 0 ]; then
	    return 1
	fi
    fi
    return 0
}

logalert()
{
    loglevel alert "$*"
}

logerr()
{
    loglevel err "$*"
}

logwarning()
{
    loglevel warning "$*"
}

logwarn()
{
    logwarning "$*"
}

lognotice()
{
    loglevel notice "$*"
}

logdeb()
{
    loglevel debug "$*"
}



runerr()
{
    # Usage: runerr cmd args ...
    # Runs cmd with args, redirecting stderr to its own file.
    # If cmd exits zero, just return 0.
    # If cmd exits non-zero, we log an error, including the contents of
    # stderr, and exit 1 from the surrounding script. 
    UTIL_RUNERR_TMPERR=/tmp/fdl_runerr.$$
    $* 2>$UTIL_RUNERR_TMPERR
    RC=$?
    if [ $RC -eq 0 ]; then
	rm -f $UTIL_RUNERR_TMPERR
	return 0;
    fi
    logerr "$* exitted non-zero ${RC}, stderr was: `cat $UTIL_RUNERR_TMPERR`"
    rm -f $UTIL_RUNERR_TMPERR
    exit 1
}


#
# Exit codes from net_diagnose_comm and friends:
#

UNSURE=1
MYNETOKAY=97
BROTHEROKAY=98
TAKEOVER=99

net_diag_status_print()
{
    # Usage: net_diag_status_print exitstatus
    NET_DIAG_STATUS_PRINT_STATUS=$1
    if [ $NET_DIAG_STATUS_PRINT_STATUS -eq $UNSURE ]; then
	echo "UNSURE"
	return 0
    elif [ $NET_DIAG_STATUS_PRINT_STATUS -eq $MYNETOKAY ]; then
	echo "MYNETOKAY"
	return 0
    elif [ $NET_DIAG_STATUS_PRINT_STATUS -eq $BROTHEROKAY ]; then
	echo "SIBLINGOKAY"
	return 0
    elif [ $NET_DIAG_STATUS_PRINT_STATUS -eq $TAKEOVER ]; then
	echo "TAKEOVER"
	return 0
    else
	echo "NET_DIAG_STATUS_UNKNOWN: ${NET_DIAG_STATUS_PRINT_STATUS}"
	return 1
    fi
}




#
# abort() -		abort error_string
#
#	Log an error message and abort.
#
abort() {
	ABORT_MSG="FATAL ERROR: $*"
	logalert "$ABORT_MSG"
        # Fork this in case attempt to open /dev/console hangs:
	( echo "$ABORT_MSG" > /dev/console ) &
	logalert "clustm abort $HA_CLUSTER this"
	clustm abort $HA_CLUSTER this
	if [ $? -ne 0 ]; then
		logalert "clustm abort command failed"
	fi

	# let failfast get us
	while true; do
		sleep 36000
	done
	exit 1
}

#
# cmstop() -		cmstop error_string
#
#	Log an error message and quit the cluster;  abort if timeout exceeded
#
cmstop() {
	UTIL_timeout=60
	logalert "STOP - $*"
	logalert "clustm stop $HA_CLUSTER this"
	clustm stop $HA_CLUSTER this
	if [ $? -ne 0 ]; then
		abort "clustm command failed"
	fi
	# We exit because callers of cmstop assume that there is no
	# return from it.  We exit 0 because exiting non-zero would
	# cause an abort if we're in a cluster transition script.
	# (The old code used to sleep here, but that was wrong,
	# because clustm stop will not interrupt a running cluster
	# transition step, that is, the stop transition step does not get 
	# invoked until the currently running cluster transition step runs
	# to completion.  So sleep just delayed the whole thing.)
	exit 0
}


#
# is_member()
# Usage: is_member element "$SET"
# The second argument should be quoted, as shown.
# Returns 0 for true, 1 for false, ala Unix programs.
#
is_member() {
	for ISM_X in $2 ; do
		if [ "$1" = "$ISM_X" ]; then
			return 0
		fi
	done
	return 1
}


#
# is_subset()
# Usage: is_subset "$FOO" "$BAR"
# The arguments should be quoted, as shown.
# The arguments may be empty lists provided that they are quoted.
# Returns 0 for true, 1 for false, ala Unix programs.
#
is_subset() {
	for ISS_X in $1 ; do
		is_member $ISS_X "$2"
		if [ $? -ne 0 ]; then
			return 1
		fi
	done
	return 0
}


#
# sets_equal()
# Usage:  sets_equal "$FOO" "$BAR"
# The arguments should be quoted, e.g., as shown.
# The arguments may be empty lists provided that they are quoted.
# Returns 0 for true, 1 for false, ala Unix programs.
#
sets_equal() {
	is_subset "$1" "$2"
	if [ $? -ne 0 ]; then
		return 1
	fi
	is_subset "$2" "$1"
	if [ $? -ne 0 ]; then
		return 1
	fi
	return 0
}


#
# set_diff()
# Usage: set_diff "$FOO" "$BAR"
# Computes the set difference: result = FOO - BAR
# The arguments should be quoted, as shown on the Usage line.
#
set_diff() {
	SETDIFF_RESULT=""
	SETDIFF_FIRST=1
	for SETDIFF_X in $1 ; do
		is_member $SETDIFF_X "$2"
		if [ $? -ne 0 ]; then
			if [ $SETDIFF_FIRST -eq 1 ]; then
				SETDIFF_RESULT="$SETDIFF_X"
				SETDIFF_FIRST=0
			else				
				SETDIFF_RESULT="$SETDIFF_RESULT $SETDIFF_X"
			fi
		fi
	done
	echo "$SETDIFF_RESULT"
	return 0
}


#
# cleanstring() -	cleanstring string
#
#	Print the string after cleaning it up for use as a parameter name.
#
cleanstring() {
	echo $* | sed 's/[^a-zA-Z0-9_]/_/g'
}

#
# count_items() -	count_items [items]
#
#	Print the number of items, or arguments
#
count_items() {
	echo $#
}

#
# getif() -		getif hostname
#
#	Print the net interface for the given hostname
#
getif() {
	UTIL_cleaned=`cleanstring $1`
	eval echo \$\{HA_IF_${UTIL_cleaned}\}
}


#
# getifunit() -		getifunit hostname
#
#	Print the net interface unit number for the given hostname
#
getifunit() {
	getif $1 | sed -n 's/.*:\(.\)$/\1/p'
}

#
# is_nativehost() -	is_nativehost [hostname]
#
#	Returns zero if the hostname is a native host.
#	Note that this works correctly when HA_ALLNATIVEHOSTS is
#	empty, i.e., it returns 1.
#
is_nativehost() {
	for UTIL_host in $HA_ALLNATIVEHOSTS
	do
		if [ "$1" = "$UTIL_host" ]; then
			return 0
		fi
	done
	return 1
}

#
# is_foreignhost() -	is_foreignhost [hostname]
#
#	Returns zero if the hostname is a foreign host.
#	Note that this works correctly when HA_ALLFOREIGNHOSTS is
#	empty, i.e., it returns 1.
#
is_foreignhost() {
	for UTIL_host in $HA_ALLFOREIGNHOSTS
	do
		if [ "$1" = "$UTIL_host" ]; then
			return 0
		fi
	done
	return 1
}

#
# is_numeric() -	is_numeric string
#
#	Returns zero is the string is numeric
#
is_numeric() {
	if [ -z "$1" ]; then
		return 1
	fi
	[ "`expr $1 : '.*'`" = "`expr $1 : '[0-9]*'`" ]
	return
}

#
# getpids() -		getpids command [command_arg]
#
#	Print all pids for "command", optionally qualified with a 
#	"command_arg".  WARNING: The implementation uses "ps -e"
#	and thus suffers from an 8 character limit on the length
#	of the command name.  Caller's should use caution.
#
getpids() {
	UTIL_command=`basename $1`
	UTIL_cmdarg=$2
	UTIL_pids="`/usr/bin/ps -e | sed -n 's/[ 	]*\([0-9]*\).*[ 	]'${UTIL_command}'$/\1/p'`"
	if [ "$UTIL_pids" -a "$UTIL_cmdarg" ]; then
		set -- $UTIL_pids
		UTIL_pids=""
		for UTIL_pid in $*; do
			/usr/bin/ps -f -p $UTIL_pid | egrep ' '$UTIL_cmdarg'( |$)' >/dev/null
			if [ $? -eq 0 ]; then
				UTIL_pids="$UTIL_pids $UTIL_pid"
			fi
		done
	fi
	echo $UTIL_pids
	return 0
}

#
# This fn originated from ds_utilities:getpids() and renamed 
# 
# getpids2() -		getpids command [command_arg]
#
#	Print all pids for "command", optionally qualified with a "command_arg".#
getpids2() 
{
        UTIL_command=`basename $1`			  # Binary basename
        UTIL_cmdarg=${2:+"`echo $2 | sed 's-/-\\\/-g'`"}  # Pre-escape /'s

	# Find instances of binary name plain or preceded by a pathname,
	# optionally followed immediately by the command-arg word.
        /usr/proc/bin/pflags /proc/* 2>/dev/null | \
                nawk 'BEGIN { FS=":" } \
/^[0-9]*:	'${UTIL_command}${UTIL_cmdarg:+" $UTIL_cmdarg"}'( |$)/ \
        { print $1 ; next } \
/^[0-9]*:	.*(\/)'${UTIL_command}${UTIL_cmdarg:+" $UTIL_cmdarg"}'( |$)/ \
        { print $1 }'
 
        return 0
}


#
# establish_cleanup_handler()
#   Establishes trap handlers for calling a cleanup() function.
#
establish_cleanup_handler()
{
    ECH_TRAPSIGNALS="1 2 3 15"
    trap "cleanup ; trap 0 ; exit 1" $ECH_TRAPSIGNALS
}



#
# prog_not_exist_err progname 
#
#   Tests whether progname exists in our path and if not issues
# an error message and returns 1.  Otherwise, returns 0.
#
prog_not_exist_err()
{
    PNE_PATH="`echo $PATH | tr ':' ' '`"
    for PNE_CAND in $PNE_PATH ; do
	if [ -x ${PNE_CAND}/$1 ]; then
	    return 0
	fi
    done
    # This handles case where argument is a full path name:
    if [ -x /$1 ]; then
	return 0
    fi
    logerr "Program $1 does not exist in PATH. Possibly a data service did not get installed properly"
    return 1
}    


#
# RPC program numbers for various demons that we want to probe.
# We list these here to avoid depending on how the name service
# is configured, or which name service is used.  Downside is
# that these program numbers could, in theory, change.  But
# that is unlikely for interoperability reasons.
#
HA_RPCPROG_rpcbind=100000;  export HA_RPCPROG_rpcbind
HA_RPCPROG_mountd=100005;  export HA_RPCPROG_mountd
HA_RPCPROG_nfsd=100003;  export HA_RPCPROG_nfsd
# Lockd is the "nlockmgr" program (n for network):
HA_RPCPROG_lockd=100021;  export HA_RPCPROG_lockd
# Statd is the "status" program ("statmon" is something else)
HA_RPCPROG_statd=100024;  export HA_RPCPROG_statd
# ypserv is used in figuring out if name service is available, for a site
# that is running NIS.
HA_RPCPROG_ypserv=100004;  export HA_RPCPROG_ypserv


#
# Last step:
# Set up debugging and stderr
#
if [ "$HA_DEBUG" = "1" ]; then
	set -x
	HA_STDERR=""
fi
if [ "$HA_STDERR" ]; then
	exec 2>$HA_STDERR
fi
