s4/torture: Fix misplaced positional arguments for u64 comparison
[gd/samba-autobuild/.git] / ctdb / config / functions
index 42b467b8d93be5ff6bfb51e0ba008897f649cdf1..a40b276e2b854da3794caa139b1b97d4c0f77213 100755 (executable)
 
 # utility functions for ctdb event scripts
 
-[ -z "$CTDB_VARDIR" ] && {
-    if [ -d "/var/lib/ctdb" ] ; then
-       export CTDB_VARDIR="/var/lib/ctdb"
-    else
-       export CTDB_VARDIR="/var/ctdb"
-    fi
-}
-[ -z "$CTDB_ETCDIR" ] && {
-    export CTDB_ETCDIR="/etc"
-}
+if [ -z "$CTDB_BASE" ]; then
+       echo 'CTDB_BASE unset in CTDB functions file'
+       exit 1
+fi
+export CTDB_BASE
+
+# CTDB_VARDIR is used elsewhere
+# shellcheck disable=SC2034
+CTDB_VARDIR="/usr/local/var/lib/ctdb"
+
+CTDB="${CTDB:-/usr/local/bin/ctdb}"
+
+# Only (and always) override these variables in test code
+
+if [ -z "$CTDB_SCRIPT_VARDIR" ]; then
+       CTDB_SCRIPT_VARDIR="/usr/local/var/lib/ctdb/scripts"
+fi
+
+if [ -z "$CTDB_SYS_ETCDIR" ]; then
+       CTDB_SYS_ETCDIR="/etc"
+fi
+
+if [ -z "$CTDB_HELPER_BINDIR" ]; then
+       CTDB_HELPER_BINDIR="/usr/local/libexec/ctdb"
+fi
 
 #######################################
 # pull in a system config file, if any
-_loadconfig() {
 
-    if [ -z "$1" ] ; then
-       foo="${service_config:-${service_name}}"
-       if [ -n "$foo" ] ; then
-           loadconfig "$foo"
-           return
-       fi
-    fi
-
-    if [ "$1" != "ctdb" ] ; then
-       loadconfig "ctdb"
-    fi
-
-    if [ -z "$1" ] ; then
-       return
-    fi
-
-    if [ -f $CTDB_ETCDIR/sysconfig/$1 ]; then
-       . $CTDB_ETCDIR/sysconfig/$1
-    elif [ -f $CTDB_ETCDIR/default/$1 ]; then
-       . $CTDB_ETCDIR/default/$1
-    elif [ -f $CTDB_BASE/sysconfig/$1 ]; then
-       . $CTDB_BASE/sysconfig/$1
-    fi
-
-    if [ "$1" = "ctdb" ] ; then
-       _config="${CTDB_BASE}/ctdbd.conf"
-       if [ -r "$_config" ] ; then
-           . "$_config"
-       fi
-    fi
+load_system_config()
+{
+       for _i; do
+
+               if [ -f "${CTDB_SYS_ETCDIR}/sysconfig/${_i}" ]; then
+                       . "${CTDB_SYS_ETCDIR}/sysconfig/${_i}"
+                       return
+               elif [ -f "${CTDB_SYS_ETCDIR}/default/${_i}" ]; then
+                       . "${CTDB_SYS_ETCDIR}/default/${_i}"
+                       return
+               fi
+       done
 }
 
-loadconfig () {
-    _loadconfig "$@"
-}
+# load_script_options [ component script ]
+#   script is an event script name relative to a component
+#   component is currently ignored
+load_script_options()
+{
+       if [ $# -eq 2 ]; then
+               _script="$2"
+       elif [ $# -eq 0 ]; then
+               _script=""
+       else
+               die "usage: load_script_options [ component script ]"
+       fi
 
-##############################################################
+       _options="${CTDB_BASE}/script.options"
 
-# CTDB_SCRIPT_DEBUGLEVEL can be overwritten by setting it in a
-# configuration file.
-debug ()
-{
-    if [ ${CTDB_SCRIPT_DEBUGLEVEL:-2} -ge 4 ] ; then
-       # If there are arguments then echo them.  Otherwise expect to
-       # use stdin, which allows us to pass lots of debug using a
-       # here document.
-       if [ -n "$1" ] ; then
-           echo "DEBUG: $*"
+       if [ -r "$_options" ]; then
+               . "$_options"
+       fi
+
+       if [ -n "$_script" ]; then
+               _s="${CTDB_BASE}/events/legacy/${_script}"
        else
-           sed -e 's@^@DEBUG: @'
+               _s="${0%.script}"
        fi
-    else
-       if [ -z "$1" ] ; then
-           cat >/dev/null
+       _options="${_s}.options"
+
+       if [ -r "$_options" ]; then
+               . "$_options"
        fi
-    fi
 }
 
-die ()
+##############################################################
+
+die()
 {
-    _msg="$1"
-    _rc="${2:-1}"
+       _msg="$1"
+       _rc="${2:-1}"
 
-    echo "$_msg"
-    exit $_rc
+       echo "$_msg" >&2
+       exit "$_rc"
 }
 
 # Log given message or stdin to either syslog or a CTDB log file
 # $1 is the tag passed to logger if syslog is in use.
-script_log ()
+script_log()
 {
-    _tag="$1" ; shift
-
-    case "$CTDB_LOGGING" in
-       file:*|"")
-           if [ -n "$CTDB_LOGGING" ] ; then
-               _file="${CTDB_LOGGING#file:}"
-           else
-               _file="/var/log/log.ctdb"
-           fi
-           {
+       _tag="$1"
+       shift
+
+       case "$CTDB_LOGGING" in
+       file:)
                if [ -n "$*" ] ; then
-                   echo "$*"
+                       echo "$*"
                else
-                   cat
+                       cat
+               fi >&2
+               ;;
+       file:* | "")
+               if [ -n "$CTDB_LOGGING" ]; then
+                       _file="${CTDB_LOGGING#file:}"
+               else
+                       _file="/usr/local/var/log/log.ctdb"
                fi
-           } >>"$_file"
-           ;;
+               {
+                       if [ -n "$*" ]; then
+                               echo "$*"
+                       else
+                               cat
+                       fi
+               } >>"$_file"
+               ;;
        *)
-           # Handle all syslog:* variants here too.  There's no tool to do
-           # the lossy things, so just use logger.
-           logger -t "ctdbd: ${_tag}" $*
-           ;;
-    esac
+               # Handle all syslog:* variants here too.  There's no tool to do
+               # the lossy things, so just use logger.
+               logger -t "ctdbd: ${_tag}" "$@"
+               ;;
+       esac
 }
 
 # When things are run in the background in an eventscript then logging
 # output might get lost.  This is the "solution".  :-)
-background_with_logging ()
+background_with_logging()
 {
-    (
-       "$@" 2>&1 </dev/null |
-       script_log "${script_name}&"
-    )&
+       (
+               "$@" 2>&1 </dev/null |
+                       script_log "${script_name}&"
+       ) &
 
-    return 0
+       return 0
 }
 
 ##############################################################
 # check number of args for different events
-ctdb_check_args ()
+ctdb_check_args()
 {
-    case "$1" in
-       takeip|releaseip)
-           if [ $# != 4 ]; then
-               echo "ERROR: must supply interface, IP and maskbits"
-               exit 1
-           fi
-           ;;
+       case "$1" in
+       takeip | releaseip)
+               if [ $# != 4 ]; then
+                       echo "ERROR: must supply interface, IP and maskbits"
+                       exit 1
+               fi
+               ;;
        updateip)
