#
#
# 	Common helper functions for the OCF Resource Agents supplied by
# 	heartbeat.
#
# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée
#                    All Rights Reserved.
#
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# 

# Build version: $Format:%H$

# TODO: Some of this should probably split out into a generic OCF
# library for shell scripts, but for the time being, we'll just use it
# ourselves...
#

# TODO wish-list:
# - Generic function for evaluating version numbers
# - Generic function(s) to extract stuff from our own meta-data
# - Logging function which automatically adds resource identifier etc
#   prefixes
# TODO: Move more common functionality for OCF RAs here.
#

# This was common throughout all legacy Heartbeat agents
unset LC_ALL; export LC_ALL
unset LANGUAGE; export LANGUAGE

__SCRIPT_NAME=`basename $0`

if [ -z "$OCF_ROOT" ]; then
    : ${OCF_ROOT=/usr/lib/ocf}
fi

if [ "$OCF_FUNCTIONS_DIR" = ${OCF_ROOT}/resource.d/heartbeat ]; then  # old
	unset OCF_FUNCTIONS_DIR
fi

: ${OCF_FUNCTIONS_DIR:=${OCF_ROOT}/lib/heartbeat}

. ${OCF_FUNCTIONS_DIR}/ocf-binaries
. ${OCF_FUNCTIONS_DIR}/ocf-returncodes
. ${OCF_FUNCTIONS_DIR}/ocf-directories
. ${OCF_FUNCTIONS_DIR}/ocf-rarun

# Define OCF_RESKEY_CRM_meta_interval in case it isn't already set,
# to make sure that ocf_is_probe() always works
: ${OCF_RESKEY_CRM_meta_interval=0}

ocf_is_root() {
	if [ X`id -u` = X0 ]; then
		true
	else
		false
	fi
}

ocf_maybe_random() {
	local rnd="$RANDOM"
	# Something sane-ish in case a shell doesn't support $RANDOM
	[ -n "$rnd" ] || rnd=$$
	echo $rnd
}

# Portability comments:
# o The following rely on Bourne "sh" pattern-matching, which is usually
#   that for filename generation (note: not regexp).
# o The "*) true ;;" clause is probably unnecessary, but is included
#   here for completeness.
# o The negation in the pattern uses "!".  This seems to be common
#   across many OSes (whereas the alternative "^" fails on some).
# o If an OS is encountered where this negation fails, then a possible
#   alternative would be to replace the function contents by (e.g.):
#	[ -z "`echo $1 | tr -d '[0-9]'`" ]
#
ocf_is_decimal() {
	case "$1" in
	""|*[!0-9]*)	# empty, or at least one non-decimal
		false ;;
	*)
		true ;;
	esac
}

ocf_is_true() {
	case "$1" in
	yes|true|1|YES|TRUE|ja|on|ON) true ;;
	*)	false ;;
	esac
}

ocf_is_hex() {
	case "$1" in
        ""|*[!0-9a-fA-F]*)	# empty, or at least one non-hex
		false ;;
	*)
		true ;;
	esac
}

ocf_is_octal() {
	case "$1" in
        ""|*[!0-7]*)	# empty, or at least one non-octal
		false ;;
	*)
		true ;;
	esac
}

