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