-           if [ $# != 5 ]; then
-               echo "ERROR: must supply old interface, new interface, IP and maskbits"
-               exit 1
-           fi
-           ;;
-    esac
+               if [ $# != 5 ]; then
+                       echo "ERROR: must supply old interface, new interface, IP and maskbits"
+                       exit 1
+               fi
+               ;;
+       esac
 }
 
 ##############################################################
 # determine on what type of system (init style) we are running
 detect_init_style()
 {
-    # only do detection if not already set:
-    [ -z "$CTDB_INIT_STYLE" ] || return
-
-    if [ -x /sbin/startproc ]; then
-        CTDB_INIT_STYLE="suse"
-    elif [ -x /sbin/start-stop-daemon ]; then
-        CTDB_INIT_STYLE="debian"
-    else
-        CTDB_INIT_STYLE="redhat"
-    fi
+       _init_style_file="${CTDB_SCRIPT_VARDIR}/init-style"
+
+       if [ ! -f "$_init_style_file" ]; then
+               if [ -n "$CTDB_INIT_STYLE" ]; then
+                       echo "$CTDB_INIT_STYLE" >"$_init_style_file"
+                       return
+               fi
+
+               # Subshell to contain variables in os-release file
+               (
+                       _os_release="${CTDB_SYS_ETCDIR}/os-release"
+                       if [ -f "$_os_release" ]; then
+                               . "$_os_release"
+                               case "$ID" in
+                               centos | fedora | rhel)
+                                       echo "redhat"
+                                       ;;
+                               debian | ubuntu)
+                                       echo "debian"
+                                       ;;
+                               sles | suse)
+                                       echo "suse"
+                                       ;;
+                               *)
+                                       case "$ID_LIKE" in
+                                       *centos* | *rhel*)
+                                               echo "redhat"
+                                               ;;
+                                       *)
+                                               echo "$ID"
+                                               ;;
+                                       esac
+                                       ;;
+                               esac
+                       else
+                               echo "WARNING: unknown distribution ${ID}" >&2
+                               echo "unknown"
+                       fi
+               ) >"$_init_style_file"
+       fi
+
+       read -r CTDB_INIT_STYLE <"$_init_style_file"
 }
 
 ######################################################
 # simulate /sbin/service on platforms that don't have it
 # _service() makes it easier to hook the service() function for
 # testing.
-_service ()
+_service()
 {
-  _service_name="$1"
-  _op="$2"
-
-  # do nothing, when no service was specified
-  [ -z "$_service_name" ] && return
-
-  if [ -x /sbin/service ]; then
-      $_nice /sbin/service "$_service_name" "$_op"
-  elif [ -x /usr/sbin/service ]; then
-      $_nice /usr/sbin/service "$_service_name" "$_op"
-  elif [ -x $CTDB_ETCDIR/init.d/$_service_name ]; then
-      $_nice $CTDB_ETCDIR/init.d/$_service_name "$_op"
-  elif [ -x $CTDB_ETCDIR/rc.d/init.d/$_service_name ]; then
-      $_nice $CTDB_ETCDIR/rc.d/init.d/$_service_name "$_op"
-  fi
+       _service_name="$1"
+       _op="$2"
+
+       # do nothing, when no service was specified
+       [ -z "$_service_name" ] && return
+
+       if [ -x /sbin/service ]; then
+               $_nice /sbin/service "$_service_name" "$_op"
+       elif [ -x /usr/sbin/service ]; then
+               $_nice /usr/sbin/service "$_service_name" "$_op"
+       elif [ -x /bin/systemctl ]; then
+               $_nice /bin/systemctl "$_op" "$_service_name"
+       elif [ -x "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" ]; then
+               $_nice "${CTDB_SYS_ETCDIR}/init.d/${_service_name}" "$_op"
+       elif [ -x "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" ]; then
+               $_nice "${CTDB_SYS_ETCDIR}/rc.d/init.d/${_service_name}" "$_op"
+       fi
 }
 
 service()
 {
-    _nice=""
-    _service "$@"
+       _nice=""
+       _service "$@"
 }
 
 ######################################################
 # simulate /sbin/service (niced) on platforms that don't have it
 nice_service()
 {
-    _nice="nice"
-    _service "$@"
+       _nice="nice"
+       _service "$@"
 }
 
 ######################################################
 # Cached retrieval of PNN from local node.  This never changes so why
 # open a client connection to the server each time this is needed?
-# This sets $pnn - this avoid an unnecessary subprocess.
-ctdb_get_pnn ()
+ctdb_get_pnn()
+{
+       _pnn_file="${CTDB_SCRIPT_VARDIR}/my-pnn"
+       if [ ! -f "$_pnn_file" ]; then
+               $CTDB pnn >"$_pnn_file"
+       fi
+
+       cat "$_pnn_file"
+}
+
+# Cached retrieval of private IP address from local node.  This never
+# changes.
+ctdb_get_ip_address()
 {
-    _pnn_file="$CTDB_VARDIR/state/my-pnn"
-    if [ ! -f "$_pnn_file" ] ; then
-       ctdb pnn | sed -e 's@.*:@@' >"$_pnn_file"
-    fi
+       _ip_addr_file="${CTDB_SCRIPT_VARDIR}/my-ip-address"
+       if [ ! -f "$_ip_addr_file" ]; then
+               $CTDB -X nodestatus |
+                       awk -F '|' 'NR == 2 { print $3 }' >"$_ip_addr_file"
+       fi
 
-    read pnn <"$_pnn_file"
+       cat "$_ip_addr_file"
+}
+
+# Cached retrieval of database options for use by event scripts.
+#
+# If the variables are already set then they should not be overwritten
+# - this should only happen during event script testing.
+ctdb_get_db_options()
+{
+       _db_opts_file="${CTDB_SCRIPT_VARDIR}/db_options.cache"
+
+       if [ ! -f "$_db_opts_file" ]; then
+               {
+                       ctdb_translate_option "database" \
+                               "volatile database directory" \
+                               "CTDB_DBDIR"
+                       ctdb_translate_option "database" \
+                               "persistent database directory" \
+                               "CTDB_DBDIR_PERSISTENT"
+                       ctdb_translate_option "database" \
+                               "state database directory" \
+                               "CTDB_DBDIR_STATE"
+               } >"$_db_opts_file"
+       fi
+
+       . "$_db_opts_file"
+}
+
+ctdb_translate_option()
+{
+       _section="$1"
+       _opt="$2"
+       _variable="$3"
+
+       # ctdb-config already prints an error if something goes wrong
+       _t=$("${CTDB_HELPER_BINDIR}/ctdb-config" get "$_section" "$_opt") ||
+               exit $?
+       echo "${_variable}=\"${_t}\""
 }
 
 ######################################################
 # wrapper around /proc/ settings to allow them to be hooked
 # for testing
 # 1st arg is relative path under /proc/, 2nd arg is value to set
-set_proc ()
+set_proc()
 {
-    echo "$2" >"/proc/$1"
+       echo "$2" >"/proc/$1"
 }
 
-set_proc_maybe ()
+set_proc_maybe()
 {
-    if [ -w "/proc/$1" ] ; then
-       set_proc "$1" "$2"
-    fi
+       if [ -w "/proc/$1" ]; then
+               set_proc "$1" "$2"
+       fi
 }
 
 ######################################################
 # wrapper around getting file contents from /proc/ to allow
 # this to be hooked for testing
 # 1st arg is relative path under /proc/
-get_proc ()
+get_proc()
 {
-    cat "/proc/$1"
+       cat "/proc/$1"
 }
 
 ######################################################
 # Print up to $_max kernel stack traces for processes named $_program
-program_stack_traces ()
+program_stack_traces()
 {
-    _prog="$1"
-    _max="${2:-1}"
-
-    _count=1
-    for _pid in $(pidof "$_prog") ; do
-       [ $_count -le $_max ] || break
-
-       # Do this first to avoid racing with process exit
-       _stack=$(get_proc "${_pid}/stack" 2>/dev/null)
-       if [ -n "$_stack" ] ; then
-           echo "Stack trace for ${_prog}[${_pid}]:"
-           echo "$_stack"
-           _count=$(($_count + 1))
-       fi
-    done
+       _prog="$1"
+       _max="${2:-1}"
+
+       _count=1
+       for _pid in $(pidof "$_prog"); do
+               [ "$_count" -le "$_max" ] || break
+
+               # Do this first to avoid racing with process exit
+               _stack=$(get_proc "${_pid}/stack" 2>/dev/null)
+               if [ -n "$_stack" ]; then
+                       echo "Stack trace for ${_prog}[${_pid}]:"
+                       echo "$_stack"
+                       _count=$((_count + 1))
+               fi
+       done
 }
 
 ######################################################
 # Ensure $service_name is set
-assert_service_name ()
+assert_service_name()
 {
-    [ -n "$service_name" ] || die "INTERNAL ERROR: \$service_name not set"
+       # service_name is set by the event script
+       # shellcheck disable=SC2154
+       [ -n "$service_name" ] || die "INTERNAL ERROR: \$service_name not set"
 }
 
 ######################################################