__ocf_set_defaults() {
	__OCF_ACTION="$1"

	# Return to sanity for the agents...
	unset LANG
	LC_ALL=C
	export LC_ALL

	# TODO: Review whether we really should source this. Or rewrite
	# to match some emerging helper function syntax...? This imports
	# things which no OCF RA should be using...

	# Strip the OCF_RESKEY_ prefix from this particular parameter
	if [ -z "$OCF_RESKEY_OCF_CHECK_LEVEL" ]; then
		: ${OCF_CHECK_LEVEL:=0}
	else
		: ${OCF_CHECK_LEVEL:=$OCF_RESKEY_OCF_CHECK_LEVEL}
	fi

	if [ ! -d "$OCF_ROOT" ]; then
		ha_log "ERROR: OCF_ROOT points to non-directory $OCF_ROOT."
		exit $OCF_ERR_GENERIC
	fi

	if [ -z "$OCF_RESOURCE_TYPE" ]; then
		: ${OCF_RESOURCE_TYPE:=$__SCRIPT_NAME}
	fi

	if [ -z "$OCF_RA_VERSION_MAJOR" ]; then
		: We are being invoked as an init script.
		: Fill in some things with reasonable values.
		: ${OCF_RESOURCE_INSTANCE:="default"}
		return 0
        fi

	if [ "x$__OCF_ACTION" = "xmeta-data" ]; then
		OCF_RESOURCE_INSTANCE="undef"
	fi	

	if [ -z "$OCF_RESOURCE_INSTANCE" ]; then
		ha_log "ERROR: Need to tell us our resource instance name."
		exit $OCF_ERR_ARGS
	fi
}

hadate() {
  date "+${HA_DATEFMT}"
}

set_logtag() {
	if [ -z "$HA_LOGTAG" ]; then
		if [ -n "$OCF_RESOURCE_INSTANCE" ]; then
			HA_LOGTAG="$__SCRIPT_NAME($OCF_RESOURCE_INSTANCE)[$$]"
		else
			HA_LOGTAG="$__SCRIPT_NAME[$$]"
		fi
	fi
}

ha_log() {
	local loglevel
	[ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=""
	# if we're connected to a tty, then output to stderr
	if tty >/dev/null; then
		if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then
			return 0
		fi
		if [ "$HA_LOGTAG" ]; then
			echo "$HA_LOGTAG: $*"
		else
			echo "$*"
		fi >&2
		return 0
	fi

	set_logtag

	if [ "x${HA_LOGD}" = "xyes" ] ; then 
		ha_logger -t "${HA_LOGTAG}" "$@"
		if [ "$?" -eq "0" ] ; then
			return 0
		fi
	fi

	if
	  [ -n "$HA_LOGFACILITY" ]
        then
	  : logging through syslog
	  # loglevel is unknown, use 'notice' for now
          loglevel=notice
          case "${*}" in
            *ERROR*)		loglevel=err;;
            *WARN*)		loglevel=warning;;
            *INFO*|info)	loglevel=info;;
	  esac
	  logger -t "$HA_LOGTAG" -p ${HA_LOGFACILITY}.${loglevel} "${*}"
        fi	
        if
	  [ -n "$HA_LOGFILE" ]
	then
	  : appending to $HA_LOGFILE
	  echo "$HA_LOGTAG:	"`hadate`"${*}" >> $HA_LOGFILE
	fi
	if
	  [ -z "$HA_LOGFACILITY" -a -z "$HA_LOGFILE" ]
	then
	  : appending to stderr
	  echo `hadate`"${*}" >&2
	fi
        if
          [ -n "$HA_DEBUGLOG" ]
        then
          : appending to $HA_DEBUGLOG
		  if [ "$HA_LOGFILE"x != "$HA_DEBUGLOG"x ]; then
            echo "$HA_LOGTAG:	"`hadate`"${*}" >> $HA_DEBUGLOG
          fi
        fi
}

