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