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