ha_debug() {

        if [ "x${HA_debug}" = "x0" ] ; then
                return 0
        fi
	if tty >/dev/null; then
		if [ "$HA_LOGTAG" ]; then
			echo "$HA_LOGTAG: $*"
		else
			echo "$*"
		fi >&2
		return 0
	fi

	set_logtag

        if [ "x${HA_LOGD}" = "xyes" ] ; then  
		ha_logger -t "${HA_LOGTAG}" -D "ha-debug" "$@"
                if [ "$?" -eq "0" ] ; then
                        return 0
                fi
        fi

	[ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=""

	if
	  [ -n "$HA_LOGFACILITY" ]
	then
	  : logging through syslog
	  logger -t "$HA_LOGTAG" -p "${HA_LOGFACILITY}.debug" "${*}"
	fi
        if
	  [ -n "$HA_DEBUGLOG" ]
	then
	  : appending to $HA_DEBUGLOG
	  echo "$HA_LOGTAG:	"`hadate`"${*}" >> $HA_DEBUGLOG
	fi
	if
	  [ -z "$HA_LOGFACILITY" -a -z "$HA_DEBUGLOG" ]
	then
	  : appending to stderr
	  echo "$HA_LOGTAG:	`hadate`${*}:	${HA_LOGFACILITY}" >&2
	fi
}

ha_parameter() {
	local VALUE
    VALUE=`sed -e 's%[	][	]*% %' -e 's%^ %%' -e 's%#.*%%'   $HA_CF | grep -i "^$1 " | sed 's%[^ ]* %%'`
    if
	[ "X$VALUE" = X ]
    then
	
	case $1 in
	    keepalive)	VALUE=2;;
	    deadtime)
		ka=`ha_parameter keepalive`
		VALUE=`expr $ka '*' 2 '+' 1`;;
	esac
    fi
    echo $VALUE
}

ocf_log() {
	# TODO: Revisit and implement internally.
	if
          [ $# -lt 2 ]
        then
          ocf_log err "Not enough arguments [$#] to ocf_log."
        fi
        __OCF_PRIO="$1"
        shift
        __OCF_MSG="$*"

        case "${__OCF_PRIO}" in
          crit)	__OCF_PRIO="CRIT";;
          err)	__OCF_PRIO="ERROR";;
          warn)	__OCF_PRIO="WARNING";;
          info)	__OCF_PRIO="INFO";;
          debug)__OCF_PRIO="DEBUG";;
          *)	__OCF_PRIO=`echo ${__OCF_PRIO}| tr '[a-z]' '[A-Z]'`;;
	esac

	if [ "${__OCF_PRIO}" = "DEBUG" ]; then
		ha_debug "${__OCF_PRIO}: $__OCF_MSG"
	else
		ha_log "${__OCF_PRIO}: $__OCF_MSG"
	fi
}

#
# ocf_deprecated: Log a deprecation warning
# Usage:          ocf_deprecated [param-name]
# Arguments:      param-name optional, name of a boolean resource
#                            parameter that can be used to suppress
#                            the warning (default
#                            "ignore_deprecation")
ocf_deprecated() {
    local param
    param=${1:-ignore_deprecation}
    # don't use ${!param} here, it's a bashism
    if ! ocf_is_true $(eval echo \$OCF_RESKEY_$param); then
	ocf_log warn "This resource agent is deprecated" \
	    "and may be removed in a future release." \
	    "See the man page for details." \
	    "To suppress this warning, set the \"${param}\"" \
	    "resource parameter to true."
    fi
}

#
# Ocf_run: Run a script, and log its output.
# Usage:   ocf_run [-q] [-info|-warn|-err] <command>
#	-q: don't log the output of the command if it succeeds
#	-info|-warn|-err: log the output of the command at given
#		severity if it fails (defaults to err)
#
ocf_run() {
	local rc
	local output
	local verbose=1
	local loglevel=err
	local var

	for var in 1 2
	do
	    case "$1" in
		"-q")
		    verbose=""
		    shift 1;;
		"-info"|"-warn"|"-err")
		    loglevel=`echo $1 | sed -e s/-//g`
		    shift 1;;
		*)
		    ;;		
	    esac
	done

	output=`"$@" 2>&1`
	rc=$?
	output=`echo $output`
	if [ $rc -eq 0 ]; then 
	    if [ "$verbose" -a ! -z "$output" ]; then
		ocf_log info "$output"
	    fi
	    return $OCF_SUCCESS
	else
	    if [ ! -z "$output" ]; then
		ocf_log $loglevel "$output"
	    else
		ocf_log $loglevel "command failed: $*"
	    fi
	    return $rc
	fi
}

