ctdb-scripts: Quote some variable expansions
[nivanova/samba-autobuild/.git] / ctdb / config / functions
1 # Hey Emacs, this is a -*- shell-script -*- !!!
2
3 # utility functions for ctdb event scripts
4
5 if [ -z "$CTDB_BASE" ] ; then
6     echo 'CTDB_BASE unset in CTDB functions file'
7     exit 1
8 fi
9 export CTDB_BASE
10
11 CTDB_VARDIR="/usr/local/var/lib/ctdb"
12 ctdb_rundir="/usr/local/var/run/ctdb"
13
14 CTDB="${CTDB:-/usr/local/bin/ctdb}"
15
16 # Only (and always) override these variables in test code
17
18 if [ -z "$CTDB_SCRIPT_VARDIR" ] ; then
19     CTDB_SCRIPT_VARDIR="/usr/local/var/lib/ctdb/state"
20 fi
21
22 if [ -z "$CTDB_SYS_ETCDIR" ] ; then
23     CTDB_SYS_ETCDIR="/etc"
24 fi
25
26 if [ -z "$CTDB_HELPER_BINDIR" ] ; then
27     CTDB_HELPER_BINDIR="/usr/local/libexec/ctdb"
28 fi
29
30 #######################################
31 # pull in a system config file, if any
32
33 rewrite_ctdb_options ()
34 {
35     case "$CTDB_DBDIR" in
36         tmpfs|tmpfs:*)
37             _opts_defaults="mode=700"
38             # Get any extra options specified after colon
39             if [ "$CTDB_DBDIR" = "tmpfs" ] ; then
40                 _opts=""
41             else
42                 _opts="${CTDB_DBDIR#tmpfs:}"
43             fi
44             # This is an internal┬ávariable, only used by ctdbd_wrapper.
45             # It is OK to repeat mount options - last value wins
46             CTDB_DBDIR_TMPFS_OPTIONS="${_opts_defaults}${_opts:+,}${_opts}"
47
48             CTDB_DBDIR="${ctdb_rundir}/CTDB_DBDIR"
49             ;;
50         *)
51             CTDB_DBDIR_TMPFS_OPTIONS=""
52     esac
53 }
54
55 _loadconfig() {
56
57     if [ -z "$1" ] ; then
58         foo="${service_config:-${service_name}}"
59         if [ -n "$foo" ] ; then
60             loadconfig "$foo"
61             return
62         fi
63     fi
64
65     if [ "$1" != "ctdb" ] ; then
66         loadconfig "ctdb"
67     fi
68
69     if [ -z "$1" ] ; then
70         return
71     fi
72
73     if [ -f "${CTDB_SYS_ETCDIR}/sysconfig/$1" ]; then
74         . "${CTDB_SYS_ETCDIR}/sysconfig/$1"
75     elif [ -f "${CTDB_SYS_ETCDIR}/default/$1" ]; then
76         . "${CTDB_SYS_ETCDIR}/default/$1"
77     elif [ -f "${CTDB_BASE}/sysconfig/$1" ]; then
78         . "${CTDB_BASE}/sysconfig/$1"
79     fi
80
81     if [ "$1" = "ctdb" ] ; then
82         _config="${CTDBD_CONF:-${CTDB_BASE}/ctdbd.conf}"
83         if [ -r "$_config" ] ; then
84             . "$_config"
85         fi
86         rewrite_ctdb_options
87     fi
88 }
89
90 loadconfig () {
91     _loadconfig "$@"
92 }
93
94 ##############################################################
95
96 # CTDB_SCRIPT_DEBUGLEVEL can be overwritten by setting it in a
97 # configuration file.
98 debug ()
99 {
100     if [ "${CTDB_SCRIPT_DEBUGLEVEL:-2}" -ge 4 ] ; then
101         # If there are arguments then echo them.  Otherwise expect to
102         # use stdin, which allows us to pass lots of debug using a
103         # here document.
104         if [ -n "$1" ] ; then
105             echo "DEBUG: $*"
106         else
107             sed -e 's@^@DEBUG: @'
108         fi
109     else
110         if [ -z "$1" ] ; then
111             cat >/dev/null
112         fi
113     fi
114 }
115
116 die ()
117 {
118     _msg="$1"
119     _rc="${2:-1}"
120
121     echo "$_msg" >&2
122     exit $_rc
123 }
124
125 # Log given message or stdin to either syslog or a CTDB log file
126 # $1 is the tag passed to logger if syslog is in use.
127 script_log ()
128 {
129     _tag="$1" ; shift
130
131     case "$CTDB_LOGGING" in
132         file:*|"")
133             if [ -n "$CTDB_LOGGING" ] ; then
134                 _file="${CTDB_LOGGING#file:}"
135             else
136                 _file="/usr/local/var/log/log.ctdb"
137             fi
138             {
139                 if [ -n "$*" ] ; then
140                     echo "$*"
141                 else
142                     cat
143                 fi
144             } >>"$_file"
145             ;;
146         *)
147             # Handle all syslog:* variants here too.  There's no tool to do
148             # the lossy things, so just use logger.
149             logger -t "ctdbd: ${_tag}" "$*"
150             ;;
151     esac
152 }
153
154 # When things are run in the background in an eventscript then logging
155 # output might get lost.  This is the "solution".  :-)
156 background_with_logging ()
157 {
158     (
159         "$@" 2>&1 </dev/null |
160         script_log "${script_name}&"
161     )&
162
163     return 0
164 }
165
166 ##############################################################
167 # check number of args for different events
168 ctdb_check_args ()
169 {
170     case "$1" in
171         takeip|releaseip)
172             if [ $# != 4 ]; then
173                 echo "ERROR: must supply interface, IP and maskbits"
174                 exit 1
175             fi
176             ;;
177         updateip)
178             if [ $# != 5 ]; then
179                 echo "ERROR: must supply old interface, new interface, IP and maskbits"
180                 exit 1
181             fi
182             ;;
183     esac
184 }
185
186 ##############################################################
187 # determine on what type of system (init style) we are running
188 detect_init_style()
189 {
190     # only do detection if not already set:
191     [ -z "$CTDB_INIT_STYLE" ] || return
192
193     if [ -x /sbin/startproc ]; then
194         CTDB_INIT_STYLE="suse"
195     elif [ -x /sbin/start-stop-daemon ]; then
196         CTDB_INIT_STYLE="debian"
197     else
198         CTDB_INIT_STYLE="redhat"
199     fi
200 }
201
202 ######################################################
203 # simulate /sbin/service on platforms that don't have it
204 # _service() makes it easier to hook the service() function for
205 # testing.
206 _service ()
207 {
208   _service_name="$1"
209   _op="$2"
210
211   # do nothing, when no service was specified
212   [ -z "$_service_name" ] && return
213
214   if [ -x /sbin/service ]; then
215       $_nice /sbin/service "$_service_name" "$_op"
216   elif [ -x /usr/sbin/service ]; then
217       $_nice /usr/sbin/service "$_service_name" "$_op"
218   elif [ -x /bin/systemctl ]; then
219       $_nice /bin/systemctl "$_op" "$_service_name"
220   elif [ -x "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" ]; then
221       $_nice "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" "$_op"
222   elif [ -x "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" ]; then
223       $_nice "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" "$_op"
224   fi
225 }
226
227 service()
228 {
229     _nice=""
230     _service "$@"
231 }
232
233 ######################################################
234 # simulate /sbin/service (niced) on platforms that don't have it
235 nice_service()
236 {
237     _nice="nice"
238     _service "$@"
239 }
240
241 ######################################################
242 # Cached retrieval of PNN from local node.  This never changes so why
243 # open a client connection to the server each time this is needed?
244 # This sets $pnn - this avoid an unnecessary subprocess.
245 ctdb_get_pnn ()
246 {
247     _pnn_file="${CTDB_SCRIPT_VARDIR}/my-pnn"
248     if [ ! -f "$_pnn_file" ] ; then
249         $CTDB pnn | sed -e 's@.*:@@' >"$_pnn_file"
250     fi
251
252     read pnn <"$_pnn_file"
253 }
254
255 # Cached retrieval of private IP address from local node.  This never
256 # changes.  Sets $ip_address to avoid an unnecessary subprocess.
257 ctdb_get_ip_address ()
258 {
259     _ip_addr_file="${CTDB_SCRIPT_VARDIR}/my-ip-address"
260     if [ ! -f "$_ip_addr_file" ] ; then
261         $CTDB -X nodestatus |
262             awk -F '|' 'NR == 2 { print $3 }' >"$_ip_addr_file"
263     fi
264
265     read ip_address <"$_ip_addr_file"
266 }
267
268 ######################################################
269 # wrapper around /proc/ settings to allow them to be hooked
270 # for testing
271 # 1st arg is relative path under /proc/, 2nd arg is value to set
272 set_proc ()
273 {
274     echo "$2" >"/proc/$1"
275 }
276
277 set_proc_maybe ()
278 {
279     if [ -w "/proc/$1" ] ; then
280         set_proc "$1" "$2"
281     fi
282 }
283
284 ######################################################
285 # wrapper around getting file contents from /proc/ to allow
286 # this to be hooked for testing
287 # 1st arg is relative path under /proc/
288 get_proc ()
289 {
290     cat "/proc/$1"
291 }
292
293 ######################################################
294 # Print up to $_max kernel stack traces for processes named $_program
295 program_stack_traces ()
296 {
297     _prog="$1"
298     _max="${2:-1}"
299
300     _count=1
301     for _pid in $(pidof "$_prog") ; do
302         [ $_count -le $_max ] || break
303
304         # Do this first to avoid racing with process exit
305         _stack=$(get_proc "${_pid}/stack" 2>/dev/null)
306         if [ -n "$_stack" ] ; then
307             echo "Stack trace for ${_prog}[${_pid}]:"
308             echo "$_stack"
309             _count=$(($_count + 1))
310         fi
311     done
312 }
313
314 ######################################################
315 # Ensure $service_name is set
316 assert_service_name ()
317 {
318     [ -n "$service_name" ] || die "INTERNAL ERROR: \$service_name not set"
319 }
320
321 ######################################################
322 # check a set of directories is available
323 # return 1 on a missing directory
324 # directories are read from stdin
325 ######################################################
326 ctdb_check_directories_probe()
327 {
328     while IFS="" read d ; do
329         case "$d" in
330             *%*)
331                 continue
332                 ;;
333             *)
334                 [ -d "${d}/." ] || return 1
335         esac
336     done
337 }
338
339 ######################################################
340 # check a set of directories is available
341 # directories are read from stdin
342 ######################################################
343 ctdb_check_directories()
344 {
345     ctdb_check_directories_probe || {
346         echo "ERROR: $service_name directory \"$d\" not available"
347         exit 1
348     }
349 }
350
351 ######################################################
352 # check a set of tcp ports
353 # usage: ctdb_check_tcp_ports <ports...>
354 ######################################################
355
356 # This flag file is created when a service is initially started.  It
357 # is deleted the first time TCP port checks for that service succeed.
358 # Until then ctdb_check_tcp_ports() prints a more subtle "error"
359 # message if a port check fails.
360 _ctdb_check_tcp_common ()
361 {
362     assert_service_name
363     _d="${CTDB_SCRIPT_VARDIR}/failcount"
364     _ctdb_service_started_file="${_d}/${service_name}.started"
365 }
366
367 ctdb_check_tcp_init ()
368 {
369     _ctdb_check_tcp_common
370     mkdir -p "${_ctdb_service_started_file%/*}" # dirname
371     touch "$_ctdb_service_started_file"
372 }
373
374 # Check whether something is listening on all of the given TCP ports
375 # using the "ctdb checktcpport" command.
376 ctdb_check_tcp_ports()
377 {
378     if [ -z "$1" ] ; then
379         echo "INTERNAL ERROR: ctdb_check_tcp_ports - no ports specified"
380         exit 1
381     fi
382
383     for _p ; do  # process each function argument (port)
384         _cmd="$CTDB checktcpport $_p"
385         _out=$($_cmd 2>&1)
386         _ret=$?
387         case "$_ret" in
388             0)
389                 _ctdb_check_tcp_common
390                 if [ ! -f "$_ctdb_service_started_file" ] ; then
391                     echo "ERROR: $service_name tcp port $_p is not responding"
392                     debug "\"ctdb checktcpport $_p\" was able to bind to port"
393                 else
394                     echo "INFO: $service_name tcp port $_p is not responding"
395                 fi
396
397                 return 1
398                 ;;
399             98)
400                 # Couldn't bind, something already listening, next port...
401                 continue
402                 ;;
403             *)
404                 echo "ERROR: unexpected error running \"ctdb checktcpport\""
405                 debug <<EOF
406 $CTDB checktcpport (exited with $_ret) with output:
407 $_out"
408 EOF
409                 return $_ret
410         esac
411     done
412
413     # All ports listening
414     _ctdb_check_tcp_common
415     rm -f "$_ctdb_service_started_file"
416     return 0
417 }
418
419 ######################################################
420 # check a unix socket
421 # usage: ctdb_check_unix_socket SERVICE_NAME <socket_path>
422 ######################################################
423 ctdb_check_unix_socket() {
424     socket_path="$1"
425     [ -z "$socket_path" ] && return
426
427     if ! netstat --unix -a -n | grep -q "^unix.*LISTEN.*${socket_path}$"; then
428         echo "ERROR: $service_name socket $socket_path not found"
429         return 1
430     fi
431 }
432
433 ######################################################
434 # check a command returns zero status
435 # usage: ctdb_check_command <command>
436 ######################################################
437 ctdb_check_command ()
438 {
439     _out=$("$@" 2>&1) || {
440         echo "ERROR: $* returned error"
441         echo "$_out" | debug
442         exit 1
443     }
444 }
445
446 ################################################
447 # kill off any TCP connections with the given IP
448 ################################################
449 kill_tcp_connections ()
450 {
451     _iface="$1"
452     _ip="$2"
453
454     _oneway=false
455     if [ "$3" = "oneway" ] ; then
456         _oneway=true
457     fi
458
459     get_tcp_connections_for_ip "$_ip" | {
460         _killcount=0
461         _connections=""
462         _nl="
463 "
464         while read _dst _src; do
465             _destport="${_dst##*:}"
466             __oneway=$_oneway
467             case $_destport in
468                 # we only do one-way killtcp for CIFS
469                 139|445) __oneway=true ;;
470             esac
471
472             echo "Killing TCP connection $_src $_dst"
473             _connections="${_connections}${_nl}${_src} ${_dst}"
474             if ! $__oneway ; then
475                 _connections="${_connections}${_nl}${_dst} ${_src}"
476             fi
477
478             _killcount=$(($_killcount + 1))
479         done
480
481         if [ $_killcount -eq 0 ] ; then
482             return
483         fi
484
485         echo "$_connections" | \
486                 "${CTDB_HELPER_BINDIR}/ctdb_killtcp" "$_iface" || {
487                 echo "Failed to kill TCP connections"
488                 return
489         }
490
491         _remaining=$(get_tcp_connections_for_ip "$_ip" | wc -l)
492
493         if [ $_remaining -eq 0 ] ; then
494                 echo "Killed $_killcount TCP connections to released IP $_ip"
495                 return
496         fi
497
498         _t="${_remaining}/${_killcount}"
499         echo "Failed to kill TCP connections for IP $_ip (${_t} remaining)"
500     }
501 }
502
503 ##################################################################
504 # kill off the local end for any TCP connections with the given IP
505 ##################################################################
506 kill_tcp_connections_local_only ()
507 {
508     kill_tcp_connections "$@" "oneway"
509 }
510
511 ##################################################################
512 # tickle any TCP connections with the given IP
513 ##################################################################
514 tickle_tcp_connections ()
515 {
516     _ip="$1"
517
518     get_tcp_connections_for_ip "$_ip" |
519     {
520         _failed=false
521
522         while read dest src; do
523             echo "Tickle TCP connection $src $dest"
524             $CTDB tickle "$src" "$dest" >/dev/null 2>&1 || _failed=true
525             echo "Tickle TCP connection $dest $src"
526             $CTDB tickle "$dest" "$src" >/dev/null 2>&1 || _failed=true
527         done
528
529         if $_failed ; then
530             echo "Failed to send tickle control"
531         fi
532     }
533 }
534
535 get_tcp_connections_for_ip ()
536 {
537     _ip="$1"
538
539     ss -tn state established "src [$_ip]" | awk 'NR > 1 {print $3, $4}'
540 }
541
542 ########################################################
543
544 add_ip_to_iface ()
545 {
546     _iface=$1
547     _ip=$2
548     _maskbits=$3
549
550     # Ensure interface is up
551     ip link set "$_iface" up || \
552         die "Failed to bringup interface $_iface"
553
554     # Only need to define broadcast for IPv4
555     case "$_ip" in
556         *:*) _bcast=""      ;;
557         *)   _bcast="brd +" ;;
558     esac
559
560     ip addr add "$_ip/$_maskbits" $_bcast dev "$_iface" || {
561         echo "Failed to add $_ip/$_maskbits on dev $_iface"
562         return 1
563     }
564
565     # Wait 5 seconds for IPv6 addresses to stop being tentative...
566     if [ -z "$_bcast" ] ; then
567         for _x in $(seq 1 10) ; do
568             ip addr show to "${_ip}/128" | grep -q "tentative" || break
569             sleep 0.5
570         done
571
572         # If the address was a duplicate then it won't be on the
573         # interface so flag an error.
574         _t=$(ip addr show to "${_ip}/128")
575         case "$_t" in
576             "")
577                 echo "Failed to add $_ip/$_maskbits on dev $_iface"
578                 return 1
579                 ;;
580             *tentative*|*dadfailed*)
581                 echo "Failed to add $_ip/$_maskbits on dev $_iface"
582                 ip addr del "$_ip/$_maskbits" dev "$_iface"
583                 return 1
584                 ;;
585         esac
586     fi
587 }
588
589 delete_ip_from_iface()
590 {
591     _iface=$1
592     _ip=$2
593     _maskbits=$3
594
595     # This could be set globally for all interfaces but it is probably
596     # better to avoid surprises, so limit it the interfaces where CTDB
597     # has public IP addresses.  There isn't anywhere else convenient
598     # to do this so just set it each time.  This is much cheaper than
599     # remembering and re-adding secondaries.
600     set_proc "sys/net/ipv4/conf/${_iface}/promote_secondaries" 1
601
602     ip addr del "$_ip/$_maskbits" dev "$_iface" || {
603         echo "Failed to del $_ip on dev $_iface"
604         return 1
605     }
606 }
607
608 # If the given IP is hosted then print 2 items: maskbits and iface
609 ip_maskbits_iface ()
610 {
611     _addr="$1"
612
613     case "$_addr" in
614         *:*) _bits=128 ;;
615         *)   _bits=32  ;;
616     esac
617
618     ip addr show to "${_addr}/${_bits}" 2>/dev/null | \
619         awk 'NR == 1 { iface = $2; sub(":$", "", iface) ; \
620                        sub("@.*", "", iface) } \
621              $1 ~ /inet/ { mask = $2; sub(".*/", "", mask); \
622                            print mask, iface }'
623 }
624
625 drop_ip ()
626 {
627     _addr="${1%/*}"  # Remove optional maskbits
628
629     set -- $(ip_maskbits_iface "$_addr")
630     if [ -n "$1" ] ; then
631         _maskbits="$1"
632         _iface="$2"
633         echo "Removing public address $_addr/$_maskbits from device $_iface"
634         delete_ip_from_iface "$_iface" "$_addr" "$_maskbits" >/dev/null 2>&1
635     fi
636 }
637
638 drop_all_public_ips ()
639 {
640     while read _ip _x ; do
641         drop_ip "$_ip"
642     done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
643 }
644
645 flush_route_cache ()
646 {
647     set_proc_maybe sys/net/ipv4/route/flush 1
648     set_proc_maybe sys/net/ipv6/route/flush 1
649 }
650
651 ########################################################
652 # Interface monitoring
653
654 # If the interface is a virtual one (e.g. VLAN) then get the
655 # underlying interface
656 interface_get_real ()
657 {
658     # Output of "ip link show <iface>"
659     _iface_info="$1"
660
661     # Extract the full interface description to see if it is a VLAN
662     _t=$(echo "$_iface_info" |
663                 awk 'NR == 1 { iface = $2; sub(":$", "", iface) ; \
664                                print iface }')
665     case "$_t" in
666         *@*)
667             # VLAN: use the underlying interface, after the '@'
668             echo "${_t##*@}"
669             ;;
670         *)
671             # Not a regular VLAN.  For backward compatibility, assume
672             # there is some other sort of VLAN that doesn't have the
673             # '@' in the output and only use what is before a '.'.  If
674             # there is no '.' then this will be the whole interface
675             # name.
676             echo "${_t%%.*}"
677     esac
678 }
679
680 # Check whether an interface is operational
681 interface_monitor ()
682 {
683     _iface="$1"
684
685     _iface_info=$(ip link show "$_iface" 2>&1) || {
686         echo "ERROR: Monitored interface ${_iface} does not exist"
687         return 1
688     }
689
690
691     # If the interface is a virtual one (e.g. VLAN) then get the
692     # underlying interface.
693     _realiface=$(interface_get_real "$_iface_info")
694
695     if _bi=$(get_proc "net/bonding/${_realiface}" 2>/dev/null) ; then
696         # This is a bond: various monitoring strategies
697         echo "$_bi" | grep -q 'Currently Active Slave: None' && {
698             echo "ERROR: No active slaves for bond device ${_realiface}"
699             return 1
700         }
701         echo "$_bi" | grep -q '^MII Status: up' || {
702             echo "ERROR: public network interface ${_realiface} is down"
703             return 1
704         }
705         echo "$_bi" | grep -q '^Bonding Mode: IEEE 802.3ad Dynamic link aggregation' && {
706             # This works around a bug in the driver where the
707             # overall bond status can be up but none of the actual
708             # physical interfaces have a link.
709             echo "$_bi" | grep 'MII Status:' | tail -n +2 | grep -q '^MII Status: up' || {
710                 echo "ERROR: No active slaves for 802.ad bond device ${_realiface}"
711                 return 1
712             }
713         }
714
715         return 0
716     else
717         # Not a bond
718         case "$_iface" in
719             lo*)
720                 # loopback is always working
721                 return 0
722                 ;;
723             ib*)
724                 # we don't know how to test ib links
725                 return 0
726                 ;;
727             *)
728                 ethtool "$_iface" | grep -q 'Link detected: yes' || {
729                     # On some systems, this is not successful when a
730                     # cable is plugged but the interface has not been
731                     # brought up previously. Bring the interface up
732                     # and try again...
733                     ip link set "$_iface" up
734                     ethtool "$_iface" | grep -q 'Link detected: yes' || {
735                         echo "ERROR: No link on the public network interface ${_iface}"
736                         return 1
737                     }
738                 }
739                 return 0
740                 ;;
741         esac
742     fi
743 }
744
745 ########################################################
746 # Simple counters
747 _ctdb_counter_common () {
748     _service_name="${1:-${service_name:-${script_name}}}"
749     _counter_file="${CTDB_SCRIPT_VARDIR}/failcount/${_service_name}"
750     mkdir -p "${_counter_file%/*}" # dirname
751 }
752 ctdb_counter_init () {
753     _ctdb_counter_common "$1"
754
755     >"$_counter_file"
756 }
757 ctdb_counter_incr () {
758     _ctdb_counter_common "$1"
759
760     # unary counting!
761     echo -n 1 >> "$_counter_file"
762 }
763 ctdb_counter_get () {
764     _ctdb_counter_common "$1"
765     # unary counting!
766     stat -c "%s" "$_counter_file" 2>/dev/null || echo 0
767 }
768 ctdb_check_counter () {
769     _msg="${1:-error}"  # "error"  - anything else is silent on fail
770     _op="${2:--ge}"  # an integer operator supported by test
771     _limit="${3:-${service_fail_limit}}"
772     shift 3
773
774     _size=$(ctdb_counter_get "$1")
775
776     _hit=false
777     if [ "$_op" != "%" ] ; then
778         if [ $_size $_op $_limit ] ; then
779             _hit=true
780         fi
781     else
782         if [ $(($_size $_op $_limit)) -eq 0 ] ; then
783             _hit=true
784         fi
785     fi
786     if $_hit ; then
787         if [ "$_msg" = "error" ] ; then
788             echo "ERROR: $_size consecutive failures for $_service_name, marking node unhealthy"
789             exit 1              
790         else
791             return 1
792         fi
793     fi
794 }
795
796 ########################################################
797
798 ctdb_setup_service_state_dir ()
799 {
800     service_state_dir="${CTDB_SCRIPT_VARDIR}/service_state/${1:-${service_name}}"
801     mkdir -p "$service_state_dir" || {
802         echo "Error creating state dir \"$service_state_dir\""
803         exit 1
804     }
805 }
806
807 ########################################################
808 # Managed status history, for auto-start/stop
809
810 _ctdb_managed_common ()
811 {
812     _ctdb_managed_file="${CTDB_SCRIPT_VARDIR}/managed_history/${service_name}"
813 }
814
815 ctdb_service_managed ()
816 {
817     _ctdb_managed_common
818     mkdir -p "${_ctdb_managed_file%/*}" # dirname
819     touch "$_ctdb_managed_file"
820 }
821
822 ctdb_service_unmanaged ()
823 {
824     _ctdb_managed_common
825     rm -f "$_ctdb_managed_file"
826 }
827
828 is_ctdb_previously_managed_service ()
829 {
830     _ctdb_managed_common
831     [ -f "$_ctdb_managed_file" ]
832 }
833
834 ##################################################################
835 # Reconfigure a service on demand
836
837 _ctdb_service_reconfigure_common ()
838 {
839     _d="${CTDB_SCRIPT_VARDIR}/service_status/${service_name}"
840     mkdir -p "$_d"
841     _ctdb_service_reconfigure_flag="$_d/reconfigure"
842 }
843
844 ctdb_service_needs_reconfigure ()
845 {
846     _ctdb_service_reconfigure_common
847     [ -e "$_ctdb_service_reconfigure_flag" ]
848 }
849
850 ctdb_service_set_reconfigure ()
851 {
852     _ctdb_service_reconfigure_common
853     >"$_ctdb_service_reconfigure_flag"
854 }
855
856 ctdb_service_unset_reconfigure ()
857 {
858     _ctdb_service_reconfigure_common
859     rm -f "$_ctdb_service_reconfigure_flag"
860 }
861
862 ctdb_service_reconfigure ()
863 {
864     echo "Reconfiguring service \"${service_name}\"..."
865     ctdb_service_unset_reconfigure
866     service_reconfigure || return $?
867     ctdb_counter_init
868 }
869
870 # Default service_reconfigure() function does nothing.
871 service_reconfigure ()
872 {
873     :
874 }
875
876 ctdb_reconfigure_take_lock ()
877 {
878     _ctdb_service_reconfigure_common
879     _lock="${_d}/reconfigure_lock"
880     mkdir -p "${_lock%/*}" # dirname
881     touch "$_lock"
882
883     (
884         flock 0
885         # This is overkill but will work if we need to extend this to
886         # allow certain events to run multiple times in parallel
887         # (e.g. takeip) and write multiple PIDs to the file.
888         read _locker_event 
889         if [ -n "$_locker_event" ] ; then
890             while read _pid ; do
891                 if [ -n "$_pid" -a "$_pid" != $$ ] && \
892                     kill -0 "$_pid" 2>/dev/null ; then
893                     exit 1
894                 fi
895             done
896         fi
897
898         printf "%s\n%s\n" "$event_name" $$ >"$_lock"
899         exit 0
900     ) <"$_lock"
901 }
902
903 ctdb_reconfigure_release_lock ()
904 {
905     _ctdb_service_reconfigure_common
906     _lock="${_d}/reconfigure_lock"
907
908     rm -f "$_lock"
909 }
910
911 ctdb_replay_monitor_status ()
912 {
913     echo "Replaying previous status for this script due to reconfigure..."
914     # Leading separator ('|') is missing in some versions...
915     _out=$($CTDB scriptstatus -X | grep -E "^\|?monitor\|${script_name}\|")
916     # Output looks like this:
917     # |monitor|60.nfs|1|ERROR|1314764004.030861|1314764004.035514|foo bar|
918     # This is the cheapest way of getting fields in the middle.
919     set -- $(IFS="|" ; echo $_out)
920     _code="$3"
921     _status="$4"
922     # The error output field can include colons so we'll try to
923     # preserve them.  The weak checking at the beginning tries to make
924     # this work for both broken (no leading '|') and fixed output.
925     _out="${_out%|}"
926     _err_out="${_out#*monitor|${script_name}|*|*|*|*|}"
927     case "$_status" in
928         OK) : ;;  # Do nothing special.
929         TIMEDOUT)
930             # Recast this as an error, since we can't exit with the
931             # correct negative number.
932             _code=1
933             _err_out="[Replay of TIMEDOUT scriptstatus - note incorrect return code.] ${_err_out}"
934             ;;
935         DISABLED)
936             # Recast this as an OK, since we can't exit with the
937             # correct negative number.
938             _code=0
939             _err_out="[Replay of DISABLED scriptstatus - note incorrect return code.] ${_err_out}"
940             ;;
941         *) : ;;  # Must be ERROR, do nothing special.
942     esac
943     if [ -n "$_err_out" ] ; then
944         echo "$_err_out"
945     fi
946     exit $_code
947 }
948
949 ctdb_service_check_reconfigure ()
950 {
951     assert_service_name
952
953     # We only care about some events in this function.  For others we
954     # return now.
955     case "$event_name" in
956         monitor|ipreallocated|reconfigure) : ;;
957         *) return 0 ;;
958     esac
959
960     if ctdb_reconfigure_take_lock ; then
961         # No events covered by this function are running, so proceed
962         # with gay abandon.
963         case "$event_name" in
964             reconfigure)
965                 (ctdb_service_reconfigure)
966                 exit $?
967                 ;;
968             ipreallocated)
969                 if ctdb_service_needs_reconfigure ; then
970                     ctdb_service_reconfigure
971                 fi
972                 ;;
973         esac
974
975         ctdb_reconfigure_release_lock
976     else
977         # Somebody else is running an event we don't want to collide
978         # with.  We proceed with caution.
979         case "$event_name" in
980             reconfigure)
981                 # Tell whoever called us to retry.
982                 exit 2
983                 ;;
984             ipreallocated)
985                 # Defer any scheduled reconfigure and just run the
986                 # rest of the ipreallocated event, as per the
987                 # eventscript.  There's an assumption here that the
988                 # event doesn't depend on any scheduled reconfigure.
989                 # This is true in the current code.
990                 return 0
991                 ;;
992             monitor)
993                 # There is most likely a reconfigure in progress so
994                 # the service is possibly unstable.  As above, we
995                 # defer any scheduled reconfigured.  We also replay
996                 # the previous monitor status since that's the best
997                 # information we have.
998                 ctdb_replay_monitor_status
999                 ;;
1000         esac
1001     fi
1002 }
1003
1004 ##################################################################
1005 # Does CTDB manage this service? - and associated auto-start/stop
1006
1007 ctdb_compat_managed_service ()
1008 {
1009     if [ "$1" = "yes" -a "$2" = "$service_name" ] ; then
1010         CTDB_MANAGED_SERVICES="$CTDB_MANAGED_SERVICES $2"
1011     fi
1012 }
1013
1014 is_ctdb_managed_service ()
1015 {
1016     assert_service_name
1017
1018     # $t is used just for readability and to allow better accurate
1019     # matching via leading/trailing spaces
1020     t=" $CTDB_MANAGED_SERVICES "
1021
1022     # Return 0 if "<space>$service_name<space>" appears in $t
1023     if [ "${t#* ${service_name} }" != "${t}" ] ; then
1024         return 0
1025     fi
1026
1027     # If above didn't match then update $CTDB_MANAGED_SERVICES for
1028     # backward compatibility and try again.
1029     ctdb_compat_managed_service "$CTDB_MANAGES_VSFTPD"   "vsftpd"
1030     ctdb_compat_managed_service "$CTDB_MANAGES_SAMBA"    "samba"
1031     ctdb_compat_managed_service "$CTDB_MANAGES_WINBIND"  "winbind"
1032     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "apache2"
1033     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "httpd"
1034     ctdb_compat_managed_service "$CTDB_MANAGES_ISCSI"    "iscsi"
1035     ctdb_compat_managed_service "$CTDB_MANAGES_CLAMD"    "clamd"
1036     ctdb_compat_managed_service "$CTDB_MANAGES_NFS"      "nfs"
1037
1038     t=" $CTDB_MANAGED_SERVICES "
1039
1040     # Return 0 if "<space>$service_name<space>" appears in $t
1041     [ "${t#* ${service_name} }" != "${t}" ]
1042 }
1043
1044 ctdb_start_stop_service ()
1045 {
1046     assert_service_name
1047
1048     # Allow service-start/service-stop pseudo-events to start/stop
1049     # services when we're not auto-starting/stopping and we're not
1050     # monitoring.
1051     case "$event_name" in
1052         service-start)
1053             if is_ctdb_managed_service ; then
1054                 die 'service-start event not permitted when service is managed'
1055             fi
1056             if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
1057                 die 'service-start event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
1058             fi
1059             ctdb_service_start
1060             exit $?
1061             ;;
1062         service-stop)
1063             if is_ctdb_managed_service ; then
1064                 die 'service-stop event not permitted when service is managed'
1065             fi
1066             if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
1067                 die 'service-stop event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
1068             fi
1069             ctdb_service_stop
1070             exit $?
1071             ;;
1072     esac
1073
1074     # Do nothing unless configured to...
1075     [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] || return 0
1076
1077     [ "$event_name" = "monitor" ] || return 0
1078
1079     if is_ctdb_managed_service ; then
1080         if ! is_ctdb_previously_managed_service ; then
1081             echo "Starting service \"$service_name\" - now managed"
1082             background_with_logging ctdb_service_start
1083             exit $?
1084         fi
1085     else
1086         if is_ctdb_previously_managed_service ; then
1087             echo "Stopping service \"$service_name\" - no longer managed"
1088             background_with_logging ctdb_service_stop
1089             exit $?
1090         fi
1091     fi
1092 }
1093
1094 ctdb_service_start ()
1095 {
1096     # The service is marked managed if we've ever tried to start it.
1097     ctdb_service_managed
1098
1099     service_start || return $?
1100
1101     ctdb_counter_init
1102     ctdb_check_tcp_init
1103 }
1104
1105 ctdb_service_stop ()
1106 {
1107     ctdb_service_unmanaged
1108     service_stop
1109 }
1110
1111 # Default service_start() and service_stop() functions.
1112  
1113 # These may be overridden in an eventscript.
1114 service_start ()
1115 {
1116     service "$service_name" start
1117 }
1118
1119 service_stop ()
1120 {
1121     service "$service_name" stop
1122 }
1123
1124 ##################################################################
1125
1126 ctdb_standard_event_handler ()
1127 {
1128     :
1129 }
1130
1131 iptables_wrapper ()
1132 {
1133     _family="$1" ; shift
1134     if [ "$_family" = "inet6" ] ; then
1135         _iptables_cmd="ip6tables"
1136     else
1137         _iptables_cmd="iptables"
1138     fi
1139
1140     # iptables doesn't like being re-entered, so flock-wrap it.
1141     flock -w 30 "${CTDB_SCRIPT_VARDIR}/iptables.flock" "$_iptables_cmd" "$@"
1142 }
1143
1144 # AIX (and perhaps others?) doesn't have mktemp
1145 if ! type mktemp >/dev/null 2>&1 ; then
1146     mktemp ()
1147     {
1148         _dir=false
1149         if [ "$1" = "-d" ] ; then
1150             _dir=true
1151             shift
1152         fi
1153         _d="${TMPDIR:-/tmp}"
1154         _hex10=$(dd if=/dev/urandom count=20 2>/dev/null | \
1155             md5sum | \
1156             sed -e 's@\(..........\).*@\1@')
1157         _t="${_d}/tmp.${_hex10}"
1158         (
1159             umask 077
1160             if $_dir ; then
1161                 mkdir "$_t"
1162             else
1163                 >"$_t"
1164             fi
1165         )
1166         echo "$_t"
1167     }
1168 fi
1169
1170 ######################################################################
1171 # NFS callout handling
1172
1173 nfs_callout_init ()
1174 {
1175         if [ -z "$CTDB_NFS_CALLOUT" ] ; then
1176                 CTDB_NFS_CALLOUT="${CTDB_BASE}/nfs-linux-kernel-callout"
1177         fi
1178         # Always export, for statd callout
1179         export CTDB_NFS_CALLOUT
1180
1181         # If the callout wants to use this then it must create it
1182         export CTDB_NFS_CALLOUT_STATE_DIR="${service_state_dir}/callout-state"
1183
1184         # Export, if set, for use by clustered NFS callouts
1185         if [ -n "$CTDB_NFS_STATE_FS_TYPE" ] ; then
1186                 export CTDB_NFS_STATE_FS_TYPE
1187         fi
1188         if [ -n "$CTDB_NFS_STATE_MNT" ] ; then
1189                 export CTDB_NFS_STATE_MNT
1190         fi
1191
1192         nfs_callout_cache="${service_state_dir}/nfs_callout_cache"
1193         nfs_callout_cache_callout="${nfs_callout_cache}/CTDB_NFS_CALLOUT"
1194         nfs_callout_cache_ops="${nfs_callout_cache}/ops"
1195 }
1196
1197 nfs_callout_register ()
1198 {
1199     mkdir -p "$nfs_callout_cache_ops"
1200     rm -f "$nfs_callout_cache_ops"/*
1201
1202     echo "$CTDB_NFS_CALLOUT" >"$nfs_callout_cache_callout"
1203
1204     _t=$(eval "$CTDB_NFS_CALLOUT" "register")
1205     if [ -n "$_t" ] ; then
1206         echo "$_t" |
1207             while IFS="" read _op ; do
1208                 touch "${nfs_callout_cache_ops}/${_op}"
1209             done
1210     else
1211         touch "${nfs_callout_cache_ops}/ALL"
1212     fi
1213 }
1214
1215 nfs_callout ()
1216 {
1217     # Re-run registration if $CTDB_NFS_CALLOUT has changed
1218     _prev=""
1219     if [ -r "$nfs_callout_cache_callout" ] ; then
1220         read _prev <"$nfs_callout_cache_callout"
1221     fi
1222     if [ "$CTDB_NFS_CALLOUT" != "$_prev" ] ; then
1223         nfs_callout_register
1224     fi
1225
1226     # Run the operation if it is registered...
1227     if [ -e "${nfs_callout_cache_ops}/${1}" ] || \
1228            [ -e "${nfs_callout_cache_ops}/ALL" ]; then
1229         eval "$CTDB_NFS_CALLOUT" "$@"
1230     fi
1231 }
1232
1233 ########################################################
1234 # tickle handling
1235 ########################################################
1236
1237 update_tickles ()
1238 {
1239         _port="$1"
1240
1241         tickledir="${CTDB_SCRIPT_VARDIR}/tickles"
1242         mkdir -p "$tickledir"
1243
1244         ctdb_get_pnn
1245
1246         # What public IPs do I hold?
1247         _ips=$($CTDB -X ip | awk -F'|' -v pnn="$pnn" '$3 == pnn {print $2}')
1248
1249         # IPs and port as ss filters
1250         _ip_filter=""
1251         for _ip in $_ips ; do
1252             _ip_filter="${_ip_filter}${_ip_filter:+ || }src [${_ip}]"
1253         done
1254         _port_filter="sport == :${_port}"
1255
1256         # Record connections to our public IPs in a temporary file.
1257         # This temporary file is in CTDB's private state directory and
1258         # $$ is used to avoid a very rare race involving CTDB's script
1259         # debugging.  No security issue, nothing to see here...
1260         _my_connections="${tickledir}/${_port}.connections.$$"
1261         # Parentheses are needed around the filters for precedence but
1262         # the parentheses can't be empty!
1263         ss -tn state established \
1264            "${_ip_filter:+( ${_ip_filter} )}" \
1265            "${_port_filter:+( ${_port_filter} )}" |
1266         awk 'NR > 1 {print $4, $3}' |
1267         sort >"$_my_connections"
1268
1269         # Record our current tickles in a temporary file
1270         _my_tickles="${tickledir}/${_port}.tickles.$$"
1271         for _i in $_ips ; do
1272                 $CTDB -X gettickles "$_i" "$_port" |
1273                 awk -F'|' 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
1274         done |
1275         sort >"$_my_tickles"
1276
1277         # Add tickles for connections that we haven't already got tickles for
1278         comm -23 "$_my_connections" "$_my_tickles" |
1279         while read _src _dst ; do
1280                 $CTDB addtickle "$_src" "$_dst"
1281         done
1282
1283         # Remove tickles for connections that are no longer there
1284         comm -13 "$_my_connections" "$_my_tickles" |
1285         while read _src _dst ; do
1286                 $CTDB deltickle "$_src" "$_dst"
1287         done
1288
1289         rm -f "$_my_connections" "$_my_tickles"
1290
1291         # Remove stale files from killed scripts
1292         find "$tickledir" -type f -mmin +10 | xargs -r rm
1293 }
1294
1295 ########################################################
1296 # load a site local config file
1297 ########################################################
1298
1299 [ -n "$CTDB_RC_LOCAL" -a -x "$CTDB_RC_LOCAL" ] && {
1300         . "$CTDB_RC_LOCAL"
1301 }
1302
1303 [ -x "${CTDB_BASE}/rc.local" ] && {
1304         . "${CTDB_BASE}/rc.local"
1305 }
1306
1307 [ -d "${CTDB_BASE}/rc.local.d" ] && {
1308         for i in "${CTDB_BASE}/rc.local.d"/* ; do
1309                 [ -x "$i" ] && . "$i"
1310         done
1311 }
1312
1313 script_name="${0##*/}"       # basename
1314 service_fail_limit=1
1315 event_name="$1"