@@ -272,15 +368,16 @@ assert_service_name ()
 ######################################################
 ctdb_check_directories_probe()
 {
-    while IFS="" read d ; do
-       case "$d" in
-           *%*)
-               continue
-               ;;
-           *)
-               [ -d "${d}/." ] || return 1
-       esac
-    done
+       while IFS="" read -r d; do
+               case "$d" in
+               *%*)
+                       continue
+                       ;;
+               *)
+                       [ -d "${d}/." ] || return 1
+                       ;;
+               esac
+       done
 }
 
 ######################################################
@@ -289,10 +386,10 @@ ctdb_check_directories_probe()
 ######################################################
 ctdb_check_directories()
 {
-    ctdb_check_directories_probe || {
-       echo "ERROR: $service_name directory \"$d\" not available"
-       exit 1
-    }
+       ctdb_check_directories_probe || {
+               echo "ERROR: $service_name directory \"$d\" not available"
+               exit 1
+       }
 }
 
 ######################################################
@@ -300,858 +397,776 @@ ctdb_check_directories()
 # usage: ctdb_check_tcp_ports <ports...>
 ######################################################
 
-# This flag file is created when a service is initially started.  It
-# is deleted the first time TCP port checks for that service succeed.
-# Until then ctdb_check_tcp_ports() prints a more subtle "error"
-# message if a port check fails.
-_ctdb_check_tcp_common ()
-{
-    assert_service_name
-    _ctdb_service_started_file="$ctdb_fail_dir/$service_name.started"
-}
-
-ctdb_check_tcp_init ()
-{
-    _ctdb_check_tcp_common
-    mkdir -p "${_ctdb_service_started_file%/*}" # dirname
-    touch "$_ctdb_service_started_file"
-}
-
 # Check whether something is listening on all of the given TCP ports
 # using the "ctdb checktcpport" command.
 ctdb_check_tcp_ports()
 {
-    if [ -z "$1" ] ; then
-       echo "INTERNAL ERROR: ctdb_check_tcp_ports - no ports specified"
-       exit 1
-    fi
-
-    for _p ; do  # process each function argument (port)
-       _cmd="ctdb checktcpport $_p"
-       _out=$($_cmd 2>&1)
-       _ret=$?
-       case "$_ret" in
-           0)
-               _ctdb_check_tcp_common
-               if [ ! -f "$_ctdb_service_started_file" ] ; then
-                   echo "ERROR: $service_name tcp port $_p is not responding"
-                   debug "\"ctdb checktcpport $_p\" was able to bind to port"
-               else
-                   echo "INFO: $service_name tcp port $_p is not responding"
-               fi
+       if [ -z "$1" ]; then
+               echo "INTERNAL ERROR: ctdb_check_tcp_ports - no ports specified"
+               exit 1
+       fi
 
-               return 1
-               ;;
-           98)
-               # Couldn't bind, something already listening, next port...
-               continue
-               ;;
-           *)
-               echo "ERROR: unexpected error running \"ctdb checktcpport\""
-               debug <<EOF
-ctdb checktcpport (exited with $_ret) with output:
-$_out"
-EOF
-               return $_ret
-       esac
-    done
+       for _p; do # process each function argument (port)
+               _cmd="$CTDB checktcpport $_p"
+               _out=$($_cmd 2>&1)
+               _ret=$?
+               case "$_ret" in
+               0)
+                       echo "$service_name not listening on TCP port $_p"
+                       return 1
+                       ;;
+               98)
+                       # Couldn't bind, something already listening, next port
+                       continue
+                       ;;
+               *)
+                       echo "unexpected error (${_ret}) running \"${_cmd}\""
+                       if [ -n "$_out" ]; then
+                               echo "$_out"
+                       fi
+                       return $_ret
+                       ;;
+               esac
+       done
 
-    # All ports listening
-    _ctdb_check_tcp_common
-    rm -f "$_ctdb_service_started_file"
-    return 0
+       # All ports listening
+       return 0
 }
 
 ######################################################
 # check a unix socket
-# usage: ctdb_check_unix_socket SERVICE_NAME <socket_path>
+# usage: ctdb_check_unix_socket SOCKPATH
 ######################################################
-ctdb_check_unix_socket() {
-    socket_path="$1"
-    [ -z "$socket_path" ] && return
-
-    if ! netstat --unix -a -n | grep -q "^unix.*LISTEN.*${socket_path}$"; then
-        echo "ERROR: $service_name socket $socket_path not found"
-        return 1
-    fi
-}
-
-######################################################
-# check a command returns zero status
-# usage: ctdb_check_command <command>
-######################################################
-ctdb_check_command ()
+ctdb_check_unix_socket()
 {
-    _out=$("$@" 2>&1) || {
-       echo "ERROR: $* returned error"
-       echo "$_out" | debug
-       exit 1
-    }
+       _sockpath="$1"
+
+       if [ -z "$_sockpath" ]; then
+               echo "ERROR: ctdb_check_unix_socket() requires socket path"
+               return 1
+       fi
+
+       _out=$(ss -l -x "src ${_sockpath}" | tail -n +2)
+       if [ -z "$_out" ]; then
+               echo "ERROR: ${service_name} not listening on ${_sockpath}"
+               return 1
+       fi
 }
 
 ################################################
 # kill off any TCP connections with the given IP
 ################################################
-kill_tcp_connections ()
+kill_tcp_connections()
 {
-    _ip="$1"
-
-    _oneway=false
-    if [ "$2" = "oneway" ] ; then
-       _oneway=true
-    fi
+       _iface="$1"
+       _ip="$2"
 
-    get_tcp_connections_for_ip "$_ip" | {
-       _killcount=0
-       _connections=""
-       _nl="
-"
-       while read _dst _src; do
-           _destport="${_dst##*:}"
-           __oneway=$_oneway
-           case $_destport in
-               # we only do one-way killtcp for CIFS
-               139|445) __oneway=true ;;
-           esac
-
-           echo "Killing TCP connection $_src $_dst"
-           _connections="${_connections}${_nl}${_src} ${_dst}"
-           if ! $__oneway ; then
-               _connections="${_connections}${_nl}${_dst} ${_src}"
-           fi
-
-           _killcount=$(($_killcount + 1))
-       done
-
-       if [ $_killcount -eq 0 ] ; then
-           return
+       _oneway=false
+       if [ "$3" = "oneway" ]; then
+               _oneway=true
        fi
 
-       echo "$_connections" | ctdb killtcp || {
-           echo "Failed to send killtcp control"
-           return
-       }
+       get_tcp_connections_for_ip "$_ip" | {
+               _killcount=0
+               _connections=""
+               _nl="
+"
+               while read -r _dst _src; do
+                       _destport="${_dst##*:}"
+                       __oneway=$_oneway
+                       case $_destport in
+                       # we only do one-way killtcp for CIFS
+                       139 | 445) __oneway=true ;;
+                       esac
+
+                       _connections="${_connections}${_nl}${_src} ${_dst}"
+                       if ! $__oneway; then
+                               _connections="${_connections}${_nl}${_dst} ${_src}"
+                       fi
+
+                       _killcount=$((_killcount + 1))
+               done
+
+               if [ $_killcount -eq 0 ]; then
+                       return
+               fi
 
-       _count=0
-       while : ; do
-           _remaining=$(get_tcp_connections_for_ip $_ip | wc -l)
+               if [ -n "$CTDB_KILLTCP_DEBUGLEVEL" ]; then
+                       _debuglevel="$CTDB_KILLTCP_DEBUGLEVEL"
+               else
+                       _debuglevel="$CTDB_DEBUGLEVEL"
+               fi
+               echo "$_connections" |
+                       CTDB_DEBUGLEVEL="$_debuglevel" \
+                               "${CTDB_HELPER_BINDIR}/ctdb_killtcp" "$_iface" || {
+                       echo "Failed to kill TCP connections"
+                       return
+               }
+
+               _connections=$(get_tcp_connections_for_ip "$_ip")
+               if [ -z "$_connections" ]; then
+                       _remaining=0
+               else
+                       _remaining=$(echo "$_connections" | wc -l)
+               fi
 
-           if [ $_remaining -eq 0 ] ; then
-               echo "Killed $_killcount TCP connections to released IP $_ip"
-               return
-           fi
+               _actually_killed=$((_killcount - _remaining))
 
-           _count=$(($_count + 1))
-           if [ $_count -gt 3 ] ; then
-               echo "Timed out killing tcp connections for IP $_ip ($_remaining remaining)"
-               return
-           fi
+               _t="${_actually_killed}/${_killcount}"
+               echo "Killed ${_t} TCP connections to released IP $_ip"
 
-           echo "Waiting for $_remaining connections to be killed for IP $_ip"
-           sleep 1
-       done
-    }
+               if [ -n "$_connections" ]; then
+                       echo "Remaining connections:"
+                       echo "$_connections" | sed -e 's|^|  |'
+               fi
+       }
 }
 
 ##################################################################
 # kill off the local end for any TCP connections with the given IP
 ##################################################################
