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