020a19a7c3f060644c2557303c42aafe40a035a7
[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/scripts"
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 load_system_config ()
60 {
61         if [ -z "$1" ] ; then
62                 return
63         fi
64
65         if [ -f "${CTDB_SYS_ETCDIR}/sysconfig/$1" ]; then
66                 . "${CTDB_SYS_ETCDIR}/sysconfig/$1"
67         elif [ -f "${CTDB_SYS_ETCDIR}/default/$1" ]; then
68                 . "${CTDB_SYS_ETCDIR}/default/$1"
69         fi
70 }
71
72 loadconfig() {
73
74         load_system_config "ctdb"
75
76         _config="${CTDB_BASE}/ctdbd.conf"
77         if [ -r "$_config" ] ; then
78                 . "$_config"
79         fi
80         rewrite_ctdb_options
81 }
82
83 load_script_options ()
84 {
85         loadconfig
86
87         _options="${0%.script}.options"
88
89         if [ -r "$_options" ] ; then
90                 . "$_options"
91         fi
92 }
93
94 ##############################################################
95
96 die ()
97 {
98     _msg="$1"
99     _rc="${2:-1}"
100
101     echo "$_msg" >&2
102     exit "$_rc"
103 }
104
105 # Log given message or stdin to either syslog or a CTDB log file
106 # $1 is the tag passed to logger if syslog is in use.
107 script_log ()
108 {
109     _tag="$1" ; shift
110
111     case "$CTDB_LOGGING" in
112         file:*|"")
113             if [ -n "$CTDB_LOGGING" ] ; then
114                 _file="${CTDB_LOGGING#file:}"
115             else
116                 _file="/usr/local/var/log/log.ctdb"
117             fi
118             {
119                 if [ -n "$*" ] ; then
120                     echo "$*"
121                 else
122                     cat
123                 fi
124             } >>"$_file"
125             ;;
126         *)
127             # Handle all syslog:* variants here too.  There's no tool to do
128             # the lossy things, so just use logger.
129             logger -t "ctdbd: ${_tag}" "$@"
130             ;;
131     esac
132 }
133
134 # When things are run in the background in an eventscript then logging
135 # output might get lost.  This is the "solution".  :-)
136 background_with_logging ()
137 {
138     (
139         "$@" 2>&1 </dev/null |
140         script_log "${script_name}&"
141     )&
142
143     return 0
144 }
145
146 ##############################################################
147 # check number of args for different events
148 ctdb_check_args ()
149 {
150     case "$1" in
151         takeip|releaseip)
152             if [ $# != 4 ]; then
153                 echo "ERROR: must supply interface, IP and maskbits"
154                 exit 1
155             fi
156             ;;
157         updateip)
158             if [ $# != 5 ]; then
159                 echo "ERROR: must supply old interface, new interface, IP and maskbits"
160                 exit 1
161             fi
162             ;;
163     esac
164 }
165
166 ##############################################################
167 # determine on what type of system (init style) we are running
168 detect_init_style()
169 {
170         # only do detection if not already set:
171         if [ -n "$CTDB_INIT_STYLE" ] ; then
172                 return
173         fi
174
175         if [ -x /sbin/startproc ]; then
176                 CTDB_INIT_STYLE="suse"
177         elif [ -x /sbin/start-stop-daemon ]; then
178                 CTDB_INIT_STYLE="debian"
179         else
180                 CTDB_INIT_STYLE="redhat"
181         fi
182 }
183
184 ######################################################
185 # simulate /sbin/service on platforms that don't have it
186 # _service() makes it easier to hook the service() function for
187 # testing.
188 _service ()
189 {
190   _service_name="$1"
191   _op="$2"
192
193   # do nothing, when no service was specified
194   [ -z "$_service_name" ] && return
195
196   if [ -x /sbin/service ]; then
197       $_nice /sbin/service "$_service_name" "$_op"
198   elif [ -x /usr/sbin/service ]; then
199       $_nice /usr/sbin/service "$_service_name" "$_op"
200   elif [ -x /bin/systemctl ]; then
201       $_nice /bin/systemctl "$_op" "$_service_name"
202   elif [ -x "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" ]; then
203       $_nice "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" "$_op"
204   elif [ -x "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" ]; then
205       $_nice "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" "$_op"
206   fi
207 }
208
209 service()
210 {
211     _nice=""
212     _service "$@"
213 }
214
215 ######################################################
216 # simulate /sbin/service (niced) on platforms that don't have it
217 nice_service()
218 {
219     _nice="nice"
220     _service "$@"
221 }
222
223 ######################################################
224 # Cached retrieval of PNN from local node.  This never changes so why
225 # open a client connection to the server each time this is needed?
226 ctdb_get_pnn ()
227 {
228     _pnn_file="${CTDB_SCRIPT_VARDIR}/my-pnn"
229     if [ ! -f "$_pnn_file" ] ; then
230         $CTDB pnn >"$_pnn_file"
231     fi
232
233     cat "$_pnn_file"
234 }
235
236 # Cached retrieval of private IP address from local node.  This never
237 # changes.
238 ctdb_get_ip_address ()
239 {
240     _ip_addr_file="${CTDB_SCRIPT_VARDIR}/my-ip-address"
241     if [ ! -f "$_ip_addr_file" ] ; then
242         $CTDB -X nodestatus |
243             awk -F '|' 'NR == 2 { print $3 }' >"$_ip_addr_file"
244     fi
245
246     # ip_address is used by caller
247     # shellcheck disable=SC2034
248     cat "$_ip_addr_file"
249 }
250
251 ######################################################
252 # wrapper around /proc/ settings to allow them to be hooked
253 # for testing
254 # 1st arg is relative path under /proc/, 2nd arg is value to set
255 set_proc ()
256 {
257     echo "$2" >"/proc/$1"
258 }
259
260 set_proc_maybe ()
261 {
262     if [ -w "/proc/$1" ] ; then
263         set_proc "$1" "$2"
264     fi
265 }
266
267 ######################################################
268 # wrapper around getting file contents from /proc/ to allow
269 # this to be hooked for testing
270 # 1st arg is relative path under /proc/
271 get_proc ()
272 {
273     cat "/proc/$1"
274 }
275
276 ######################################################
277 # Print up to $_max kernel stack traces for processes named $_program
278 program_stack_traces ()
279 {
280     _prog="$1"
281     _max="${2:-1}"
282
283     _count=1
284     for _pid in $(pidof "$_prog") ; do
285         [ "$_count" -le "$_max" ] || break
286
287         # Do this first to avoid racing with process exit
288         _stack=$(get_proc "${_pid}/stack" 2>/dev/null)
289         if [ -n "$_stack" ] ; then
290             echo "Stack trace for ${_prog}[${_pid}]:"
291             echo "$_stack"
292             _count=$((_count + 1))
293         fi
294     done
295 }
296
297 ######################################################
298 # Ensure $service_name is set
299 assert_service_name ()
300 {
301         # service_name is set by the event script
302         # shellcheck disable=SC2154
303         [ -n "$service_name" ] || die "INTERNAL ERROR: \$service_name not set"
304 }
305
306 ######################################################
307 # check a set of directories is available
308 # return 1 on a missing directory
309 # directories are read from stdin
310 ######################################################
311 ctdb_check_directories_probe()
312 {
313     while IFS="" read d ; do
314         case "$d" in
315             *%*)
316                 continue
317                 ;;
318             *)
319                 [ -d "${d}/." ] || return 1
320         esac
321     done
322 }
323
324 ######################################################
325 # check a set of directories is available
326 # directories are read from stdin
327 ######################################################
328 ctdb_check_directories()
329 {
330     ctdb_check_directories_probe || {
331         echo "ERROR: $service_name directory \"$d\" not available"
332         exit 1
333     }
334 }
335
336 ######################################################
337 # check a set of tcp ports
338 # usage: ctdb_check_tcp_ports <ports...>
339 ######################################################
340
341 # Check whether something is listening on all of the given TCP ports
342 # using the "ctdb checktcpport" command.
343 ctdb_check_tcp_ports()
344 {
345         if [ -z "$1" ] ; then
346                 echo "INTERNAL ERROR: ctdb_check_tcp_ports - no ports specified"
347                 exit 1
348         fi
349
350         for _p ; do  # process each function argument (port)
351                 _cmd="$CTDB checktcpport $_p"
352                 _out=$($_cmd 2>&1)
353                 _ret=$?
354                 case "$_ret" in
355                 0)
356                         echo "$service_name not listening on TCP port $_p"
357                         return 1
358                         ;;
359                 98)
360                         # Couldn't bind, something already listening, next port
361                         continue
362                         ;;
363                 *)
364                         echo "unexpected error (${_ret}) running \"${_cmd}\""
365                         if [ -n "$_out" ] ; then
366                                 echo "$_out"
367                         fi
368                         return $_ret
369                         ;;
370                 esac
371         done
372
373         # All ports listening
374         return 0
375 }
376
377 ######################################################
378 # check a unix socket
379 # usage: ctdb_check_unix_socket SOCKPATH
380 ######################################################
381 ctdb_check_unix_socket()
382 {
383         _sockpath="$1"
384
385         if [ -z "$_sockpath" ] ; then
386                 echo "ERROR: ctdb_check_unix_socket() requires socket path"
387                 return 1
388         fi
389
390         _out=$(ss -l -x "src ${_sockpath}" | tail -n +2)
391         if [ -z "$_out" ] ; then
392                 echo "ERROR: ${service_name} not listening on ${_sockpath}"
393                 return 1
394         fi
395 }
396
397 ################################################
398 # kill off any TCP connections with the given IP
399 ################################################
400 kill_tcp_connections ()
401 {
402     _iface="$1"
403     _ip="$2"
404
405     _oneway=false
406     if [ "$3" = "oneway" ] ; then
407         _oneway=true
408     fi
409
410     get_tcp_connections_for_ip "$_ip" | {
411         _killcount=0
412         _connections=""
413         _nl="
414 "
415         while read _dst _src; do
416             _destport="${_dst##*:}"
417             __oneway=$_oneway
418             case $_destport in
419                 # we only do one-way killtcp for CIFS
420                 139|445) __oneway=true ;;
421             esac
422
423             _connections="${_connections}${_nl}${_src} ${_dst}"
424             if ! $__oneway ; then
425                 _connections="${_connections}${_nl}${_dst} ${_src}"
426             fi
427
428             _killcount=$((_killcount + 1))
429         done
430
431         if [ $_killcount -eq 0 ] ; then
432             return
433         fi
434
435         echo "$_connections" | \
436                 "${CTDB_HELPER_BINDIR}/ctdb_killtcp" "$_iface" || {
437                 echo "Failed to kill TCP connections"
438                 return
439         }
440
441         _connections=$(get_tcp_connections_for_ip "$_ip")
442         if [ -z "$_connections" ] ; then
443                 _remaining=0
444         else
445                 _remaining=$(echo "$_connections" | wc -l)
446         fi
447
448         _actually_killed=$((_killcount - _remaining))
449
450         _t="${_actually_killed}/${_killcount}"
451         echo "Killed ${_t} TCP connections to released IP $_ip"
452
453         if [ -n "$_connections" ] ; then
454                 echo "Remaining connections:"
455                 echo "$_connections" | sed -e 's|^|  |'
456         fi
457     }
458 }
459
460 ##################################################################
461 # kill off the local end for any TCP connections with the given IP
462 ##################################################################
463 kill_tcp_connections_local_only ()
464 {
465     kill_tcp_connections "$@" "oneway"
466 }
467
468 ##################################################################
469 # tickle any TCP connections with the given IP
470 ##################################################################
471 tickle_tcp_connections ()
472 {
473     _ip="$1"
474
475     # Get connections, both directions
476     _conns=$(get_tcp_connections_for_ip "$_ip" | \
477                     awk '{ print $1, $2 ; print $2, $1 }')
478
479     echo "$_conns" | awk '{ print "Tickle TCP connection", $1, $2 }'
480     echo "$_conns" | ctdb tickle
481 }
482
483 get_tcp_connections_for_ip ()
484 {
485     _ip="$1"
486
487     ss -tn state established "src [$_ip]" | awk 'NR > 1 {print $3, $4}'
488 }
489
490 ########################################################
491
492 add_ip_to_iface ()
493 {
494     _iface=$1
495     _ip=$2
496     _maskbits=$3
497
498     # Ensure interface is up
499     ip link set "$_iface" up || \
500         die "Failed to bringup interface $_iface"
501
502     # Only need to define broadcast for IPv4
503     case "$_ip" in
504         *:*) _bcast=""      ;;
505         *)   _bcast="brd +" ;;
506     esac
507
508     # Intentionally unquoted multi-word value here
509     # shellcheck disable=SC2086
510     ip addr add "$_ip/$_maskbits" $_bcast dev "$_iface" || {
511         echo "Failed to add $_ip/$_maskbits on dev $_iface"
512         return 1
513     }
514
515     # Wait 5 seconds for IPv6 addresses to stop being tentative...
516     if [ -z "$_bcast" ] ; then
517         for _x in $(seq 1 10) ; do
518             ip addr show to "${_ip}/128" | grep -q "tentative" || break
519             sleep 0.5
520         done
521
522         # If the address was a duplicate then it won't be on the
523         # interface so flag an error.
524         _t=$(ip addr show to "${_ip}/128")
525         case "$_t" in
526             "")
527                 echo "Failed to add $_ip/$_maskbits on dev $_iface"
528                 return 1
529                 ;;
530             *tentative*|*dadfailed*)
531                 echo "Failed to add $_ip/$_maskbits on dev $_iface"
532                 ip addr del "$_ip/$_maskbits" dev "$_iface"
533                 return 1
534                 ;;
535         esac
536     fi
537 }
538
539 delete_ip_from_iface()
540 {
541     _iface=$1
542     _ip=$2
543     _maskbits=$3
544
545     # This could be set globally for all interfaces but it is probably
546     # better to avoid surprises, so limit it the interfaces where CTDB
547     # has public IP addresses.  There isn't anywhere else convenient
548     # to do this so just set it each time.  This is much cheaper than
549     # remembering and re-adding secondaries.
550     set_proc "sys/net/ipv4/conf/${_iface}/promote_secondaries" 1
551
552     ip addr del "$_ip/$_maskbits" dev "$_iface" || {
553         echo "Failed to del $_ip on dev $_iface"
554         return 1
555     }
556 }
557
558 # If the given IP is hosted then print 2 items: maskbits and iface
559 ip_maskbits_iface ()
560 {
561     _addr="$1"
562
563     case "$_addr" in
564         *:*) _bits=128 ;;
565         *)   _bits=32  ;;
566     esac
567     ip addr show to "${_addr}/${_bits}" 2>/dev/null | \
568         awk 'NR == 1 { iface = $2; sub(":$", "", iface) ;
569                        sub("@.*", "", iface) }
570              $1 ~ /inet/ { mask = $2; sub(".*/", "", mask);
571                            print mask, iface }'
572 }
573
574 drop_ip ()
575 {
576     _addr="${1%/*}"  # Remove optional maskbits
577
578     # Intentional word splitting here
579     # shellcheck disable=SC2046
580     set -- $(ip_maskbits_iface "$_addr")
581     if [ -n "$1" ] ; then
582         _maskbits="$1"
583         _iface="$2"
584         echo "Removing public address $_addr/$_maskbits from device $_iface"
585         delete_ip_from_iface "$_iface" "$_addr" "$_maskbits" >/dev/null 2>&1
586     fi
587 }
588
589 drop_all_public_ips ()
590 {
591         # _x is intentionally ignored
592         # shellcheck disable=SC2034
593         while read _ip _x ; do
594                 drop_ip "$_ip"
595         done <"${CTDB_BASE}/public_addresses"
596 }
597
598 flush_route_cache ()
599 {
600     set_proc_maybe sys/net/ipv4/route/flush 1
601     set_proc_maybe sys/net/ipv6/route/flush 1
602 }
603
604 ########################################################
605 # Interface monitoring
606
607 # If the interface is a virtual one (e.g. VLAN) then get the
608 # underlying interface
609 interface_get_real ()
610 {
611     # Output of "ip link show <iface>"
612     _iface_info="$1"
613
614     # Extract the full interface description to see if it is a VLAN
615     _t=$(echo "$_iface_info" |
616                 awk 'NR == 1 { iface = $2; sub(":$", "", iface) ;
617                                print iface }')
618     case "$_t" in
619         *@*)
620             # VLAN: use the underlying interface, after the '@'
621             echo "${_t##*@}"
622             ;;
623         *)
624             # Not a regular VLAN.  For backward compatibility, assume
625             # there is some other sort of VLAN that doesn't have the
626             # '@' in the output and only use what is before a '.'.  If
627             # there is no '.' then this will be the whole interface
628             # name.
629             echo "${_t%%.*}"
630     esac
631 }
632
633 # Check whether an interface is operational
634 interface_monitor ()
635 {
636     _iface="$1"
637
638     _iface_info=$(ip link show "$_iface" 2>&1) || {
639         echo "ERROR: Monitored interface ${_iface} does not exist"
640         return 1
641     }
642
643
644     # If the interface is a virtual one (e.g. VLAN) then get the
645     # underlying interface.
646     _realiface=$(interface_get_real "$_iface_info")
647
648     if _bi=$(get_proc "net/bonding/${_realiface}" 2>/dev/null) ; then
649         # This is a bond: various monitoring strategies
650         echo "$_bi" | grep -q 'Currently Active Slave: None' && {
651             echo "ERROR: No active slaves for bond device ${_realiface}"
652             return 1
653         }
654         echo "$_bi" | grep -q '^MII Status: up' || {
655             echo "ERROR: public network interface ${_realiface} is down"
656             return 1
657         }
658         echo "$_bi" | grep -q '^Bonding Mode: IEEE 802.3ad Dynamic link aggregation' && {
659             # This works around a bug in the driver where the
660             # overall bond status can be up but none of the actual
661             # physical interfaces have a link.
662             echo "$_bi" | grep 'MII Status:' | tail -n +2 | grep -q '^MII Status: up' || {
663                 echo "ERROR: No active slaves for 802.ad bond device ${_realiface}"
664                 return 1
665             }
666         }
667
668         return 0
669     else
670         # Not a bond
671         case "$_iface" in
672             lo*)
673                 # loopback is always working
674                 return 0
675                 ;;
676             ib*)
677                 # we don't know how to test ib links
678                 return 0
679                 ;;
680             *)
681                 ethtool "$_iface" | grep -q 'Link detected: yes' || {
682                     # On some systems, this is not successful when a
683                     # cable is plugged but the interface has not been
684                     # brought up previously. Bring the interface up
685                     # and try again...
686                     ip link set "$_iface" up
687                     ethtool "$_iface" | grep -q 'Link detected: yes' || {
688                         echo "ERROR: No link on the public network interface ${_iface}"
689                         return 1
690                     }
691                 }
692                 return 0
693                 ;;
694         esac
695     fi
696 }
697
698 ########################################################
699 # Simple counters
700 _ctdb_counter_common ()
701 {
702         [ $# -le 1 ] || die "usage: _ctdb_counter_common [name]"
703
704         if [ $# -eq 1 ] ; then
705                 _counter_name="${1}.failcount"
706         else
707                 _counter_name="failcount"
708         fi
709
710         if [ -z "$script_state_dir" ] ; then
711                 die "ctdb_counter_* functions need ctdb_setup_state_dir()"
712         fi
713
714         _counter_file="${script_state_dir}/${_counter_name}"
715 }
716 # Some code passes an argument
717 # shellcheck disable=SC2120
718 ctdb_counter_init () {
719     _ctdb_counter_common "$1"
720
721     : >"$_counter_file"
722 }
723 ctdb_counter_incr () {
724     _ctdb_counter_common "$1"
725
726     # unary counting using newlines!
727     echo >>"$_counter_file"
728 }
729 ctdb_counter_get () {
730     _ctdb_counter_common "$1"
731     # unary counting!
732     stat -c "%s" "$_counter_file" 2>/dev/null || echo 0
733 }
734
735 ########################################################
736
737 # ctdb_setup_state_dir <type> <name>
738 #   Sets/creates script_state_dir)
739 ctdb_setup_state_dir ()
740 {
741         [ $# -eq 2 ] || die "usage: ctdb_setup_state_dir <type> <name>"
742
743         _type="$1"
744         _name="$2"
745
746         script_state_dir="${CTDB_SCRIPT_VARDIR}/${_type}/${_name}"
747
748         mkdir -p "$script_state_dir" || \
749                 die "Error creating script state dir \"${script_state_dir}\""
750 }
751
752 ##################################################################
753 # Reconfigure a service on demand
754
755 _ctdb_service_reconfigure_common ()
756 {
757         if [ -z "$script_state_dir" ] ; then
758                 die "ctdb_service_*_reconfigure() needs ctdb_setup_state_dir()"
759         fi
760
761         _ctdb_service_reconfigure_flag="${script_state_dir}/need_reconfigure"
762 }
763
764 ctdb_service_needs_reconfigure ()
765 {
766     _ctdb_service_reconfigure_common
767     [ -e "$_ctdb_service_reconfigure_flag" ]
768 }
769
770 ctdb_service_set_reconfigure ()
771 {
772     _ctdb_service_reconfigure_common
773     : >"$_ctdb_service_reconfigure_flag"
774 }
775
776 ctdb_service_unset_reconfigure ()
777 {
778     _ctdb_service_reconfigure_common
779     rm -f "$_ctdb_service_reconfigure_flag"
780 }
781
782 ctdb_service_reconfigure ()
783 {
784     echo "Reconfiguring service \"${service_name}\"..."
785     ctdb_service_unset_reconfigure
786     service_reconfigure || return $?
787     # Intentionally have this use $service_name as default
788     # shellcheck disable=SC2119
789     ctdb_counter_init
790 }
791
792 # Default service_reconfigure() function does nothing.
793 service_reconfigure ()
794 {
795     :
796 }
797
798 # Default service_start() and service_stop() functions.
799
800 # These may be overridden in an eventscript.
801 service_start ()
802 {
803     service "$service_name" start
804 }
805
806 service_stop ()
807 {
808     service "$service_name" stop
809 }
810
811 ##################################################################
812
813 # This exists only for backward compatibility with 3rd party scripts
814 # that call it
815 ctdb_standard_event_handler ()
816 {
817     :
818 }
819
820 iptables_wrapper ()
821 {
822     _family="$1" ; shift
823     if [ "$_family" = "inet6" ] ; then
824         _iptables_cmd="ip6tables"
825     else
826         _iptables_cmd="iptables"
827     fi
828
829     # iptables doesn't like being re-entered, so flock-wrap it.
830     flock -w 30 "${CTDB_SCRIPT_VARDIR}/iptables.flock" "$_iptables_cmd" "$@"
831 }
832
833 # AIX (and perhaps others?) doesn't have mktemp
834 # type is commonly supported and more portable than which(1)
835 # shellcheck disable=SC2039
836 if ! type mktemp >/dev/null 2>&1 ; then
837     mktemp ()
838     {
839         _dir=false
840         if [ "$1" = "-d" ] ; then
841             _dir=true
842             shift
843         fi
844         _d="${TMPDIR:-/tmp}"
845         _hex10=$(dd if=/dev/urandom count=20 2>/dev/null | \
846             md5sum | \
847             sed -e 's@\(..........\).*@\1@')
848         _t="${_d}/tmp.${_hex10}"
849         (
850             umask 077
851             if $_dir ; then
852                 mkdir "$_t"
853             else
854                 : >"$_t"
855             fi
856         )
857         echo "$_t"
858     }
859 fi
860
861 ######################################################################
862 # NFS callout handling
863
864 nfs_callout_init ()
865 {
866         _state_dir="$1"
867
868         if [ -z "$CTDB_NFS_CALLOUT" ] ; then
869                 CTDB_NFS_CALLOUT="${CTDB_BASE}/nfs-linux-kernel-callout"
870         fi
871         # Always export, for statd callout
872         export CTDB_NFS_CALLOUT
873
874         # If the callout wants to use this then it must create it
875         export CTDB_NFS_CALLOUT_STATE_DIR="${_state_dir}/callout-state"
876
877         # Export, if set, for use by clustered NFS callouts
878         if [ -n "$CTDB_NFS_STATE_FS_TYPE" ] ; then
879                 export CTDB_NFS_STATE_FS_TYPE
880         fi
881         if [ -n "$CTDB_NFS_STATE_MNT" ] ; then
882                 export CTDB_NFS_STATE_MNT
883         fi
884
885         nfs_callout_cache="${_state_dir}/nfs_callout_cache"
886         nfs_callout_cache_callout="${nfs_callout_cache}/CTDB_NFS_CALLOUT"
887         nfs_callout_cache_ops="${nfs_callout_cache}/ops"
888 }
889
890 nfs_callout_register ()
891 {
892     mkdir -p "$nfs_callout_cache_ops"
893     rm -f "$nfs_callout_cache_ops"/*
894
895     echo "$CTDB_NFS_CALLOUT" >"$nfs_callout_cache_callout"
896
897     _t=$(eval "$CTDB_NFS_CALLOUT" "register")
898     if [ -n "$_t" ] ; then
899         echo "$_t" |
900             while IFS="" read _op ; do
901                 touch "${nfs_callout_cache_ops}/${_op}"
902             done
903     else
904         touch "${nfs_callout_cache_ops}/ALL"
905     fi
906 }
907
908 nfs_callout ()
909 {
910     # Re-run registration if $CTDB_NFS_CALLOUT has changed
911     _prev=""
912     if [ -r "$nfs_callout_cache_callout" ] ; then
913         read _prev <"$nfs_callout_cache_callout"
914     fi
915     if [ "$CTDB_NFS_CALLOUT" != "$_prev" ] ; then
916         nfs_callout_register
917     fi
918
919     # Run the operation if it is registered...
920     if [ -e "${nfs_callout_cache_ops}/${1}" ] || \
921            [ -e "${nfs_callout_cache_ops}/ALL" ]; then
922         eval "$CTDB_NFS_CALLOUT" "$@"
923     fi
924 }
925
926 ########################################################
927 # tickle handling
928 ########################################################
929
930 update_tickles ()
931 {
932         _port="$1"
933
934         tickledir="${CTDB_SCRIPT_VARDIR}/tickles"
935         mkdir -p "$tickledir"
936
937         # What public IPs do I hold?
938         _pnn=$(ctdb_get_pnn)
939         _ips=$($CTDB -X ip | awk -F'|' -v pnn="$_pnn" '$3 == pnn {print $2}')
940
941         # IPs and port as ss filters
942         _ip_filter=""
943         for _ip in $_ips ; do
944             _ip_filter="${_ip_filter}${_ip_filter:+ || }src [${_ip}]"
945         done
946         _port_filter="sport == :${_port}"
947
948         # Record connections to our public IPs in a temporary file.
949         # This temporary file is in CTDB's private state directory and
950         # $$ is used to avoid a very rare race involving CTDB's script
951         # debugging.  No security issue, nothing to see here...
952         _my_connections="${tickledir}/${_port}.connections.$$"
953         # Parentheses are needed around the filters for precedence but
954         # the parentheses can't be empty!
955         ss -tn state established \
956            "${_ip_filter:+( ${_ip_filter} )}" \
957            "${_port_filter:+( ${_port_filter} )}" |
958         awk 'NR > 1 {print $4, $3}' |
959         sort >"$_my_connections"
960
961         # Record our current tickles in a temporary file
962         _my_tickles="${tickledir}/${_port}.tickles.$$"
963         for _i in $_ips ; do
964                 $CTDB -X gettickles "$_i" "$_port" |
965                 awk -F'|' 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
966         done |
967         sort >"$_my_tickles"
968
969         # Add tickles for connections that we haven't already got tickles for
970         comm -23 "$_my_connections" "$_my_tickles" | \
971                 $CTDB addtickle
972
973         # Remove tickles for connections that are no longer there
974         comm -13 "$_my_connections" "$_my_tickles" | \
975                 $CTDB deltickle
976
977         rm -f "$_my_connections" "$_my_tickles"
978
979         # Remove stale files from killed scripts
980         # Files can't have spaces in name, more portable than -print0/-0
981         # shellcheck disable=SC2038
982         (cd "$tickledir" && find . -type f -mmin +10 | xargs -r rm)
983 }
984
985 ########################################################
986 # load a site local config file
987 ########################################################
988
989 [ -x "${CTDB_BASE}/rc.local" ] && {
990         . "${CTDB_BASE}/rc.local"
991 }
992
993 [ -d "${CTDB_BASE}/rc.local.d" ] && {
994         for i in "${CTDB_BASE}/rc.local.d"/* ; do
995                 [ -x "$i" ] && . "$i"
996         done
997 }
998
999 script_name="${0##*/}"       # basename