-kill_tcp_connections_local_only ()
+kill_tcp_connections_local_only()
 {
-    kill_tcp_connections "$1" "oneway"
+       kill_tcp_connections "$@" "oneway"
 }
 
 ##################################################################
 # tickle any TCP connections with the given IP
 ##################################################################
-tickle_tcp_connections ()
+tickle_tcp_connections()
 {
-    _ip="$1"
-
-    get_tcp_connections_for_ip "$_ip" |
-    {
-       _failed=false
+       _ip="$1"
 
-       while read dest src; do
-           echo "Tickle TCP connection $src $dest"
-           ctdb tickle $src $dest >/dev/null 2>&1 || _failed=true
-           echo "Tickle TCP connection $dest $src"
-           ctdb tickle $dest $src >/dev/null 2>&1 || _failed=true
-       done
+       # Get connections, both directions
+       _conns=$(get_tcp_connections_for_ip "$_ip" |
+               awk '{ print $1, $2 ; print $2, $1 }')
 
-       if $_failed ; then
-           echo "Failed to send tickle control"
-       fi
-    }
+       echo "$_conns" | awk '{ print "Tickle TCP connection", $1, $2 }'
+       echo "$_conns" | ctdb tickle
 }
 
-get_tcp_connections_for_ip ()
+get_tcp_connections_for_ip()
 {
-    _ip="$1"
+       _ip="$1"
 
-    netstat -tn | awk -v ip=$_ip \
-       'index($1, "tcp") == 1 && \
-        (index($4, ip ":") == 1 || index($4, "::ffff:" ip ":") == 1) \
-        && $6 == "ESTABLISHED" \
-        {print $4" "$5}'
+       ss -tn state established "src [$_ip]" | awk 'NR > 1 {print $3, $4}'
 }
 
 ########################################################
 
-add_ip_to_iface ()
+add_ip_to_iface()
 {
-    _iface=$1
-    _ip=$2
-    _maskbits=$3
-
-    # Ensure interface is up
-    ip link set "$_iface" up || \
-       die "Failed to bringup interface $_iface"
-
-    # Only need to define broadcast for IPv4
-    case "$ip" in
-        *:*) _bcast=""      ;;
-       *)   _bcast="brd +" ;;
-    esac
-
-    ip addr add "$_ip/$_maskbits" $_bcast dev "$_iface" || {
-       echo "Failed to add $_ip/$_maskbits on dev $_iface"
-       return 1
-    }
-
-    # Wait 5 seconds for IPv6 addresses to stop being tentative...
-    if [ -z "$_bcast" ] ; then
-       for _x in $(seq 1 10) ; do
-           ip addr show to "${_ip}/128" | grep -q "tentative" || break
-           sleep 0.5
-       done
+       _iface=$1
+       _ip=$2
+       _maskbits=$3
+
+       # Ensure interface is up
+       ip link set "$_iface" up ||
+               die "Failed to bringup interface $_iface"
+
+       # Only need to define broadcast for IPv4
+       case "$_ip" in
+       *:*) _bcast="" ;;
+       *) _bcast="brd +" ;;
+       esac
 
-       # If the address was a duplicate then it won't be on the
-       # interface so flag an error.
-       _t=$(ip addr show to "${_ip}/128")
-       case "$_t" in
-           "")
-               echo "Failed to add $_ip/$_maskbits on dev $_iface"
-               return 1
-               ;;
-           *tentative*|*dadfailed*)
+       # Intentionally unquoted multi-word value here
+       # shellcheck disable=SC2086
+       ip addr add "$_ip/$_maskbits" $_bcast dev "$_iface" || {
                echo "Failed to add $_ip/$_maskbits on dev $_iface"
-               ip addr del "$_ip/$_maskbits" dev "$_iface"
                return 1
-               ;;
-       esac
-    fi
+       }
+
+       # Wait 5 seconds for IPv6 addresses to stop being tentative...
+       if [ -z "$_bcast" ]; then
+               for _x in $(seq 1 10); do
+                       ip addr show to "${_ip}/128" | grep -q "tentative" || break
+                       sleep 0.5
+               done
+
+               # If the address was a duplicate then it won't be on the
+               # interface so flag an error.
+               _t=$(ip addr show to "${_ip}/128")
+               case "$_t" in
+               "")
+                       echo "Failed to add $_ip/$_maskbits on dev $_iface"
+                       return 1
+                       ;;
+               *tentative* | *dadfailed*)
+                       echo "Failed to add $_ip/$_maskbits on dev $_iface"
+                       ip addr del "$_ip/$_maskbits" dev "$_iface"
+                       return 1
+                       ;;
+               esac
+       fi
 }
 
 delete_ip_from_iface()
 {
-    _iface=$1
-    _ip=$2
-    _maskbits=$3
-
-    # This could be set globally for all interfaces but it is probably
-    # better to avoid surprises, so limit it the interfaces where CTDB
-    # has public IP addresses.  There isn't anywhere else convenient
-    # to do this so just set it each time.  This is much cheaper than
-    # remembering and re-adding secondaries.
-    set_proc "sys/net/ipv4/conf/${_iface}/promote_secondaries" 1
-
-    ip addr del "$_ip/$_maskbits" dev "$_iface" || {
-       echo "Failed to del $_ip on dev $_iface"
-       return 1
-    }
+       _iface=$1
+       _ip=$2
+       _maskbits=$3
+
+       # This could be set globally for all interfaces but it is probably
+       # better to avoid surprises, so limit it the interfaces where CTDB
+       # has public IP addresses.  There isn't anywhere else convenient
+       # to do this so just set it each time.  This is much cheaper than
+       # remembering and re-adding secondaries.
+       set_proc "sys/net/ipv4/conf/${_iface}/promote_secondaries" 1
+
+       ip addr del "$_ip/$_maskbits" dev "$_iface" || {
+               echo "Failed to del $_ip on dev $_iface"
+               return 1
+       }
 }
 
 # If the given IP is hosted then print 2 items: maskbits and iface
-ip_maskbits_iface ()
+ip_maskbits_iface()
 {
-    _addr="$1"
-
-    case "$_addr" in
-       *:*) _family="inet6" ; _bits=128 ;;
-       *)   _family="inet"  ; _bits=32  ;;
-    esac
-
-    ip addr show to "${_addr}/${_bits}" 2>/dev/null | \
-       awk -v family="${_family}" \
-           'NR == 1 { iface = $2; sub(":$", "", iface) ; \
-                      sub("@.*", "", iface) } \
-             $1 ~ /inet/ { mask = $2; sub(".*/", "", mask); \
-                           print mask, iface, family }'
+       _addr="$1"
+
+       case "$_addr" in
+       *:*) _bits=128 ;;
+       *) _bits=32 ;;
+       esac
+       ip addr show to "${_addr}/${_bits}" 2>/dev/null |
+               awk 'NR == 1 { iface = $2; sub(":$", "", iface) ;
+                      sub("@.*", "", iface) }
+             $1 ~ /inet/ { mask = $2; sub(".*/", "", mask);
+                           print mask, iface }'
 }
 