ocf_pidfile_status() {
    local pid pidfile=$1
    if [ ! -e $pidfile ]; then
	# Not exists
	return 2
    fi
    pid=`cat $pidfile`
    kill -0 $pid 2>&1 > /dev/null
    if [ $? = 0 ]; then
	return 0
    fi

    # Stale
    return 1
}

ocf_take_lock() {
    local lockfile=$1
    local rnd=$(ocf_maybe_random)

    sleep 0.$rnd
    while 
	ocf_pidfile_status $lockfile
    do
	ocf_log info "Sleeping until $lockfile is released..."
	sleep 0.$rnd
    done
    echo $$ > $lockfile
}


ocf_release_lock_on_exit() {
    local lockfile=$1
    trap "rm -f $lockfile" EXIT
}

# returns true if the CRM is currently running a probe. A probe is
# defined as a monitor operation with a monitoring interval of zero.
ocf_is_probe() {
    [ "$__OCF_ACTION" = "monitor" -a "$OCF_RESKEY_CRM_meta_interval" = 0 ]
}

# returns true if the resource is configured as a clone. This is
# defined as a resource where the clone-max meta attribute is present,
# and set to greater than zero.
ocf_is_clone() {
    [ ! -z "${OCF_RESKEY_CRM_meta_clone_max}" ] && [ "${OCF_RESKEY_CRM_meta_clone_max}" -gt 0 ]
}

# returns true if the resource is configured as a multistate
# (master/slave) resource. This is defined as a resource where the
# master-max meta attribute is present, and set to greater than zero.
ocf_is_ms() {
    [ ! -z "${OCF_RESKEY_CRM_meta_master_max}" ] && [ "${OCF_RESKEY_CRM_meta_master_max}" -gt 0 ]
}

# version check functions
# allow . and - to delimit version numbers
# max version number is 999
# letters and such are effectively ignored
#
ocf_is_ver() {
	echo $1 | grep '^[0-9][0-9.-]*[0-9]$' >/dev/null 2>&1
}
ocf_ver2num() {
	echo $1 | awk -F'[.-]' '
	{for(i=1; i<=NF; i++) s=s*1000+$i; print s}
	'
}
ocf_ver_level(){
	echo $1 | awk -F'[.-]' '{print NF}'
}
ocf_ver_complete_level(){
	local ver="$1"
	local level="$2"
	local i=0
	while [ $i -lt $level ]; do
		ver=${ver}.0
		i=`expr $i + 1`
	done
	echo $ver
}

# usage: ocf_version_cmp VER1 VER2
#     version strings can contain digits, dots, and dashes
#     must start and end with a digit
# returns:
#     0: VER1 smaller (older) than VER2
#     1: versions equal
#     2: VER1 greater (newer) than VER2
#     3: bad format
ocf_version_cmp() {
	ocf_is_ver "$1" || return 3
	ocf_is_ver "$2" || return 3
	local v1=$1
	local v2=$2
	local v1_level=`ocf_ver_level $v1`
	local v2_level=`ocf_ver_level $v2`
	local level_diff
	if [ $v1_level -lt $v2_level ]; then
		level_diff=`expr $v2_level - $v1_level`
		v1=`ocf_ver_complete_level $v1 $level_diff`
	elif [ $v1_level -gt $v2_level ]; then
		level_diff=`expr $v1_level - $v2_level`
		v2=`ocf_ver_complete_level $v2 $level_diff`
	fi
	v1=`ocf_ver2num $v1`
	v2=`ocf_ver2num $v2`
	if [ $v1 -eq $v2 ]; then
		return 1
	elif [ $v1 -lt $v2 ]; then
		return 0
	else
		return 2 # -1 would look funny in shell ;-)
	fi
}

