ctdb-scripts: Fix a bug in counter checking
[samba.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     if [ $# -le 3 ] ; then
773             shift $#
774     else
775             shift 3
776     fi
777
778     _size=$(ctdb_counter_get "$1")
779
780     _hit=false
781     if [ "$_op" != "%" ] ; then
782         if [ $_size $_op $_limit ] ; then
783             _hit=true
784         fi
785     else
786         if [ $(($_size $_op $_limit)) -eq 0 ] ; then
787             _hit=true
788         fi
789     fi
790     if $_hit ; then
791         if [ "$_msg" = "error" ] ; then
792             echo "ERROR: $_size consecutive failures for $_service_name, marking node unhealthy"
793             exit 1              
794         else
795             return 1
796         fi
797     fi
798 }
799
800 ########################################################
801
802 ctdb_setup_service_state_dir ()
803 {
804     service_state_dir="${CTDB_SCRIPT_VARDIR}/service_state/${1:-${service_name}}"
805     mkdir -p "$service_state_dir" || {
806         echo "Error creating state dir \"$service_state_dir\""
807         exit 1
808     }
809 }
810
811 ########################################################
812 # Managed status history, for auto-start/stop
813
814 _ctdb_managed_common ()
815 {
816     _ctdb_managed_file="${CTDB_SCRIPT_VARDIR}/managed_history/${service_name}"
817 }
818
819 ctdb_service_managed ()
820 {
821     _ctdb_managed_common
822     mkdir -p "${_ctdb_managed_file%/*}" # dirname
823     touch "$_ctdb_managed_file"
824 }
825
826 ctdb_service_unmanaged ()
827 {
828     _ctdb_managed_common
829     rm -f "$_ctdb_managed_file"
830 }
831
832 is_ctdb_previously_managed_service ()
833 {
834     _ctdb_managed_common
835     [ -f "$_ctdb_managed_file" ]
836 }
837
838 ##################################################################
839 # Reconfigure a service on demand
840
841 _ctdb_service_reconfigure_common ()
842 {
843     _d="${CTDB_SCRIPT_VARDIR}/service_status/${service_name}"
844     mkdir -p "$_d"
845     _ctdb_service_reconfigure_flag="$_d/reconfigure"
846 }
847
848 ctdb_service_needs_reconfigure ()
849 {
850     _ctdb_service_reconfigure_common
851     [ -e "$_ctdb_service_reconfigure_flag" ]
852 }
853
854 ctdb_service_set_reconfigure ()
855 {
856     _ctdb_service_reconfigure_common
857     >"$_ctdb_service_reconfigure_flag"
858 }
859
860 ctdb_service_unset_reconfigure ()
861 {
862     _ctdb_service_reconfigure_common
863     rm -f "$_ctdb_service_reconfigure_flag"
864 }
865
866 ctdb_service_reconfigure ()
867 {
868     echo "Reconfiguring service \"${service_name}\"..."
869     ctdb_service_unset_reconfigure
870     service_reconfigure || return $?
871     ctdb_counter_init
872 }
873
874 # Default service_reconfigure() function does nothing.
875 service_reconfigure ()
876 {
877     :
878 }
879
880 ctdb_reconfigure_take_lock ()
881 {
882     _ctdb_service_reconfigure_common
883     _lock="${_d}/reconfigure_lock"
884     mkdir -p "${_lock%/*}" # dirname
885     touch "$_lock"
886
887     (
888         flock 0
889         # This is overkill but will work if we need to extend this to
890         # allow certain events to run multiple times in parallel
891         # (e.g. takeip) and write multiple PIDs to the file.
892         read _locker_event 
893         if [ -n "$_locker_event" ] ; then
894             while read _pid ; do
895                 if [ -n "$_pid" -a "$_pid" != $$ ] && \
896                     kill -0 "$_pid" 2>/dev/null ; then
897                     exit 1
898                 fi
899             done
900         fi
901
902         printf "%s\n%s\n" "$event_name" $$ >"$_lock"
903         exit 0
904     ) <"$_lock"
905 }
906
907 ctdb_reconfigure_release_lock ()
908 {
909     _ctdb_service_reconfigure_common
910     _lock="${_d}/reconfigure_lock"
911
912     rm -f "$_lock"
913 }
914
915 ctdb_replay_monitor_status ()
916 {
917     echo "Replaying previous status for this script due to reconfigure..."
918     # Leading separator ('|') is missing in some versions...
919     _out=$($CTDB scriptstatus -X | grep -E "^\|?monitor\|${script_name}\|")
920     # Output looks like this:
921     # |monitor|60.nfs|1|ERROR|1314764004.030861|1314764004.035514|foo bar|
922     # This is the cheapest way of getting fields in the middle.
923     set -- $(IFS="|" ; echo $_out)
924     _code="$3"
925     _status="$4"
926     # The error output field can include colons so we'll try to
927     # preserve them.  The weak checking at the beginning tries to make
928     # this work for both broken (no leading '|') and fixed output.
929     _out="${_out%|}"
930     _err_out="${_out#*monitor|${script_name}|*|*|*|*|}"
931     case "$_status" in
932         OK) : ;;  # Do nothing special.
933         TIMEDOUT)
934             # Recast this as an error, since we can't exit with the
935             # correct negative number.
936             _code=1
937             _err_out="[Replay of TIMEDOUT scriptstatus - note incorrect return code.] ${_err_out}"
938             ;;
939         DISABLED)
940             # Recast this as an OK, since we can't exit with the
941             # correct negative number.
942             _code=0
943             _err_out="[Replay of DISABLED scriptstatus - note incorrect return code.] ${_err_out}"
944             ;;
945         *) : ;;  # Must be ERROR, do nothing special.
946     esac
947     if [ -n "$_err_out" ] ; then
948         echo "$_err_out"
949     fi
950     exit $_code
951 }
952
953 ctdb_service_check_reconfigure ()
954 {
955     assert_service_name
956
957     # We only care about some events in this function.  For others we
958     # return now.
959     case "$event_name" in
960         monitor|ipreallocated|reconfigure) : ;;
961         *) return 0 ;;
962     esac
963
964     if ctdb_reconfigure_take_lock ; then
965         # No events covered by this function are running, so proceed
966         # with gay abandon.
967         case "$event_name" in
968             reconfigure)
969                 (ctdb_service_reconfigure)
970                 exit $?
971                 ;;
972             ipreallocated)
973                 if ctdb_service_needs_reconfigure ; then
974                     ctdb_service_reconfigure
975                 fi
976                 ;;
977         esac
978
979         ctdb_reconfigure_release_lock
980     else
981         # Somebody else is running an event we don't want to collide
982         # with.  We proceed with caution.
983         case "$event_name" in
984             reconfigure)
985                 # Tell whoever called us to retry.
986                 exit 2
987                 ;;
988             ipreallocated)
989                 # Defer any scheduled reconfigure and just run the
990                 # rest of the ipreallocated event, as per the
991                 # eventscript.  There's an assumption here that the
992                 # event doesn't depend on any scheduled reconfigure.
993                 # This is true in the current code.
994                 return 0
995                 ;;
996             monitor)
997                 # There is most likely a reconfigure in progress so
998                 # the service is possibly unstable.  As above, we
999                 # defer any scheduled reconfigured.  We also replay
1000                 # the previous monitor status since that's the best
1001                 # information we have.
1002                 ctdb_replay_monitor_status
1003                 ;;
1004         esac
1005     fi
1006 }
1007
1008 ##################################################################
1009 # Does CTDB manage this service? - and associated auto-start/stop
1010
1011 ctdb_compat_managed_service ()
1012 {
1013     if [ "$1" = "yes" -a "$2" = "$service_name" ] ; then
1014         CTDB_MANAGED_SERVICES="$CTDB_MANAGED_SERVICES $2"
1015     fi
1016 }
1017
1018 is_ctdb_managed_service ()
1019 {
1020     assert_service_name
1021
1022     # $t is used just for readability and to allow better accurate
1023     # matching via leading/trailing spaces
1024     t=" $CTDB_MANAGED_SERVICES "
1025
1026     # Return 0 if "<space>$service_name<space>" appears in $t
1027     if [ "${t#* ${service_name} }" != "${t}" ] ; then
1028         return 0
1029     fi
1030
1031     # If above didn't match then update $CTDB_MANAGED_SERVICES for
1032     # backward compatibility and try again.
1033     ctdb_compat_managed_service "$CTDB_MANAGES_VSFTPD"   "vsftpd"
1034     ctdb_compat_managed_service "$CTDB_MANAGES_SAMBA"    "samba"
1035     ctdb_compat_managed_service "$CTDB_MANAGES_WINBIND"  "winbind"
1036     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "apache2"
1037     ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "httpd"
1038     ctdb_compat_managed_service "$CTDB_MANAGES_ISCSI"    "iscsi"
1039     ctdb_compat_managed_service "$CTDB_MANAGES_CLAMD"    "clamd"
1040     ctdb_compat_managed_service "$CTDB_MANAGES_NFS"      "nfs"
1041
1042     t=" $CTDB_MANAGED_SERVICES "
1043
1044     # Return 0 if "<space>$service_name<space>" appears in $t
1045     [ "${t#* ${service_name} }" != "${t}" ]
1046 }
1047
1048 ctdb_start_stop_service ()
1049 {
1050     assert_service_name
1051
1052     # Allow service-start/service-stop pseudo-events to start/stop
1053     # services when we're not auto-starting/stopping and we're not
1054     # monitoring.
1055     case "$event_name" in
1056         service-start)
1057             if is_ctdb_managed_service ; then
1058                 die 'service-start event not permitted when service is managed'
1059             fi
1060             if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
1061                 die 'service-start event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
1062             fi
1063             ctdb_service_start
1064             exit $?
1065             ;;
1066         service-stop)
1067             if is_ctdb_managed_service ; then
1068                 die 'service-stop event not permitted when service is managed'
1069             fi
1070             if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
1071                 die 'service-stop event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
1072             fi
1073             ctdb_service_stop
1074             exit $?
1075             ;;
1076     esac
1077
1078     # Do nothing unless configured to...
1079     [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] || return 0
1080
1081     [ "$event_name" = "monitor" ] || return 0
1082
1083     if is_ctdb_managed_service ; then
1084         if ! is_ctdb_previously_managed_service ; then
1085             echo "Starting service \"$service_name\" - now managed"
1086             background_with_logging ctdb_service_start
1087             exit $?
1088         fi
1089     else
1090         if is_ctdb_previously_managed_service ; then
1091             echo "Stopping service \"$service_name\" - no longer managed"
1092             background_with_logging ctdb_service_stop
1093             exit $?
1094         fi
1095     fi
1096 }
1097
1098 ctdb_service_start ()
1099 {
1100     # The service is marked managed if we've ever tried to start it.
1101     ctdb_service_managed
1102
1103     service_start || return $?
1104
1105     ctdb_counter_init
1106     ctdb_check_tcp_init
1107 }
1108
1109 ctdb_service_stop ()
1110 {
1111     ctdb_service_unmanaged
1112     service_stop
1113 }
1114
1115 # Default service_start() and service_stop() functions.
1116  
1117 # These may be overridden in an eventscript.
1118 service_start ()
1119 {
1120     service "$service_name" start
1121 }
1122
1123 service_stop ()
1124 {
1125     service "$service_name" stop
1126 }
1127
1128 ##################################################################
1129
1130 ctdb_standard_event_handler ()
1131 {
1132     :
1133 }
1134
1135 iptables_wrapper ()
1136 {
1137     _family="$1" ; shift
1138     if [ "$_family" = "inet6" ] ; then
1139         _iptables_cmd="ip6tables"
1140     else
1141         _iptables_cmd="iptables"
1142     fi
1143
1144     # iptables doesn't like being re-entered, so flock-wrap it.
1145     flock -w 30 "${CTDB_SCRIPT_VARDIR}/iptables.flock" "$_iptables_cmd" "$@"
1146 }
1147
1148 # AIX (and perhaps others?) doesn't have mktemp
1149 if ! type mktemp >/dev/null 2>&1 ; then
1150     mktemp ()
1151     {
1152         _dir=false
1153         if [ "$1" = "-d" ] ; then
1154             _dir=true
1155             shift
1156         fi
1157         _d="${TMPDIR:-/tmp}"
1158         _hex10=$(dd if=/dev/urandom count=20 2>/dev/null | \
1159             md5sum | \
1160             sed -e 's@\(..........\).*@\1@')
1161         _t="${_d}/tmp.${_hex10}"
1162         (
1163             umask 077
1164             if $_dir ; then
1165                 mkdir "$_t"
1166             else
1167                 >"$_t"
1168             fi
1169         )
1170         echo "$_t"
1171     }
1172 fi
1173
1174 ######################################################################
1175 # NFS callout handling
1176
1177 nfs_callout_init ()
1178 {
1179         if [ -z "$CTDB_NFS_CALLOUT" ] ; then
1180                 CTDB_NFS_CALLOUT="${CTDB_BASE}/nfs-linux-kernel-callout"
1181         fi
1182         # Always export, for statd callout
1183         export CTDB_NFS_CALLOUT
1184
1185         # If the callout wants to use this then it must create it
1186         export CTDB_NFS_CALLOUT_STATE_DIR="${service_state_dir}/callout-state"
1187
1188         # Export, if set, for use by clustered NFS callouts
1189         if [ -n "$CTDB_NFS_STATE_FS_TYPE" ] ; then
1190                 export CTDB_NFS_STATE_FS_TYPE
1191         fi
1192         if [ -n "$CTDB_NFS_STATE_MNT" ] ; then
1193                 export CTDB_NFS_STATE_MNT
1194         fi
1195
1196         nfs_callout_cache="${service_state_dir}/nfs_callout_cache"
1197         nfs_callout_cache_callout="${nfs_callout_cache}/CTDB_NFS_CALLOUT"
1198         nfs_callout_cache_ops="${nfs_callout_cache}/ops"
1199 }
1200
1201 nfs_callout_register ()
1202 {
1203     mkdir -p "$nfs_callout_cache_ops"
1204     rm -f "$nfs_callout_cache_ops"/*
1205
1206     echo "$CTDB_NFS_CALLOUT" >"$nfs_callout_cache_callout"
1207
1208     _t=$(eval "$CTDB_NFS_CALLOUT" "register")
1209     if [ -n "$_t" ] ; then
1210         echo "$_t" |
1211             while IFS="" read _op ; do
1212                 touch "${nfs_callout_cache_ops}/${_op}"
1213             done
1214     else
1215         touch "${nfs_callout_cache_ops}/ALL"
1216     fi
1217 }
1218
1219 nfs_callout ()
1220 {
1221     # Re-run registration if $CTDB_NFS_CALLOUT has changed
1222     _prev=""
1223     if [ -r "$nfs_callout_cache_callout" ] ; then
1224         read _prev <"$nfs_callout_cache_callout"
1225     fi
1226     if [ "$CTDB_NFS_CALLOUT" != "$_prev" ] ; then
1227         nfs_callout_register
1228     fi
1229
1230     # Run the operation if it is registered...
1231     if [ -e "${nfs_callout_cache_ops}/${1}" ] || \
1232            [ -e "${nfs_callout_cache_ops}/ALL" ]; then
1233         eval "$CTDB_NFS_CALLOUT" "$@"
1234     fi
1235 }
1236
1237 ########################################################
1238 # tickle handling
1239 ########################################################
1240
1241 update_tickles ()
1242 {
1243         _port="$1"
1244
1245         tickledir="${CTDB_SCRIPT_VARDIR}/tickles"
1246         mkdir -p "$tickledir"
1247
1248         ctdb_get_pnn
1249
1250         # What public IPs do I hold?
1251         _ips=$($CTDB -X ip | awk -F'|' -v pnn="$pnn" '$3 == pnn {print $2}')
1252
1253         # IPs and port as ss filters
1254         _ip_filter=""
1255         for _ip in $_ips ; do
1256             _ip_filter="${_ip_filter}${_ip_filter:+ || }src [${_ip}]"
1257         done
1258         _port_filter="sport == :${_port}"
1259
1260         # Record connections to our public IPs in a temporary file.
1261         # This temporary file is in CTDB's private state directory and
1262         # $$ is used to avoid a very rare race involving CTDB's script
1263         # debugging.  No security issue, nothing to see here...
1264         _my_connections="${tickledir}/${_port}.connections.$$"
1265         # Parentheses are needed around the filters for precedence but
1266         # the parentheses can't be empty!
1267         ss -tn state established \
1268            "${_ip_filter:+( ${_ip_filter} )}" \
1269            "${_port_filter:+( ${_port_filter} )}" |
1270         awk 'NR > 1 {print $4, $3}' |
1271         sort >"$_my_connections"
1272
1273         # Record our current tickles in a temporary file
1274         _my_tickles="${tickledir}/${_port}.tickles.$$"
1275         for _i in $_ips ; do
1276                 $CTDB -X gettickles "$_i" "$_port" |
1277                 awk -F'|' 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
1278         done |
1279         sort >"$_my_tickles"
1280
1281         # Add tickles for connections that we haven't already got tickles for
1282         comm -23 "$_my_connections" "$_my_tickles" |
1283         while read _src _dst ; do
1284                 $CTDB addtickle "$_src" "$_dst"
1285         done
1286
1287         # Remove tickles for connections that are no longer there
1288         comm -13 "$_my_connections" "$_my_tickles" |
1289         while read _src _dst ; do
1290                 $CTDB deltickle "$_src" "$_dst"
1291         done
1292
1293         rm -f "$_my_connections" "$_my_tickles"
1294
1295         # Remove stale files from killed scripts
1296         find "$tickledir" -type f -mmin +10 | xargs -r rm
1297 }
1298
1299 ########################################################
1300 # load a site local config file
1301 ########################################################
1302
1303 [ -n "$CTDB_RC_LOCAL" -a -x "$CTDB_RC_LOCAL" ] && {
1304         . "$CTDB_RC_LOCAL"
1305 }
1306
1307 [ -x "${CTDB_BASE}/rc.local" ] && {
1308         . "${CTDB_BASE}/rc.local"
1309 }
1310
1311 [ -d "${CTDB_BASE}/rc.local.d" ] && {
1312         for i in "${CTDB_BASE}/rc.local.d"/* ; do
1313                 [ -x "$i" ] && . "$i"
1314         done
1315 }
1316
1317 script_name="${0##*/}"       # basename
1318 service_fail_limit=1
1319 event_name="$1"