-drop_ip ()
+drop_ip()
 {
-    _addr="${1%/*}"  # Remove optional maskbits
-
-    set -- $(ip_maskbits_iface $_addr)
-    if [ -n "$1" ] ; then
-       _maskbits="$1"
-       _iface="$2"
-       echo "Removing public address $_addr/$_maskbits from device $_iface"
-       delete_ip_from_iface $_iface $_addr $_maskbits >/dev/null 2>&1
-    fi
+       _addr="${1%/*}" # Remove optional maskbits
+
+       # Intentional word splitting here
+       # shellcheck disable=SC2046
+       set -- $(ip_maskbits_iface "$_addr")
+       if [ -n "$1" ]; then
+               _maskbits="$1"
+               _iface="$2"
+               echo "Removing public address $_addr/$_maskbits from device $_iface"
+               delete_ip_from_iface "$_iface" "$_addr" "$_maskbits" >/dev/null 2>&1
+       fi
 }
 
-drop_all_public_ips ()
+drop_all_public_ips()
 {
-    while read _ip _x ; do
-       drop_ip "$_ip"
-    done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
+       # _x is intentionally ignored
+       # shellcheck disable=SC2034
+       while read -r _ip _x; do
+               case "$_ip" in
+               \#*) continue ;;
+               esac
+               drop_ip "$_ip"
+       done <"${CTDB_BASE}/public_addresses"
 }
 
-flush_route_cache ()
+flush_route_cache()
 {
-    set_proc_maybe sys/net/ipv4/route/flush 1
-    set_proc_maybe sys/net/ipv6/route/flush 1
+       set_proc_maybe sys/net/ipv4/route/flush 1
+       set_proc_maybe sys/net/ipv6/route/flush 1
 }
 
 ########################################################
-# Simple counters
-_ctdb_counter_common () {
-    _service_name="${1:-${service_name:-${script_name}}}"
-    _counter_file="$ctdb_fail_dir/$_service_name"
-    mkdir -p "${_counter_file%/*}" # dirname
-}
-ctdb_counter_init () {
-    _ctdb_counter_common "$1"
+# Interface monitoring
 
-    >"$_counter_file"
-}
-ctdb_counter_incr () {
-    _ctdb_counter_common "$1"
+# If the interface is a virtual one (e.g. VLAN) then get the
+# underlying interface
+interface_get_real()
+{
+       _iface="$1"
 
-    # unary counting!
-    echo -n 1 >> "$_counter_file"
-}
-ctdb_counter_get () {
-    _ctdb_counter_common "$1"
-    # unary counting!
-    stat -c "%s" "$_counter_file" 2>/dev/null || echo 0
+       # If $_iface is a VLAN (i.e. contains an '@') then strip every
+       # before the '@', otherwise print the whole interface
+       echo "${_iface##*@}"
 }
-ctdb_check_counter () {
-    _msg="${1:-error}"  # "error"  - anything else is silent on fail
-    _op="${2:--ge}"  # an integer operator supported by test
-    _limit="${3:-${service_fail_limit}}"
-    shift 3
-
-    _size=$(ctdb_counter_get "$1")
-
-    _hit=false
-    if [ "$_op" != "%" ] ; then
-       if [ $_size $_op $_limit ] ; then
-           _hit=true
-       fi
-    else
-       if [ $(($_size $_op $_limit)) -eq 0 ] ; then
-           _hit=true
-       fi
-    fi
-    if $_hit ; then
-       if [ "$_msg" = "error" ] ; then
-           echo "ERROR: $_size consecutive failures for $_service_name, marking node unhealthy"
-           exit 1              
+
+# Check whether an interface is operational
+interface_monitor()
+{
+       _iface="$1"
+
+       _iface_info=$(ip -br link show "$_iface" 2>&1) || {
+               echo "ERROR: Monitored interface ${_iface} does not exist"
+               return 1
+       }
+
+       # If the interface is a virtual one (e.g. VLAN) then get the
+       # underlying interface.
+       _realiface=$(interface_get_real "${_iface_info%% *}")
+
+       if _bi=$(get_proc "net/bonding/${_realiface}" 2>/dev/null); then
+               # This is a bond: various monitoring strategies
+               echo "$_bi" | grep -q 'Currently Active Slave: None' && {
+                       echo "ERROR: No active slaves for bond device ${_realiface}"
+                       return 1
+               }
+               echo "$_bi" | grep -q '^MII Status: up' || {
+                       echo "ERROR: public network interface ${_realiface} is down"
+                       return 1
+               }
+               echo "$_bi" | grep -q '^Bonding Mode: IEEE 802.3ad Dynamic link aggregation' && {
+                       # This works around a bug in the driver where the
+                       # overall bond status can be up but none of the actual
+                       # physical interfaces have a link.
+                       echo "$_bi" | grep 'MII Status:' | tail -n +2 | grep -q '^MII Status: up' || {
+                               echo "ERROR: No active slaves for 802.ad bond device ${_realiface}"
+                               return 1
+                       }
+               }
+
+               return 0
        else
-           return 1
+               # Not a bond
+               case "$_iface" in
+               lo*)
+                       # loopback is always working
+                       return 0
+                       ;;
+               ib*)
+                       # we don't know how to test ib links
+                       return 0
+                       ;;
+               *)
+                       ethtool "$_iface" | grep -q 'Link detected: yes' || {
+                               # On some systems, this is not successful when a
+                               # cable is plugged but the interface has not been
+                               # brought up previously. Bring the interface up
+                               # and try again...
+                               ip link set "$_iface" up
+                               ethtool "$_iface" | grep -q 'Link detected: yes' || {
+                                       echo "ERROR: No link on the public network interface ${_iface}"
+                                       return 1
+                               }
+                       }
+                       return 0
+                       ;;
+               esac
        fi
-    fi
 }
 
 ########################################################
+# Simple counters
+_ctdb_counter_common()
+{
+       [ $# -le 1 ] || die "usage: _ctdb_counter_common [name]"
 
-ctdb_status_dir="$CTDB_VARDIR/state/service_status"
-ctdb_fail_dir="$CTDB_VARDIR/state/failcount"
+       if [ $# -eq 1 ]; then
+               _counter_name="${1}.failcount"
+       else
+               _counter_name="failcount"
+       fi
 
-ctdb_setup_service_state_dir ()
+       if [ -z "$script_state_dir" ]; then
+               die "ctdb_counter_* functions need ctdb_setup_state_dir()"
+       fi
+
+       _counter_file="${script_state_dir}/${_counter_name}"
+}
+# Some code passes an argument
+# shellcheck disable=SC2120
+ctdb_counter_init()
 {
-    service_state_dir="$CTDB_VARDIR/state/service_state/${1:-${service_name}}"
-    mkdir -p "$service_state_dir" || {
-       echo "Error creating state dir \"$service_state_dir\""
-       exit 1
-    }
+       _ctdb_counter_common "$1"
+
+       : >"$_counter_file"
 }
+ctdb_counter_incr()
+{
+       _ctdb_counter_common "$1"
 
-########################################################
-# Managed status history, for auto-start/stop
+       # unary counting using newlines!
+       echo >>"$_counter_file"
+}
+ctdb_counter_get()
+{
+       _ctdb_counter_common "$1"
+       # unary counting!
+       _val=$(wc -c 2>/dev/null <"$_counter_file" || echo 0)
+       # Strip leading spaces from output of wc (on freebsd)
+       # shellcheck disable=SC2086
+       echo $_val
+}
 
-ctdb_managed_dir="$CTDB_VARDIR/state/managed_history"
+#
+# Fail counter/threshold combination to control warnings and node unhealthy
+#
 
-_ctdb_managed_common ()
+_failcount_validate_threshold()
 {
-    _ctdb_managed_file="$ctdb_managed_dir/$service_name"
+       case "$1" in
+       "") return 1 ;; # A failure that doesn't need a warning
+       *)
+               if echo "$1" | grep -qx '[0-9]*'; then
+                       return 0
+               fi
+
+               echo "WARNING: ${1} is an invalid threshold in \"${2}\" check"
+               return 1
+               ;;
+       esac
 }
 
-ctdb_service_managed ()
+_failcount_common()
 {
-    _ctdb_managed_common
-    mkdir -p "$ctdb_managed_dir"
-    touch "$_ctdb_managed_file"
+       _thing="$1"
+
+       _counter=$(echo "$_thing" | sed -e 's@/@_SLASH_@g' -e 's@ @_@g')
 }
 
