1#
2#
3# 	Common helper functions for the OCF Resource Agents supplied by
4# 	heartbeat.
5#
6# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée
7#                    All Rights Reserved.
8#
9#
10# This library is free software; you can redistribute it and/or
11# modify it under the terms of the GNU Lesser General Public
12# License as published by the Free Software Foundation; either
13# version 2.1 of the License, or (at your option) any later version.
14#
15# This library is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18# Lesser General Public License for more details.
19#
20# You should have received a copy of the GNU Lesser General Public
21# License along with this library; if not, write to the Free Software
22# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23#
24
25# Build version: 55a4e2c91cb846b2f35490645a1ebe832b7bdde9
26
27# TODO: Some of this should probably split out into a generic OCF
28# library for shell scripts, but for the time being, we'll just use it
29# ourselves...
30#
31
32# TODO wish-list:
33# - Generic function for evaluating version numbers
34# - Generic function(s) to extract stuff from our own meta-data
35# - Logging function which automatically adds resource identifier etc
36#   prefixes
37# TODO: Move more common functionality for OCF RAs here.
38#
39
40# This was common throughout all legacy Heartbeat agents
41unset LC_ALL; export LC_ALL
42unset LANGUAGE; export LANGUAGE
43
44: ${HA_SBIN_DIR:=@sbindir@}
45
46__SCRIPT_NAME=`basename $0`
47
48if [ -z "$OCF_ROOT" ]; then
49    : ${OCF_ROOT=@OCF_ROOT_DIR@}
50fi
51
52if [ "$OCF_FUNCTIONS_DIR" = ${OCF_ROOT}/resource.d/heartbeat ]; then  # old
53	unset OCF_FUNCTIONS_DIR
54fi
55
56: ${OCF_FUNCTIONS_DIR:=${OCF_ROOT}/lib/heartbeat}
57
58. ${OCF_FUNCTIONS_DIR}/ocf-binaries
59. ${OCF_FUNCTIONS_DIR}/ocf-returncodes
60. ${OCF_FUNCTIONS_DIR}/ocf-directories
61. ${OCF_FUNCTIONS_DIR}/ocf-rarun
62. ${OCF_FUNCTIONS_DIR}/ocf-distro
63
64# Define OCF_RESKEY_CRM_meta_interval in case it isn't already set,
65# to make sure that ocf_is_probe() always works
66: ${OCF_RESKEY_CRM_meta_interval=0}
67
68ocf_is_root() {
69	if [ X`id -u` = X0 ]; then
70		true
71	else
72		false
73	fi
74}
75
76ocf_maybe_random() {
77	if test -c /dev/urandom; then
78		od -An -N4 -tu4 /dev/urandom | tr -d '[:space:]'
79	else
80		awk -v pid=$$ 'BEGIN{srand(pid); print rand()}' | sed 's/^.*[.]//'
81	fi
82}
83
84# Portability comments:
85# o The following rely on Bourne "sh" pattern-matching, which is usually
86#   that for filename generation (note: not regexp).
87# o The "*) true ;;" clause is probably unnecessary, but is included
88#   here for completeness.
89# o The negation in the pattern uses "!".  This seems to be common
90#   across many OSes (whereas the alternative "^" fails on some).
91# o If an OS is encountered where this negation fails, then a possible
92#   alternative would be to replace the function contents by (e.g.):
93#	[ -z "`echo $1 | tr -d '[0-9]'`" ]
94#
95ocf_is_decimal() {
96	case "$1" in
97	""|*[!0-9]*)	# empty, or at least one non-decimal
98		false ;;
99	*)
100		true ;;
101	esac
102}
103
104ocf_is_true() {
105	case "$1" in
106	yes|true|1|YES|TRUE|True|ja|on|ON) true ;;
107	*)	false ;;
108	esac
109}
110
111ocf_is_hex() {
112	case "$1" in
113        ""|*[!0-9a-fA-F]*)	# empty, or at least one non-hex
114		false ;;
115	*)
116		true ;;
117	esac
118}
119
120ocf_is_octal() {
121	case "$1" in
122        ""|*[!0-7]*)	# empty, or at least one non-octal
123		false ;;
124	*)
125		true ;;
126	esac
127}
128
129__ocf_set_defaults() {
130	__OCF_ACTION="$1"
131
132	# Return to sanity for the agents...
133	unset LANG
134	LC_ALL=C
135	export LC_ALL
136
137	# TODO: Review whether we really should source this. Or rewrite
138	# to match some emerging helper function syntax...? This imports
139	# things which no OCF RA should be using...
140
141	# Strip the OCF_RESKEY_ prefix from this particular parameter
142	if [ -z "$OCF_RESKEY_OCF_CHECK_LEVEL" ]; then
143		: ${OCF_CHECK_LEVEL:=0}
144	else
145		: ${OCF_CHECK_LEVEL:=$OCF_RESKEY_OCF_CHECK_LEVEL}
146	fi
147
148	if [ ! -d "$OCF_ROOT" ]; then
149		ha_log "ERROR: OCF_ROOT points to non-directory $OCF_ROOT."
150		exit $OCF_ERR_GENERIC
151	fi
152
153	if [ -z "$OCF_RESOURCE_TYPE" ]; then
154		: ${OCF_RESOURCE_TYPE:=$__SCRIPT_NAME}
155	fi
156
157	if [ "x$__OCF_ACTION" = "xmeta-data" ]; then
158		: ${OCF_RESOURCE_INSTANCE:="RESOURCE_ID"}
159	fi
160
161	if [ -z "$OCF_RA_VERSION_MAJOR" ]; then
162		: We are being invoked as an init script.
163		: Fill in some things with reasonable values.
164		: ${OCF_RESOURCE_INSTANCE:="default"}
165		return 0
166        fi
167
168	if [ -z "$OCF_RESOURCE_INSTANCE" ]; then
169		ha_log "ERROR: Need to tell us our resource instance name."
170		exit $OCF_ERR_ARGS
171	fi
172}
173
174hadate() {
175  date "+${HA_DATEFMT}"
176}
177
178set_logtag() {
179	if [ -z "$HA_LOGTAG" ]; then
180		if [ -n "$OCF_RESOURCE_INSTANCE" ]; then
181			HA_LOGTAG="$__SCRIPT_NAME($OCF_RESOURCE_INSTANCE)[$$]"
182		else
183			HA_LOGTAG="$__SCRIPT_NAME[$$]"
184		fi
185	fi
186}
187
188__ha_log() {
189	local ignore_stderr=false
190	local loglevel
191
192	[ "x$1" = "x--ignore-stderr" ] && ignore_stderr=true && shift
193
194	[ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=""
195	# if we're connected to a tty, then output to stderr
196	if tty >/dev/null; then
197		if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then
198			return 0
199		elif [ "$ignore_stderr" = "true" ]; then
200			# something already printed this error to stderr, so ignore
201			return 0
202		fi
203		if [ "$HA_LOGTAG" ]; then
204			echo "$HA_LOGTAG: $*"
205		else
206			echo "$*"
207		fi >&2
208		return 0
209	fi
210
211	set_logtag
212
213	if [ "x${HA_LOGD}" = "xyes" ] ; then
214		ha_logger -t "${HA_LOGTAG}" "$@"
215		if [ "$?" -eq "0" ] ; then
216			return 0
217		fi
218	fi
219
220	if
221	  [ -n "$HA_LOGFACILITY" ]
222        then
223	  : logging through syslog
224	  # loglevel is unknown, use 'notice' for now
225          loglevel=notice
226          case "${*}" in
227            *ERROR*)		loglevel=err;;
228            *WARN*)		loglevel=warning;;
229            *INFO*|info)	loglevel=info;;
230	  esac
231	  logger -t "$HA_LOGTAG" -p ${HA_LOGFACILITY}.${loglevel} "${*}"
232        fi
233        if
234	  [ -n "$HA_LOGFILE" ]
235	then
236	  : appending to $HA_LOGFILE
237	  echo `hadate`" $HA_LOGTAG:    ${*}" >> $HA_LOGFILE
238	fi
239	if
240	  [ -z "$HA_LOGFACILITY" -a -z "$HA_LOGFILE" ] && ! [ "$ignore_stderr" = "true" ]
241	then
242	  : appending to stderr
243	  echo `hadate`"${*}" >&2
244	fi
245        if
246          [ -n "$HA_DEBUGLOG" ]
247        then
248          : appending to $HA_DEBUGLOG
249		  if [ "$HA_LOGFILE"x != "$HA_DEBUGLOG"x ]; then
250            echo "$HA_LOGTAG:	"`hadate`"${*}" >> $HA_DEBUGLOG
251          fi
252        fi
253}
254
255ha_log()
256{
257	__ha_log "$@"
258}
259
260ha_debug() {
261
262        if [ "x${HA_debug}" = "x0" ] || [ -z "${HA_debug}" ] ; then
263                return 0
264        fi
265	if tty >/dev/null; then
266		if [ "$HA_LOGTAG" ]; then
267			echo "$HA_LOGTAG: $*"
268		else
269			echo "$*"
270		fi >&2
271		return 0
272	fi
273
274	set_logtag
275
276        if [ "x${HA_LOGD}" = "xyes" ] ; then
277		ha_logger -t "${HA_LOGTAG}" -D "ha-debug" "$@"
278                if [ "$?" -eq "0" ] ; then
279                        return 0
280                fi
281        fi
282
283	[ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=""
284
285	if
286	  [ -n "$HA_LOGFACILITY" ]
287	then
288	  : logging through syslog
289	  logger -t "$HA_LOGTAG" -p "${HA_LOGFACILITY}.debug" "${*}"
290	fi
291        if
292	  [ -n "$HA_DEBUGLOG" ]
293	then
294	  : appending to $HA_DEBUGLOG
295	  echo "$HA_LOGTAG:	"`hadate`"${*}" >> $HA_DEBUGLOG
296	fi
297	if
298	  [ -z "$HA_LOGFACILITY" -a -z "$HA_DEBUGLOG" ]
299	then
300	  : appending to stderr
301	  echo "$HA_LOGTAG:	`hadate`${*}:	${HA_LOGFACILITY}" >&2
302	fi
303}
304
305ha_parameter() {
306	local VALUE
307    VALUE=`sed -e 's%[	][	]*% %' -e 's%^ %%' -e 's%#.*%%'   $HA_CF | grep -i "^$1 " | sed 's%[^ ]* %%'`
308    if
309	[ "X$VALUE" = X ]
310    then
311
312	case $1 in
313	    keepalive)	VALUE=2;;
314	    deadtime)
315		ka=`ha_parameter keepalive`
316		VALUE=`expr $ka '*' 2 '+' 1`;;
317	esac
318    fi
319    echo $VALUE
320}
321
322ocf_log() {
323	# TODO: Revisit and implement internally.
324	if
325          [ $# -lt 2 ]
326        then
327          ocf_log err "Not enough arguments [$#] to ocf_log."
328        fi
329        __OCF_PRIO="$1"
330        shift
331        __OCF_MSG="$*"
332
333        case "${__OCF_PRIO}" in
334          crit)	__OCF_PRIO="CRIT";;
335          err)	__OCF_PRIO="ERROR";;
336          warn)	__OCF_PRIO="WARNING";;
337          info)	__OCF_PRIO="INFO";;
338          debug)__OCF_PRIO="DEBUG";;
339          *)	__OCF_PRIO=`echo ${__OCF_PRIO}| tr '[a-z]' '[A-Z]'`;;
340	esac
341
342	if [ "${__OCF_PRIO}" = "DEBUG" ]; then
343		ha_debug "${__OCF_PRIO}: $__OCF_MSG"
344	else
345		ha_log "${__OCF_PRIO}: $__OCF_MSG"
346	fi
347}
348
349#
350# ocf_exit_reason: print exit error string to stderr
351# Usage:           Allows the OCF script to provide a string
352#                  describing why the exit code was returned.
353# Arguments:   reason - required, The string that represents why the error
354#                       occured.
355#
356ocf_exit_reason()
357{
358	local cookie="$OCF_EXIT_REASON_PREFIX"
359	local fmt
360	local msg
361
362	# No argument is likely not intentional.
363	# Just one argument implies a printf format string of just "%s".
364	# "Least surprise" in case some interpolated string from variable
365	# expansion or other contains a percent sign.
366	# More than one argument: first argument is going to be the format string.
367	case $# in
368	0)	ocf_log err "Not enough arguments to ocf_log_exit_msg." ;;
369	1)	fmt="%s" ;;
370
371	*)	fmt=$1
372		shift
373		case $fmt in
374		*%*) : ;; # ok, does look like a format string
375		*) ocf_log warn "Does not look like format string: [$fmt]" ;;
376		esac ;;
377	esac
378
379	if [ -z "$cookie" ]; then
380		# use a default prefix
381		cookie="ocf-exit-reason:"
382	fi
383
384	msg=$(printf "${fmt}" "$@")
385	printf >&2 "%s%s\n" "$cookie" "$msg"
386	__ha_log --ignore-stderr "ERROR: $msg"
387}
388
389#
390# ocf_deprecated: Log a deprecation warning
391# Usage:          ocf_deprecated [param-name]
392# Arguments:      param-name optional, name of a boolean resource
393#                            parameter that can be used to suppress
394#                            the warning (default
395#                            "ignore_deprecation")
396ocf_deprecated() {
397    local param
398    param=${1:-ignore_deprecation}
399    # don't use ${!param} here, it's a bashism
400    if ! ocf_is_true $(eval echo \$OCF_RESKEY_$param); then
401	ocf_log warn "This resource agent is deprecated" \
402	    "and may be removed in a future release." \
403	    "See the man page for details." \
404	    "To suppress this warning, set the \"${param}\"" \
405	    "resource parameter to true."
406    fi
407}
408
409#
410# Ocf_run: Run a script, and log its output.
411# Usage:   ocf_run [-q] [-info|-warn|-err] <command>
412#	-q: don't log the output of the command if it succeeds
413#	-info|-warn|-err: log the output of the command at given
414#		severity if it fails (defaults to err)
415#
416ocf_run() {
417	local rc
418	local output
419	local verbose=1
420	local loglevel=err
421	local var
422
423	for var in 1 2
424	do
425	    case "$1" in
426		"-q")
427		    verbose=""
428		    shift 1;;
429		"-info"|"-warn"|"-err")
430		    loglevel=`echo $1 | sed -e s/-//g`
431		    shift 1;;
432		*)
433		    ;;
434	    esac
435	done
436
437	output=`"$@" 2>&1`
438	rc=$?
439	[ -n "$output" ] && output="$(echo "$output" | tr -s ' \t\r\n' ' ')"
440	if [ $rc -eq 0 ]; then
441	    if [ "$verbose" -a ! -z "$output" ]; then
442		ocf_log info "$output"
443	    fi
444	else
445	    if [ ! -z "$output" ]; then
446		ocf_log $loglevel "$output"
447	    else
448		ocf_log $loglevel "command failed: $*"
449	    fi
450	fi
451
452	return $rc
453}
454
455ocf_pidfile_status() {
456    local pid pidfile=$1
457    if [ ! -e $pidfile ]; then
458	# Not exists
459	return 2
460    fi
461    pid=`cat $pidfile`
462    kill -0 $pid > /dev/null 2>&1
463    if [ $? = 0 ]; then
464	return 0
465    fi
466
467    # Stale
468    return 1
469}
470
471# mkdir(1) based locking
472# first the directory is created with the name given as $1
473# then a file named "pid" is created within that directory with
474# the process PID
475# stale locks are handled carefully, the inode of a directory
476# needs to match before and after test if the process is running
477# empty directories are also handled appropriately
478# we relax (sleep) occasionally to allow for other processes to
479# finish managing the lock in case they are in the middle of the
480# business
481
482relax() { sleep 0.5; }
483ocf_get_stale_pid() {
484	local piddir pid dir_inode
485
486	piddir="$1"
487	[ -z "$piddir" ] && return 2
488	dir_inode="`ls -di $piddir 2>/dev/null`"
489	[ -z "$dir_inode" ] && return 1
490	pid=`cat $piddir/pid 2>/dev/null`
491	if [ -z "$pid" ]; then
492		# empty directory?
493		relax
494		if [ "$dir_inode" = "`ls -di $piddir 2>/dev/null`" ]; then
495			echo $dir_inode
496		else
497			return 1
498		fi
499	elif kill -0 $pid >/dev/null 2>&1; then
500		return 1
501	elif relax && [ -e "$piddir/pid" ] && [ "$dir_inode" = "`ls -di $piddir 2>/dev/null`" ]; then
502		echo $pid
503	else
504		return 1
505	fi
506}
507
508# There is a race when the following two functions to manage the
509# lock file (mk and rm) are invoked in parallel by different
510# instances. It is up to the caller to reduce probability of that
511# taking place (see ocf_take_lock() below).
512
513ocf_mk_pid() {
514	mkdir $1 2>/dev/null && echo $$ > $1/pid
515}
516ocf_rm_pid() {
517	rm -f $1/pid
518	rmdir $1 2>/dev/null
519}
520
521# Testing and subsequently removing a stale lock (containing the
522# process pid) is inherently difficult to do in such a way as to
523# prevent a race between creating a pid file and removing it and
524# its directory. We reduce the probability of that happening by
525# checking if the stale lock persists over a random period of
526# time.
527
528ocf_take_lock() {
529	local lockdir=$1
530	local rnd
531	local stale_pid
532
533	# we don't want it too short, so strip leading zeros
534	rnd=$(ocf_maybe_random | sed 's/^0*//')
535	stale_pid=`ocf_get_stale_pid $lockdir`
536	if [ -n "$stale_pid" ]; then
537		sleep 0.$rnd
538		# remove "stale pid" only if it persists
539		[ "$stale_pid" = "`ocf_get_stale_pid $lockdir`" ] &&
540			ocf_rm_pid $lockdir
541	fi
542	while ! ocf_mk_pid $lockdir; do
543		ocf_log info "Sleeping until $lockdir is released..."
544		sleep 0.$rnd
545	done
546}
547
548ocf_release_lock_on_exit() {
549	trap "ocf_rm_pid $1" EXIT
550}
551
552# returns true if the CRM is currently running a probe. A probe is
553# defined as a monitor operation with a monitoring interval of zero.
554ocf_is_probe() {
555    [ "$__OCF_ACTION" = "monitor" -a "$OCF_RESKEY_CRM_meta_interval" = 0 ]
556}
557
558# returns true if the resource is configured as a clone. This is
559# defined as a resource where the clone-max meta attribute is present.
560ocf_is_clone() {
561    [ ! -z "${OCF_RESKEY_CRM_meta_clone_max}" ]
562}
563
564# returns true if the resource is configured as a multistate
565# (master/slave) resource. This is defined as a resource where the
566# master-max meta attribute is present, and set to greater than zero.
567ocf_is_ms() {
568    [ "${OCF_RESKEY_CRM_meta_promotable}" = "true" ] || { [ ! -z "${OCF_RESKEY_CRM_meta_master_max}" ] && [ "${OCF_RESKEY_CRM_meta_master_max}" -gt 0 ]; }
569}
570
571# version check functions
572# allow . and - to delimit version numbers
573# max version number is 999
574#
575ocf_is_ver() {
576	echo $1 | grep '^[0-9][0-9.-]*[0-9A-Za-z.\+-]*$' >/dev/null 2>&1
577}
578
579# usage: ocf_version_cmp VER1 VER2
580#     version strings can contain digits, dots, and dashes
581#     must start and end with a digit
582# returns:
583#     0: VER1 smaller (older) than VER2
584#     1: versions equal
585#     2: VER1 greater (newer) than VER2
586#     3: bad format
587ocf_version_cmp() {
588	ocf_is_ver "$1" || return 3
589	ocf_is_ver "$2" || return 3
590	local v1=$1
591	local v2=$2
592
593	sort_version="sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n"
594	older=$( (echo "$v1"; echo "$v2") | $sort_version | head -1 )
595
596	if [ "$v1" = "$v2" ]; then
597		return 1
598	elif [ "$v1" = "$older" ]; then
599		return 0
600	else
601		return 2 # -1 would look funny in shell ;-)
602	fi
603}
604
605ocf_local_nodename() {
606	# use crm_node -n for pacemaker > 1.1.8
607	which pacemakerd > /dev/null 2>&1
608	if [ $? -eq 0 ]; then
609		local version=$(pacemakerd -$ | grep "Pacemaker .*" | awk '{ print $2 }')
610		version=$(echo $version | awk -F- '{ print $1 }')
611		ocf_version_cmp "$version" "1.1.8"
612		if [ $? -eq 2 ]; then
613			which crm_node > /dev/null 2>&1
614			if [ $? -eq 0 ]; then
615				crm_node -n
616				return
617			fi
618		fi
619	fi
620
621	# otherwise use uname -n
622	uname -n
623}
624
625# usage: dirname DIR
626dirname()
627{
628	local a
629	local b
630
631	[ $# = 1 ] || return 1
632	a="$1"
633	while [ 1 ]; do
634		b="${a%/}"
635		[ "$a" = "$b" ] && break
636		a="$b"
637	done
638	b=${a%/*}
639	[ -z "$b" -o "$a" = "$b"  ] && b="."
640
641	echo "$b"
642	return 0
643}
644
645# usage: systemd_is_running
646# returns:
647#    0  PID 1 is systemd
648#    1  otherwise
649systemd_is_running()
650{
651	[ "$(cat /proc/1/comm 2>/dev/null)" = "systemd" ]
652}
653
654# usage: systemd_drop_in <name> <After|Before> <dependency.service>
655systemd_drop_in()
656{
657	local conf_file
658	if [ $# -ne 3 ]; then
659          ocf_log err "Incorrect number of arguments [$#] for systemd_drop_in."
660        fi
661
662	systemdrundir="/run/systemd/system/resource-agents-deps.target.d"
663	mkdir -p "$systemdrundir"
664	conf_file="$systemdrundir/$1.conf"
665	cat >"$conf_file" <<EOF
666[Unit]
667$2=$3
668EOF
669	# The information is accessible through systemd API and systemd would
670	# complain about improper permissions.
671	chmod o+r "$conf_file"
672	systemctl daemon-reload
673}
674
675# usage: crm_mon_no_validation args...
676# run crm_mon without any cib schema validation
677# This is useful when an agent runs in a bundle to avoid potential
678# schema validation errors when host and bundle are not perfectly aligned
679# To be used, your shell must support on process substitution (e.g. bash)
680# returns:
681#    <crm_mon error codes>
682crm_mon_no_validation()
683{
684	# The subshell prevents parsing error with incompatible shells
685	"$SHELL" -c "CIB_file=<(${HA_SBIN_DIR}/cibadmin -Q | sed 's/validate-with=\"[^\"]*\"/validate-with=\"none\"/') \
686	${HA_SBIN_DIR}/crm_mon \$*" -- $*
687}
688
689#
690# pseudo_resource status tracking function...
691#
692# This allows pseudo resources to give correct status information.  As we add
693# resource monitoring, and better resource tracking in general, this will
694# become essential.
695#
696# These scripts work because ${HA_RSCTMP} is cleaned on node reboot.
697#
698# We create "resource-string" tracking files under ${HA_RSCTMP} in a
699# very simple way:
700#
701#	Existence of "${HA_RSCTMP}/resource-string" means that we consider
702#	the resource named by "resource-string" to be running.
703#
704# Note that "resource-string" needs to be unique.  Using the resource type
705# plus the resource instance arguments to make up the resource string
706# is probably sufficient...
707#
708# usage: ha_pseudo_resource resource-string op [tracking_file]
709# 	where op is {start|stop|monitor|status|restart|reload|print}
710#	print is a special op which just prints the tracking file location
711#	user can override our choice of the tracking file location by
712#		specifying it as the third arg
713#	Note that all operations are silent...
714#
715ha_pseudo_resource()
716{
717  local ha_resource_tracking_file="${3:-${HA_RSCTMP}/$1}"
718  case $2 in
719    start|restart|reload)  touch "$ha_resource_tracking_file";;
720    stop) rm -f "$ha_resource_tracking_file";;
721    status|monitor)
722           if
723             [ -f "$ha_resource_tracking_file" ]
724           then
725             return 0
726           else
727             case $2 in
728               status)	return 3;;
729               *)	return 7;;
730             esac
731           fi;;
732    print)  echo "$ha_resource_tracking_file";;
733    *)	return 3;;
734  esac
735}
736
737# usage: rmtempdir TMPDIR
738rmtempdir()
739{
740	[ $# = 1 ] || return 1
741	if [ -e "$1" ]; then
742		rmdir "$1" || return 1
743	fi
744	return 0
745}
746
747# usage: maketempfile [-d]
748maketempfile()
749{
750	if [ $# = 1 -a "$1" = "-d" ]; then
751		mktemp -d
752		return 0
753	elif [ $# != 0 ]; then
754		return 1
755	fi
756
757	mktemp
758	return 0
759}
760
761# usage: rmtempfile TMPFILE
762rmtempfile ()
763{
764	[ $# = 1 ] || return 1
765	if [ -e "$1" ]; then
766		rm "$1" || return 1
767	fi
768	return 0
769}
770
771# echo the first lower supported check level
772# pass set of levels supported by the agent
773# (in increasing order, 0 is optional)
774ocf_check_level()
775{
776	local lvl prev
777	lvl=0
778	prev=0
779	if ocf_is_decimal "$OCF_CHECK_LEVEL"; then
780		# the level list should be very short
781		for lvl; do
782			if [ "$lvl" -eq "$OCF_CHECK_LEVEL" ]; then
783				break
784			elif [ "$lvl" -gt "$OCF_CHECK_LEVEL" ]; then
785				lvl=$prev # the previous one
786				break
787			fi
788			prev=$lvl
789		done
790	fi
791	echo $lvl
792}
793
794# usage: ocf_stop_processes SIGNALS WAIT_TIME PIDS
795#
796# we send signals (use quotes for more than one!) in the order
797# given; if one or more processes are still running we try KILL;
798# the wait_time is the _total_ time we'll spend in this function
799# this time may be slightly exceeded if the processes won't leave
800#
801# returns:
802#     0: all processes left
803#     1: some processes still running
804#
805# example:
806#
807# ocf_stop_processes TERM 5 $pids
808#
809ocf_stop_processes() {
810	local signals="$1"
811	local wait_time="$(($2/`echo $signals|wc -w`))"
812	shift 2
813	local pids="$*"
814	local sig i
815	test -z "$pids" &&
816		return 0
817	for sig in $signals KILL; do
818		kill -s $sig $pids 2>/dev/null
819		# try to leave early, and yet leave processes time to exit
820		sleep 0.2
821		for i in `seq $wait_time`; do
822			kill -s 0 $pids 2>/dev/null ||
823				return 0
824			sleep 1
825		done
826	done
827	return 1
828}
829
830#
831# create a given status directory
832# if the directory path doesn't start with $HA_VARRUN, then
833# we return with error (most of the calls would be with the user
834# supplied configuration, hence we need to do necessary
835# protection)
836# used mostly for PID files
837#
838# usage: ocf_mkstatedir owner permissions path
839#
840# owner: user.group
841# permissions: permissions
842# path: directory path
843#
844# example:
845#	ocf_mkstatedir named 755 `dirname $pidfile`
846#
847ocf_mkstatedir()
848{
849	local owner
850	local perms
851	local path
852
853	owner=$1
854	perms=$2
855	path=$3
856
857	test -d $path && return 0
858	[ $(id -u) = 0 ] || return 1
859
860	case $path in
861	${HA_VARRUN%/}/*) : this path is ok ;;
862	*) ocf_log err "cannot create $path (does not start with $HA_VARRUN)"
863		return 1
864	;;
865	esac
866
867	mkdir -p $path &&
868	chown $owner $path &&
869	chmod $perms $path
870}
871
872#
873# create a unique status directory in $HA_VARRUN
874# used mostly for PID files
875# the directory is by default set to
876#   $HA_VARRUN/$OCF_RESOURCE_INSTANCE
877# the directory name is printed to stdout
878#
879# usage: ocf_unique_rundir owner permissions name
880#
881# owner: user.group (default: "root")
882# permissions: permissions (default: "755")
883# name: some unique string (default: "$OCF_RESOURCE_INSTANCE")
884#
885# to use the default either don't set the parameter or set it to
886# empty string ("")
887# example:
888#
889#	STATEDIR=`ocf_unique_rundir named "" myownstatedir`
890#
891ocf_unique_rundir()
892{
893	local path
894	local owner
895	local perms
896	local name
897
898	owner=${1:-"root"}
899	perms=${2:-"755"}
900	name=${3:-"$OCF_RESOURCE_INSTANCE"}
901	path=$HA_VARRUN/$name
902	if [ ! -d $path ]; then
903		[ $(id -u) = 0 ] || return 1
904		mkdir -p $path &&
905		chown $owner $path &&
906		chmod $perms $path || return 1
907	fi
908	echo $path
909}
910
911#
912# RA tracing may be turned on by setting OCF_TRACE_RA
913# the trace output will be saved to OCF_TRACE_FILE, if set, or
914# by default to
915#   $HA_VARLIB/trace_ra/<type>/<id>.<action>.<timestamp>
916#   e.g. $HA_VARLIB/trace_ra/oracle/db.start.2012-11-27.08:37:08
917#
918# OCF_TRACE_FILE:
919# - FD (small integer [3-9]) in that case it is up to the callers
920#   to capture output; the FD _must_ be open for writing
921# - absolute path
922#
923# NB: FD 9 may be used for tracing with bash >= v4 in case
924# OCF_TRACE_FILE is set to a path.
925#
926ocf_bash_has_xtracefd() {
927	[ -n "$BASH_VERSION" ] && [ ${BASH_VERSINFO[0]} -ge 4 ]
928}
929# for backwards compatibility
930ocf_is_bash4() {
931	ocf_bash_has_xtracefd
932}
933ocf_trace_redirect_to_file() {
934	local dest=$1
935	if ocf_bash_has_xtracefd; then
936		exec 9>$dest
937		BASH_XTRACEFD=9
938	else
939		exec 2>$dest
940	fi
941}
942ocf_trace_redirect_to_fd() {
943	local fd=$1
944	if ocf_bash_has_xtracefd; then
945		BASH_XTRACEFD=$fd
946	else
947		exec 2>&$fd
948	fi
949}
950__ocf_test_trc_dest() {
951	local dest=$1
952	if ! touch $dest; then
953		ocf_log warn "$dest not writable, trace not going to happen"
954		__OCF_TRC_DEST=""
955		__OCF_TRC_MANAGE=""
956		return 1
957	fi
958	return 0
959}
960ocf_default_trace_dest() {
961	tty >/dev/null && return
962	if [ -n "$OCF_RESOURCE_TYPE" -a \
963			-n "$OCF_RESOURCE_INSTANCE" -a -n "$__OCF_ACTION" ]; then
964		local ts=`date +%F.%T`
965		__OCF_TRC_DEST=$HA_VARLIB/trace_ra/${OCF_RESOURCE_TYPE}/${OCF_RESOURCE_INSTANCE}.${__OCF_ACTION}.$ts
966		__OCF_TRC_MANAGE="1"
967	fi
968}
969
970ocf_start_trace() {
971	export __OCF_TRC_DEST="" __OCF_TRC_MANAGE=""
972	case "$OCF_TRACE_FILE" in
973	[3-9]) ocf_trace_redirect_to_fd "$OCF_TRACE_FILE" ;;
974	/*/*) __OCF_TRC_DEST=$OCF_TRACE_FILE ;;
975	"") ocf_default_trace_dest ;;
976	*)
977		ocf_log warn "OCF_TRACE_FILE must be set to either FD (open for writing) or absolute file path"
978		ocf_default_trace_dest
979		;;
980	esac
981	if [ "$__OCF_TRC_DEST" ]; then
982		mkdir -p `dirname $__OCF_TRC_DEST`
983		__ocf_test_trc_dest $__OCF_TRC_DEST ||
984			return
985		ocf_trace_redirect_to_file "$__OCF_TRC_DEST"
986	fi
987	if [ -n "$BASH_VERSION" ]; then
988		PS4='+ `date +"%T"`: ${FUNCNAME[0]:+${FUNCNAME[0]}:}${LINENO}: '
989	fi
990	set -x
991	env=$( echo; printenv | sort )
992}
993ocf_stop_trace() {
994	set +x
995}
996
997# Helper functions to map from nodename/bundle-name and physical hostname
998# list_index_for_word "node0 node1 node2 node3 node4 node5" node4 --> 5
999# list_word_at_index "NA host1 host2 host3 host4 host5" 3      --> host2
1000
1001# list_index_for_word "node1 node2 node3 node4 node5" node7 --> ""
1002# list_word_at_index "host1 host2 host3 host4 host5" 8      --> ""
1003
1004# attribute_target node1                                    --> host1
1005list_index_for_word() {
1006	echo $1 | tr ' ' '\n' | awk -v x="$2" '$0~x {print NR}'
1007}
1008
1009list_word_at_index() {
1010	echo $1 | tr ' ' '\n' | awk -v n="$2" 'n == NR'
1011}
1012
1013ocf_attribute_target() {
1014	if [ x$1 = x ]; then
1015		if [ x$OCF_RESKEY_CRM_meta_container_attribute_target = xhost -a x$OCF_RESKEY_CRM_meta_physical_host != x ]; then
1016			echo $OCF_RESKEY_CRM_meta_physical_host
1017		else
1018			if [ x$OCF_RESKEY_CRM_meta_on_node != x ]; then
1019				echo $OCF_RESKEY_CRM_meta_on_node
1020			else
1021				ocf_local_nodename
1022			fi
1023		fi
1024		return
1025	elif [ x"$OCF_RESKEY_CRM_meta_notify_all_uname" != x ]; then
1026		index=$(list_index_for_word "$OCF_RESKEY_CRM_meta_notify_all_uname" $1)
1027		mapping=""
1028		if [ x$index != x ]; then
1029			mapping=$(list_word_at_index "$OCF_RESKEY_CRM_meta_notify_all_hosts" $index)
1030		fi
1031		if [ x$mapping != x -a x$mapping != xNA ]; then
1032			echo $mapping
1033			return
1034		fi
1035	fi
1036	echo $1
1037}
1038
1039__ocf_set_defaults "$@"
1040
1041: ${OCF_TRACE_RA:=$OCF_RESKEY_trace_ra}
1042ocf_is_true "$OCF_TRACE_RA" && ocf_start_trace
1043
1044# pacemaker sets HA_use_logd, some others use HA_LOGD :/
1045if ocf_is_true "$HA_use_logd"; then
1046	: ${HA_LOGD:=yes}
1047fi
1048