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