-ctdb_service_unmanaged ()
+failcount_init()
 {
-    _ctdb_managed_common
-    rm -f "$_ctdb_managed_file"
+       _thing="$1"
+
+       _failcount_common "$_thing"
+
+       ctdb_counter_init "$_counter"
 }
 
-is_ctdb_previously_managed_service ()
+failcount_reset()
 {
-    _ctdb_managed_common
-    [ -f "$_ctdb_managed_file" ]
-}
+       _thing="$1"
 
-########################################################
-# Check and set status
+       _failcount_common "$_thing"
 
-log_status_cat ()
-{
-    echo "node is \"$1\", \"${script_name}\" reports problem: $(cat $2)"
+       _failcount=$(ctdb_counter_get "$_counter")
+       if [ "$_failcount" -eq 0 ]; then
+               return
+       fi
+
+       printf 'NOTICE: %s: no longer failing\n' "$_thing"
+       ctdb_counter_init "$_counter"
 }
 
-ctdb_checkstatus ()
+failcount_incr()
 {
-    if [ -r "$ctdb_status_dir/$script_name/unhealthy" ] ; then
-       log_status_cat "unhealthy" "$ctdb_status_dir/$script_name/unhealthy"
-       return 1
-    elif [ -r "$ctdb_status_dir/$script_name/banned" ] ; then
-       log_status_cat "banned" "$ctdb_status_dir/$script_name/banned"
-       return 2
-    else
-       return 0
-    fi
+       _thing="$1"
+       _thresholds="$2"
+       _output="$3"
+
+       _failcount_common "$_thing"
+
+       ctdb_counter_incr "$_counter"
+       _failcount=$(ctdb_counter_get "$_counter")
+
+       case "$_thresholds" in
+       *:*)
+               _warn_threshold="${_thresholds%:*}"
+               _unhealthy_threshold="${_thresholds#*:}"
+               ;;
+       "")
+               _warn_threshold=1
+               _unhealthy_threshold=""
+               ;;
+       *)
+               _warn_threshold="$_thresholds"
+               _unhealthy_threshold=""
+               ;;
+       esac
+
+       if _failcount_validate_threshold "$_unhealthy_threshold" "$_thing"; then
+               if [ "$_failcount" -ge "$_unhealthy_threshold" ]; then
+                       printf 'ERROR: %s: fail count %d >= threshold %d\n' \
+                              "$_thing" \
+                              "$_failcount" \
+                              "$_unhealthy_threshold"
+                       # Only print output when exceeding the
+                       # unhealthy threshold
+                       if [ "$_failcount" -eq "$_unhealthy_threshold" ] && \
+                                  [ -n "$_output" ]; then
+                               echo "$_output"
+                       fi
+                       exit 1
+               fi
+       fi
+
+       if _failcount_validate_threshold "$_warn_threshold" "$_thing"; then
+               if [ "$_failcount" -lt "$_warn_threshold" ]; then
+                       return 0
+               fi
+       fi
+
+       printf 'WARNING: %s: fail count %d >= threshold %d\n' \
+              "$_thing" \
+              "$_failcount" \
+              "$_warn_threshold"
+       if [ "$_failcount" -eq "$_warn_threshold" ] && [ -n "$_output" ]; then
+               # Only print output when exceeding the warning threshold
+               echo "$_output"
+       fi
 }
 
-ctdb_setstatus ()
+########################################################
+
+# ctdb_setup_state_dir <type> <name>
+#   Sets/creates script_state_dir)
+ctdb_setup_state_dir()
 {
-    d="$ctdb_status_dir/$script_name"
-    case "$1" in
-       unhealthy|banned)
-           mkdir -p "$d"
-           cat "$2" >"$d/$1"
-           ;;
-       *)
-           for i in "banned" "unhealthy" ; do
-               rm -f "$d/$i"
-           done
-           ;;
-    esac
+       [ $# -eq 2 ] || die "usage: ctdb_setup_state_dir <type> <name>"
+
+       _type="$1"
+       _name="$2"
+
+       script_state_dir="${CTDB_SCRIPT_VARDIR}/${_type}/${_name}"
+
+       mkdir -p "$script_state_dir" ||
+               die "Error creating script state dir \"${script_state_dir}\""
 }
 
 ##################################################################
 # Reconfigure a service on demand
 
-_ctdb_service_reconfigure_common ()
+_ctdb_service_reconfigure_common()
 {
-    _d="$ctdb_status_dir/${service_name}"
-    mkdir -p "$_d"
-    _ctdb_service_reconfigure_flag="$_d/reconfigure"
-}
+       if [ -z "$script_state_dir" ]; then
+               die "ctdb_service_*_reconfigure() needs ctdb_setup_state_dir()"
+       fi
 
-ctdb_service_needs_reconfigure ()
-{
-    _ctdb_service_reconfigure_common
-    [ -e "$_ctdb_service_reconfigure_flag" ]
+       _ctdb_service_reconfigure_flag="${script_state_dir}/need_reconfigure"
 }
 
-ctdb_service_set_reconfigure ()
+ctdb_service_needs_reconfigure()
 {
-    _ctdb_service_reconfigure_common
-    >"$_ctdb_service_reconfigure_flag"
+       _ctdb_service_reconfigure_common
+       [ -e "$_ctdb_service_reconfigure_flag" ]
 }
 
-ctdb_service_unset_reconfigure ()
+ctdb_service_set_reconfigure()
 {
-    _ctdb_service_reconfigure_common
-    rm -f "$_ctdb_service_reconfigure_flag"
+       _ctdb_service_reconfigure_common
+       : >"$_ctdb_service_reconfigure_flag"
 }
 
-ctdb_service_reconfigure ()
+ctdb_service_unset_reconfigure()
 {
-    echo "Reconfiguring service \"${service_name}\"..."
-    ctdb_service_unset_reconfigure
-    service_reconfigure || return $?
-    ctdb_counter_init
+       _ctdb_service_reconfigure_common
+       rm -f "$_ctdb_service_reconfigure_flag"
 }
 
-# Default service_reconfigure() function does nothing.
-service_reconfigure ()
+ctdb_service_reconfigure()
 {
-    :
+       echo "Reconfiguring service \"${service_name}\"..."
+       ctdb_service_unset_reconfigure
+       service_reconfigure || return $?
+       # Intentionally have this use $service_name as default
+       # shellcheck disable=SC2119
+       ctdb_counter_init
 }
 
-ctdb_reconfigure_take_lock ()
+# Default service_reconfigure() function does nothing.
+service_reconfigure()
 {
-    _ctdb_service_reconfigure_common
-    _lock="${_d}/reconfigure_lock"
-    mkdir -p "${_lock%/*}" # dirname
-    touch "$_lock"
-
-    (
-       flock 0
-       # This is overkill but will work if we need to extend this to
-       # allow certain events to run multiple times in parallel
-       # (e.g. takeip) and write multiple PIDs to the file.
-       read _locker_event 
-       if [ -n "$_locker_event" ] ; then
-           while read _pid ; do
-               if [ -n "$_pid" -a "$_pid" != $$ ] && \
-                   kill -0 "$_pid" 2>/dev/null ; then
-                   exit 1
-               fi
-           done
-       fi
-
-       printf "%s\n%s\n" "$event_name" $$ >"$_lock"
-       exit 0
-    ) <"$_lock"
+       :
 }
 
-ctdb_reconfigure_release_lock ()
-{
-    _ctdb_service_reconfigure_common
-    _lock="${_d}/reconfigure_lock"
-
-    rm -f "$_lock"
-}
+# Default service_start() and service_stop() functions.
 
