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