ocf_local_nodename() {
	# use crm_node -n for pacemaker > 1.1.8
	which pacemakerd > /dev/null 2>&1
	if [ $? -eq 0 ]; then
		local version=$(pacemakerd -$ | grep "Pacemaker .*" | awk '{ print $2 }')
		ocf_version_cmp "$version" "1.1.8"
		if [ $? -eq 2 ]; then
			which crm_node > /dev/null 2>&1
			if [ $? -eq 0 ]; then
				crm_node -n
				return
			fi
		fi
	fi

	# otherwise use uname -n
	uname -n
}

# usage: dirname DIR
dirname()
{
	local a
	local b

	[ $# = 1 ] || return 1
	a="$1"
	while [ 1 ]; do
		b="${a%/}"
		[ "$a" = "$b" ] && break
		a="$b"
	done
	b=${a%/*}
	[ -z "$b" -o "$a" = "$b"  ] && b="."

	echo "$b"
	return 0
}

#
# pseudo_resource status tracking function...
#
# This allows pseudo resources to give correct status information.  As we add
# resource monitoring, and better resource tracking in general, this will
# become essential.
#
# These scripts work because ${HA_RSCTMP} is cleaned out every time
# heartbeat is started.
#
# We create "resource-string" tracking files under ${HA_RSCTMP} in a
# very simple way:
#
#	Existence of "${HA_RSCTMP}/resource-string" means that we consider
#	the resource named by "resource-string" to be running.
#
# Note that "resource-string" needs to be unique.  Using the resource type
# plus the resource instance arguments to make up the resource string
# is probably sufficient...
#
# usage: ha_pseudo_resource resource-string op [tracking_file]
# 	where op is {start|stop|monitor|status|restart|reload|print}
#	print is a special op which just prints the tracking file location
#	user can override our choice of the tracking file location by
#		specifying it as the third arg
#	Note that all operations are silent...
#
ha_pseudo_resource()
{
  local ha_resource_tracking_file="${3:-${HA_RSCTMP}/$1}"
  case $2 in
    start|restart|reload)  touch "$ha_resource_tracking_file";;
    stop) rm -f "$ha_resource_tracking_file";;
    status|monitor)
           if
             [ -f "$ha_resource_tracking_file" ]
           then
             return 0
           else
             case $2 in
               status)	return 3;;
               *)	return 7;;
             esac
           fi;;
    print)  echo "$ha_resource_tracking_file";;
    *)	return 3;;
  esac
}

# usage: rmtempdir TMPDIR
rmtempdir()
{
	[ $# = 1 ] || return 1
	if [ -e "$1" ]; then
		rmdir "$1" || return 1
	fi
	return 0
}

# usage: maketempfile [-d]
maketempfile()
{
	if [ $# = 1 -a "$1" = "-d" ]; then
		mktemp -d
		return -0
	elif [ $# != 0 ]; then
		return 1
	fi

	mktemp
	return 0
}

# usage: rmtempfile TMPFILE
rmtempfile ()
{
	[ $# = 1 ] || return 1
	if [ -e "$1" ]; then
		rm "$1" || return 1
	fi
	return 0
}

# echo the first lower supported check level
# pass set of levels supported by the agent
# (in increasing order, 0 is optional)
ocf_check_level()
{
	local lvl prev
	lvl=0
	prev=0
	if ocf_is_decimal "$OCF_CHECK_LEVEL"; then
		# the level list should be very short
		for lvl; do
			if [ "$lvl" -eq "$OCF_CHECK_LEVEL" ]; then
				break
			elif [ "$lvl" -gt "$OCF_CHECK_LEVEL" ]; then
				lvl=$prev # the previous one
				break
			fi
			prev=$lvl
		done
	fi
	echo $lvl
}

# usage: ocf_stop_processes SIGNALS WAIT_TIME PIDS
#
# we send signals (use quotes for more than one!) in the order
# given; if one or more processes are still running we try KILL;
# the wait_time is the _total_ time we'll spend in this function
# this time may be slightly exceeded if the processes won't leave
# 
# returns:
#     0: all processes left
#     1: some processes still running
#
# example:
#
# ocf_stop_processes TERM 5 $pids
# 
ocf_stop_processes() {
	local signals="$1"
	local wait_time="$(($2/`echo $signals|wc -w`))"
	shift 2
	local pids="$*"
	local sig i
	test -z "$pids" &&
		return 0
	for sig in $signals KILL; do
		kill -s $sig $pids 2>/dev/null
		# try to leave early, and yet leave processes time to exit
		sleep 0.2
		for i in `seq $wait_time`; do
			kill -s 0 $pids 2>/dev/null ||
				return 0
			sleep 1
		done
	done
	return 1
}

#
# RA tracing may be turned on by setting OCF_TRACE_RA
# the trace output will be saved to OCF_TRACE_FILE, if set, or
# by default to
#   $HA_VARLIB/trace_ra/<type>/<id>.<action>.<timestamp>
#   e.g. $HA_VARLIB/trace_ra/oracle/db.start.2012-11-27.08:37:08
#
# OCF_TRACE_FILE:
# - FD (small integer [3-9]) in that case it is up to the callers
#   to capture output; the FD _must_ be open for writing
# - absolute path
#
# NB: FD 9 may be used for tracing with bash >= v4 in case
# OCF_TRACE_FILE is set to a path.
#
ocf_is_bash4() {
	echo "$SHELL" | grep bash > /dev/null &&
			[ ${BASH_VERSINFO[0]} = "4" ]
}
ocf_trace_redirect_to_file() {
	local dest=$1
	if ocf_is_bash4; then
		exec 9>$dest
		BASH_XTRACEFD=9
	else
		exec 2>$dest
	fi
}
ocf_trace_redirect_to_fd() {
	local fd=$1
	if ocf_is_bash4; then
		BASH_XTRACEFD=$fd
	else
		exec 2>&$fd
	fi
}
__ocf_test_trc_dest() {
	local dest=$1
	if ! touch $dest; then
		ocf_log warn "$dest not writable, trace not going to happen"
		__OCF_TRC_DEST=""
		__OCF_TRC_MANAGE=""
		return 1
	fi
	return 0
}
ocf_default_trace_dest() {
	tty >/dev/null && return
	if [ -n "$OCF_RESOURCE_TYPE" -a \
			-n "$OCF_RESOURCE_INSTANCE" -a -n "$__OCF_ACTION" ]; then
		local ts=`date +%F.%T`
		__OCF_TRC_DEST=$HA_VARLIB/trace_ra/${OCF_RESOURCE_TYPE}/${OCF_RESOURCE_INSTANCE}.${__OCF_ACTION}.$ts
		__OCF_TRC_MANAGE="1"
	fi
}

ocf_start_trace() {
	export __OCF_TRC_DEST="" __OCF_TRC_MANAGE=""
	case "$OCF_TRACE_FILE" in
	[3-9]) ocf_trace_redirect_to_fd "$OCF_TRACE_FILE" ;;
	/*/*) __OCF_TRC_DEST=$OCF_TRACE_FILE ;;
	"") ocf_default_trace_dest ;;
	*)
		ocf_log warn "OCF_TRACE_FILE must be set to either FD (open for writing) or absolute file path"
		ocf_default_trace_dest
		;;
	esac
	if [ "$__OCF_TRC_DEST" ]; then
		mkdir -p `dirname $__OCF_TRC_DEST`
		__ocf_test_trc_dest $__OCF_TRC_DEST ||
			return
		ocf_trace_redirect_to_file "$__OCF_TRC_DEST"
	fi
	PS4='+ `date +"%T"`: ${FUNCNAME[0]:+${FUNCNAME[0]}:}${LINENO}: '
	set -x
}
ocf_stop_trace() {
	set +x
}

__ocf_set_defaults "$@"

: ${OCF_TRACE_RA:=$OCF_RESKEY_trace_ra}
ocf_is_true "$OCF_TRACE_RA" && ocf_start_trace