-ctdb_replay_monitor_status ()
+# These may be overridden in an eventscript.
+service_start()
 {
-    echo "Replaying previous status for this script due to reconfigure..."
-    # Leading separator ('|') is missing in some versions...
-    _out=$(ctdb scriptstatus -X | grep -E "^\|?monitor\|${script_name}\|")
-    # Output looks like this:
-    # |monitor|60.nfs|1|ERROR|1314764004.030861|1314764004.035514|foo bar|
-    # This is the cheapest way of getting fields in the middle.
-    set -- $(IFS="|" ; echo $_out)
-    _code="$3"
-    _status="$4"
-    # The error output field can include colons so we'll try to
-    # preserve them.  The weak checking at the beginning tries to make
-    # this work for both broken (no leading '|') and fixed output.
-    _out="${_out%|}"
-    _err_out="${_out#*monitor|${script_name}|*|*|*|*|}"
-    case "$_status" in
-       OK) : ;;  # Do nothing special.
-       TIMEDOUT)
-           # Recast this as an error, since we can't exit with the
-           # correct negative number.
-           _code=1
-           _err_out="[Replay of TIMEDOUT scriptstatus - note incorrect return code.] ${_err_out}"
-           ;;
-       DISABLED)
-           # Recast this as an OK, since we can't exit with the
-           # correct negative number.
-           _code=0
-           _err_out="[Replay of DISABLED scriptstatus - note incorrect return code.] ${_err_out}"
-           ;;
-       *) : ;;  # Must be ERROR, do nothing special.
-    esac
-    if [ -n "$_err_out" ] ; then
-       echo "$_err_out"
-    fi
-    exit $_code
+       service "$service_name" start
 }
 
-ctdb_service_check_reconfigure ()
+service_stop()
 {
-    assert_service_name
-
-    # We only care about some events in this function.  For others we
-    # return now.
-    case "$event_name" in
-       monitor|ipreallocated|reconfigure) : ;;
-       *) return 0 ;;
-    esac
-
-    if ctdb_reconfigure_take_lock ; then
-       # No events covered by this function are running, so proceed
-       # with gay abandon.
-       case "$event_name" in
-           reconfigure)
-               (ctdb_service_reconfigure)
-               exit $?
-               ;;
-           ipreallocated)
-               if ctdb_service_needs_reconfigure ; then
-                   ctdb_service_reconfigure
-               fi
-               ;;
-       esac
-
-       ctdb_reconfigure_release_lock
-    else
-       # Somebody else is running an event we don't want to collide
-       # with.  We proceed with caution.
-       case "$event_name" in
-           reconfigure)
-               # Tell whoever called us to retry.
-               exit 2
-               ;;
-           ipreallocated)
-               # Defer any scheduled reconfigure and just run the
-               # rest of the ipreallocated event, as per the
-               # eventscript.  There's an assumption here that the
-               # event doesn't depend on any scheduled reconfigure.
-               # This is true in the current code.
-               return 0
-               ;;
-           monitor)
-               # There is most likely a reconfigure in progress so
-               # the service is possibly unstable.  As above, we
-               # defer any scheduled reconfigured.  We also replay
-               # the previous monitor status since that's the best
-               # information we have.
-               ctdb_replay_monitor_status
-               ;;
-       esac
-    fi
+       service "$service_name" stop
 }
 
 ##################################################################
-# Does CTDB manage this service? - and associated auto-start/stop
 
-ctdb_compat_managed_service ()
+# This exists only for backward compatibility with 3rd party scripts
+# that call it
+ctdb_standard_event_handler()
 {
-    if [ "$1" = "yes" -a "$2" = "$service_name" ] ; then
-       CTDB_MANAGED_SERVICES="$CTDB_MANAGED_SERVICES $2"
-    fi
+       :
 }
 
-is_ctdb_managed_service ()
+iptables_wrapper()
 {
-    assert_service_name
-
-    # $t is used just for readability and to allow better accurate
-    # matching via leading/trailing spaces
-    t=" $CTDB_MANAGED_SERVICES "
+       _family="$1"
+       shift
+       if [ "$_family" = "inet6" ]; then
+               _iptables_cmd="ip6tables"
+       else
+               _iptables_cmd="iptables"
+       fi
 
-    # Return 0 if "<space>$service_name<space>" appears in $t
-    if [ "${t#* ${service_name} }" != "${t}" ] ; then
-       return 0
-    fi
-
-    # If above didn't match then update $CTDB_MANAGED_SERVICES for
-    # backward compatibility and try again.
-    ctdb_compat_managed_service "$CTDB_MANAGES_VSFTPD"   "vsftpd"
-    ctdb_compat_managed_service "$CTDB_MANAGES_SAMBA"    "samba"
-    ctdb_compat_managed_service "$CTDB_MANAGES_WINBIND"  "winbind"
-    ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "apache2"
-    ctdb_compat_managed_service "$CTDB_MANAGES_HTTPD"    "httpd"
-    ctdb_compat_managed_service "$CTDB_MANAGES_ISCSI"    "iscsi"
-    ctdb_compat_managed_service "$CTDB_MANAGES_CLAMD"    "clamd"
-    ctdb_compat_managed_service "$CTDB_MANAGES_NFS"      "nfs"
-
-    t=" $CTDB_MANAGED_SERVICES "
-
-    # Return 0 if "<space>$service_name<space>" appears in $t
-    [ "${t#* ${service_name} }" != "${t}" ]
+       # iptables doesn't like being re-entered, so flock-wrap it.
+       flock -w 30 "${CTDB_SCRIPT_VARDIR}/iptables.flock" "$_iptables_cmd" "$@"
 }
 
-ctdb_start_stop_service ()
-{
-    assert_service_name
-
-    # Allow service-start/service-stop pseudo-events to start/stop
-    # services when we're not auto-starting/stopping and we're not
-    # monitoring.
-    case "$event_name" in
-       service-start)
-           if is_ctdb_managed_service ; then
-               die 'service-start event not permitted when service is managed'
-           fi
-           if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
-               die 'service-start event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
-           fi
-           ctdb_service_start
-           exit $?
-           ;;
-       service-stop)
-           if is_ctdb_managed_service ; then
-               die 'service-stop event not permitted when service is managed'
-           fi
-           if [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] ; then
-               die 'service-stop event not permitted with $CTDB_SERVICE_AUTOSTARTSTOP = yes'
-           fi
-           ctdb_service_stop
-           exit $?
-           ;;
-    esac
-
-    # Do nothing unless configured to...
-    [ "$CTDB_SERVICE_AUTOSTARTSTOP" = "yes" ] || return 0
-
-    [ "$event_name" = "monitor" ] || return 0
-
-    if is_ctdb_managed_service ; then
-       if ! is_ctdb_previously_managed_service ; then
-           echo "Starting service \"$service_name\" - now managed"
-           background_with_logging ctdb_service_start
-           exit $?
-       fi
-    else
-       if is_ctdb_previously_managed_service ; then
-           echo "Stopping service \"$service_name\" - no longer managed"
-           background_with_logging ctdb_service_stop
-           exit $?
-       fi
-    fi
-}
+# AIX (and perhaps others?) doesn't have mktemp
+# type is commonly supported and more portable than which(1)
+# shellcheck disable=SC2039
+if ! type mktemp >/dev/null 2>&1; then
+       mktemp()
+       {
+               _dir=false
+               if [ "$1" = "-d" ]; then
+                       _dir=true
+                       shift
+               fi
+               _d="${TMPDIR:-/tmp}"
+               _hex10=$(dd if=/dev/urandom count=20 2>/dev/null |
+                       cksum |
+                       awk '{print $1}')
+               _t="${_d}/tmp.${_hex10}"
+               (
+                       umask 077
+                       if $_dir; then
+                               mkdir "$_t"
+                       else
+                               : >"$_t"
+                       fi
+               )
+               echo "$_t"
+       }
+fi
 
-ctdb_service_start ()
+######################################################################
+# NFS callout handling
+
+nfs_callout_init()
 {
-    # The service is marked managed if we've ever tried to start it.
-    ctdb_service_managed
+       _state_dir="$1"
 
-    service_start || return $?
+       if [ -z "$CTDB_NFS_CALLOUT" ]; then
+               CTDB_NFS_CALLOUT="${CTDB_BASE}/nfs-linux-kernel-callout"
+       fi
+       # Always export, for statd callout
+       export CTDB_NFS_CALLOUT
 
-    ctdb_counter_init
-    ctdb_check_tcp_init
-}
+       # If the callout wants to use this then it must create it
+       export CTDB_NFS_CALLOUT_STATE_DIR="${_state_dir}/callout-state"
 
-ctdb_service_stop ()
-{
-    ctdb_service_unmanaged
-    service_stop
-}
+       # Export, if set, for use by clustered NFS callouts
+       if [ -n "$CTDB_NFS_STATE_FS_TYPE" ]; then
+               export CTDB_NFS_STATE_FS_TYPE
+       fi
+       if [ -n "$CTDB_NFS_STATE_MNT" ]; then
+               export CTDB_NFS_STATE_MNT
+       fi
 
-# Default service_start() and service_stop() functions.
-# These may be overridden in an eventscript.
-service_start ()
-{
-    service "$service_name" start
+       nfs_callout_cache="${_state_dir}/nfs_callout_cache"
+       nfs_callout_cache_callout="${nfs_callout_cache}/CTDB_NFS_CALLOUT"
+       nfs_callout_cache_ops="${nfs_callout_cache}/ops"
 }
 
-service_stop ()
+nfs_callout_register()
 {
-    service "$service_name" stop
-}
+       mkdir -p "$nfs_callout_cache_ops"
+       rm -f "$nfs_callout_cache_ops"/*
 
-##################################################################
+       echo "$CTDB_NFS_CALLOUT" >"$nfs_callout_cache_callout"
 
-ctdb_standard_event_handler ()
-{
-    case "$1" in
-       status)
-           ctdb_checkstatus
-           exit
-           ;;
-       setstatus)
-            shift
-           ctdb_setstatus "$@"
-           exit
-           ;;
-    esac
+       _t=$("$CTDB_NFS_CALLOUT" "register")
+       if [ -n "$_t" ]; then
+               echo "$_t" |
+                       while IFS="" read -r _op; do
+                               touch "${nfs_callout_cache_ops}/${_op}"
+                       done
+       else
+               touch "${nfs_callout_cache_ops}/ALL"
+       fi
 }
 
-iptables_wrapper ()
+nfs_callout()
 {
-    _family="$1" ; shift
-    if [ "$_family" = "inet6" ] ; then
-       _iptables_cmd="ip6tables"
-    else
-       _iptables_cmd="iptables"
-    fi
-
-    # iptables doesn't like being re-entered, so flock-wrap it.
-    flock -w 30 "${CTDB_VARDIR}/iptables-ctdb.flock" "$_iptables_cmd" "$@"
-}
+       # Re-run registration if $CTDB_NFS_CALLOUT has changed
+       _prev=""
+       if [ -r "$nfs_callout_cache_callout" ]; then
+               read -r _prev <"$nfs_callout_cache_callout"
+       fi
+       if [ "$CTDB_NFS_CALLOUT" != "$_prev" ]; then
+               nfs_callout_register
+       fi
 
-# AIX (and perhaps others?) doesn't have mktemp
-if ! type mktemp >/dev/null 2>&1 ; then
-    mktemp ()
-    {
-       _dir=false
-       if [ "$1" = "-d" ] ; then
-           _dir=true
-           shift
+       # Run the operation if it is registered...
+       if [ -e "${nfs_callout_cache_ops}/${1}" ] ||
+               [ -e "${nfs_callout_cache_ops}/ALL" ]; then
+               "$CTDB_NFS_CALLOUT" "$@"
        fi
-       _d="${TMPDIR:-/tmp}"
-       _hex10=$(dd if=/dev/urandom count=20 2>/dev/null | \
-           md5sum | \
-           sed -e 's@\(..........\).*@\1@')
-       _t="${_d}/tmp.${_hex10}"
-       (
-           umask 077
-           if $_dir ; then
-               mkdir "$_t"
-           else
-               >"$_t"
-           fi
-       )
-       echo "$_t"
-    }
-fi
+}
 
 ########################################################
 # tickle handling
 ########################################################
 
-update_tickles ()
+update_tickles()
 {
        _port="$1"
 
-       tickledir="$CTDB_VARDIR/state/tickles"
+       tickledir="${CTDB_SCRIPT_VARDIR}/tickles"
        mkdir -p "$tickledir"
 
-       ctdb_get_pnn
-
        # What public IPs do I hold?
-       _ips=$(ctdb -X ip | awk -F'|' -v pnn=$pnn '$3 == pnn {print $2}')
-
-       # IPs as a regexp choice
-       _ipschoice="($(echo $_ips | sed -e 's/ /|/g' -e 's/\./\\\\./g'))"
+       _pnn=$(ctdb_get_pnn)
+       _ips=$($CTDB -X ip | awk -F'|' -v pnn="$_pnn" '$3 == pnn {print $2}')
 
-       # Record connections to our public IPs in a temporary file
-       _my_connections="${tickledir}/${_port}.connections"
-       rm -f "$_my_connections"
-       netstat -tn |
-       awk -v destpat="^${_ipschoice}:${_port}\$" \
-         '$1 == "tcp" && $6 == "ESTABLISHED" && $4 ~ destpat {print $5, $4}' |
-       sort >"$_my_connections"
+       # IPs and port as ss filters
+       _ip_filter=""
+       for _ip in $_ips; do
+               _ip_filter="${_ip_filter}${_ip_filter:+ || }src [${_ip}]"
+       done
+       _port_filter="sport == :${_port}"
+
+       # Record connections to our public IPs in a temporary file.
+       # This temporary file is in CTDB's private state directory and
+       # $$ is used to avoid a very rare race involving CTDB's script
+       # debugging.  No security issue, nothing to see here...
+       _my_connections="${tickledir}/${_port}.connections.$$"
+       # Parentheses are needed around the filters for precedence but
+       # the parentheses can't be empty!
+       #
+       # Recent versions of ss print square brackets around IPv6
+       # addresses.  While it is desirable to update CTDB's address
+       # parsing and printing code, something needs to be done here
+       # for backward compatibility, so just delete the brackets.
+       ss -tn state established \
+               "${_ip_filter:+( ${_ip_filter} )}" \
+               "${_port_filter:+( ${_port_filter} )}" |
+               awk 'NR > 1 {print $4, $3}' |
+               tr -d '][' |
+               sort >"$_my_connections"
 
        # Record our current tickles in a temporary file
-       _my_tickles="${tickledir}/${_port}.tickles"
-       rm -f "$_my_tickles"
-       for _i in $_ips ; do
-               ctdb -X gettickles $_i $_port |
-               awk -F'|' 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
+       _my_tickles="${tickledir}/${_port}.tickles.$$"
+       for _i in $_ips; do
+               $CTDB -X gettickles "$_i" "$_port" |
+                       awk -F'|' 'NR > 1 { printf "%s:%s %s:%s\n", $2, $3, $4, $5 }'
        done |
-       sort >"$_my_tickles"
+               sort >"$_my_tickles"
 
        # Add tickles for connections that we haven't already got tickles for
        comm -23 "$_my_connections" "$_my_tickles" |
-       while read _src _dst ; do
-               ctdb addtickle $_src $_dst
-       done
+               $CTDB addtickle
 
        # Remove tickles for connections that are no longer there
        comm -13 "$_my_connections" "$_my_tickles" |
-       while read _src _dst ; do
-               ctdb deltickle $_src $_dst
-       done
+               $CTDB deltickle
+
+       rm -f "$_my_connections" "$_my_tickles"
 
-       rm -f "$_my_connections" "$_my_tickles" 
+       # Remove stale files from killed scripts
+       # Files can't have spaces in name, more portable than -print0/-0
+       # shellcheck disable=SC2038
+       (cd "$tickledir" && find . -type f -mmin +10 | xargs -r rm)
 }
 
 ########################################################
 # load a site local config file
 ########################################################
 
-[ -n "$CTDB_RC_LOCAL" -a -x "$CTDB_RC_LOCAL" ] && {
-       . "$CTDB_RC_LOCAL"
-}
-
-[ -x $CTDB_BASE/rc.local ] && {
-       . $CTDB_BASE/rc.local
+[ -x "${CTDB_BASE}/rc.local" ] && {
+       . "${CTDB_BASE}/rc.local"
 }
 
-[ -d $CTDB_BASE/rc.local.d ] && {
-       for i in $CTDB_BASE/rc.local.d/* ; do
+[ -d "${CTDB_BASE}/rc.local.d" ] && {
+       for i in "${CTDB_BASE}/rc.local.d"/*; do
                [ -x "$i" ] && . "$i"
        done
 }
 
-script_name="${0##*/}"       # basename
-service_fail_limit=1
-event_name="$1"
+script_name="${0##*/}" # basename