]> Frank Brehm's Git Trees - config/bruni/etc-mint-new1.git/commitdiff
committing changes in /etc after apt run
authorFrank Brehm <frank@brehm-online.com>
Mon, 11 May 2020 21:43:39 +0000 (23:43 +0200)
committerFrank Brehm <root@nadja.home.brehm-online.com>
Mon, 11 May 2020 21:43:39 +0000 (23:43 +0200)
Package changes:
+ctdb 2:4.7.6+dfsg~ubuntu-0ubuntu2.16 amd64
+libcephfs2 12.2.12-0ubuntu0.18.04.5 amd64
+librpm8 4.14.1+dfsg1-2 amd64
+librpmio8 4.14.1+dfsg1-2 amd64
+python-dnspython 1.15.0-1 all
+qasmixer 0.21.0-1.1 amd64
+qastools-common 0.21.0-1.1 all
+rpm-common 4.14.1+dfsg1-2 amd64
+rpm-i18n 4.14.1+dfsg1-2 all
+rpm2cpio 4.14.1+dfsg1-2 amd64
+samba 2:4.7.6+dfsg~ubuntu-0ubuntu2.16 amd64
+samba-dsdb-modules 2:4.7.6+dfsg~ubuntu-0ubuntu2.16 amd64
+samba-vfs-modules 2:4.7.6+dfsg~ubuntu-0ubuntu2.16 amd64
+tdb-tools 1.3.15-2 amd64
+winbind 2:4.7.6+dfsg~ubuntu-0ubuntu2.16 amd64

96 files changed:
.etckeeper
alternatives/tdbbackup [new symlink]
alternatives/tdbbackup.8.gz [new symlink]
cron.daily/samba [new file with mode: 0755]
ctdb/ctdb-crash-cleanup.sh [new file with mode: 0755]
ctdb/ctdbd.conf [new file with mode: 0644]
ctdb/debug-hung-script.sh [new file with mode: 0755]
ctdb/debug_locks.sh [new file with mode: 0755]
ctdb/events.d/00.ctdb [new file with mode: 0755]
ctdb/events.d/01.reclock [new file with mode: 0755]
ctdb/events.d/05.system [new file with mode: 0755]
ctdb/events.d/06.nfs [new file with mode: 0755]
ctdb/events.d/10.external [new file with mode: 0644]
ctdb/events.d/10.interface [new file with mode: 0755]
ctdb/events.d/11.natgw [new file with mode: 0755]
ctdb/events.d/11.routing [new file with mode: 0755]
ctdb/events.d/13.per_ip_routing [new file with mode: 0755]
ctdb/events.d/20.multipathd [new file with mode: 0755]
ctdb/events.d/31.clamd [new file with mode: 0755]
ctdb/events.d/40.vsftpd [new file with mode: 0755]
ctdb/events.d/41.httpd [new file with mode: 0755]
ctdb/events.d/49.winbind [new file with mode: 0755]
ctdb/events.d/50.samba [new file with mode: 0755]
ctdb/events.d/60.nfs [new file with mode: 0755]
ctdb/events.d/70.iscsi [new file with mode: 0755]
ctdb/events.d/91.lvs [new file with mode: 0755]
ctdb/events.d/99.timeout [new file with mode: 0755]
ctdb/events.d/README [new file with mode: 0644]
ctdb/functions [new file with mode: 0644]
ctdb/gcore_trace.sh [new file with mode: 0755]
ctdb/nfs-checks.d/00.portmapper.check [new file with mode: 0644]
ctdb/nfs-checks.d/10.status.check [new file with mode: 0644]
ctdb/nfs-checks.d/20.nfs.check [new file with mode: 0644]
ctdb/nfs-checks.d/30.nlockmgr.check [new file with mode: 0644]
ctdb/nfs-checks.d/40.mountd.check [new file with mode: 0644]
ctdb/nfs-checks.d/50.rquotad.check [new file with mode: 0644]
ctdb/nfs-checks.d/README [new file with mode: 0644]
ctdb/nfs-linux-kernel-callout [new file with mode: 0755]
ctdb/notify.d/README [new file with mode: 0644]
ctdb/notify.sh [new file with mode: 0755]
ctdb/statd-callout [new file with mode: 0755]
default/winbind [new file with mode: 0644]
group
group-
gshadow
gshadow-
init.d/ctdb [new file with mode: 0755]
init.d/nmbd [new file with mode: 0755]
init.d/samba-ad-dc [new file with mode: 0755]
init.d/smbd [new file with mode: 0755]
init.d/winbind [new file with mode: 0755]
logrotate.d/ctdb [new file with mode: 0644]
logrotate.d/samba [new file with mode: 0644]
logrotate.d/winbind [new file with mode: 0644]
rc0.d/K01ctdb [new symlink]
rc0.d/K01nmbd [new symlink]
rc0.d/K01samba-ad-dc [new symlink]
rc0.d/K01smbd [new symlink]
rc0.d/K01winbind [new symlink]
rc1.d/K01ctdb [new symlink]
rc1.d/K01nmbd [new symlink]
rc1.d/K01samba-ad-dc [new symlink]
rc1.d/K01smbd [new symlink]
rc1.d/K01winbind [new symlink]
rc2.d/S01ctdb [new symlink]
rc2.d/S01nmbd [new symlink]
rc2.d/S01samba-ad-dc [new symlink]
rc2.d/S01smbd [new symlink]
rc2.d/S01winbind [new symlink]
rc3.d/S01ctdb [new symlink]
rc3.d/S01nmbd [new symlink]
rc3.d/S01samba-ad-dc [new symlink]
rc3.d/S01smbd [new symlink]
rc3.d/S01winbind [new symlink]
rc4.d/S01ctdb [new symlink]
rc4.d/S01nmbd [new symlink]
rc4.d/S01samba-ad-dc [new symlink]
rc4.d/S01smbd [new symlink]
rc4.d/S01winbind [new symlink]
rc5.d/S01ctdb [new symlink]
rc5.d/S01nmbd [new symlink]
rc5.d/S01samba-ad-dc [new symlink]
rc5.d/S01smbd [new symlink]
rc5.d/S01winbind [new symlink]
rc6.d/K01ctdb [new symlink]
rc6.d/K01nmbd [new symlink]
rc6.d/K01samba-ad-dc [new symlink]
rc6.d/K01smbd [new symlink]
rc6.d/K01winbind [new symlink]
sudoers.d/ctdb [new file with mode: 0644]
systemd/system/multi-user.target.wants/ctdb.service [new symlink]
systemd/system/multi-user.target.wants/nmbd.service [new symlink]
systemd/system/multi-user.target.wants/smbd.service [new symlink]
systemd/system/multi-user.target.wants/winbind.service [new symlink]
systemd/system/samba-ad-dc.service [new symlink]
ufw/applications.d/samba [new file with mode: 0644]

index a1f89afa045fd4b9a97c36487ba3b1518df8f4f8..fd9d5774c6a16f4fa4273eba78716a1f7ea367e7 100755 (executable)
@@ -1113,6 +1113,7 @@ maybe chmod 0755 'cron.daily/man-db'
 maybe chmod 0755 'cron.daily/mdadm'
 maybe chmod 0755 'cron.daily/mlocate'
 maybe chmod 0755 'cron.daily/passwd'
+maybe chmod 0755 'cron.daily/samba'
 maybe chmod 0755 'cron.daily/ubuntu-advantage-tools'
 maybe chmod 0755 'cron.daily/update-notifier-common'
 maybe chmod 0755 'cron.hourly'
@@ -1131,6 +1132,47 @@ maybe chmod 0755 'cruft/filters-unex'
 maybe chmod 0644 'cruft/filters-unex/etckeeper'
 maybe chmod 0755 'cryptsetup-initramfs'
 maybe chmod 0644 'cryptsetup-initramfs/conf-hook'
+maybe chmod 0755 'ctdb'
+maybe chmod 0755 'ctdb/ctdb-crash-cleanup.sh'
+maybe chmod 0644 'ctdb/ctdbd.conf'
+maybe chmod 0755 'ctdb/debug-hung-script.sh'
+maybe chmod 0755 'ctdb/debug_locks.sh'
+maybe chmod 0755 'ctdb/events.d'
+maybe chmod 0755 'ctdb/events.d/00.ctdb'
+maybe chmod 0755 'ctdb/events.d/01.reclock'
+maybe chmod 0755 'ctdb/events.d/05.system'
+maybe chmod 0755 'ctdb/events.d/06.nfs'
+maybe chmod 0644 'ctdb/events.d/10.external'
+maybe chmod 0755 'ctdb/events.d/10.interface'
+maybe chmod 0755 'ctdb/events.d/11.natgw'
+maybe chmod 0755 'ctdb/events.d/11.routing'
+maybe chmod 0755 'ctdb/events.d/13.per_ip_routing'
+maybe chmod 0755 'ctdb/events.d/20.multipathd'
+maybe chmod 0755 'ctdb/events.d/31.clamd'
+maybe chmod 0755 'ctdb/events.d/40.vsftpd'
+maybe chmod 0755 'ctdb/events.d/41.httpd'
+maybe chmod 0755 'ctdb/events.d/49.winbind'
+maybe chmod 0755 'ctdb/events.d/50.samba'
+maybe chmod 0755 'ctdb/events.d/60.nfs'
+maybe chmod 0755 'ctdb/events.d/70.iscsi'
+maybe chmod 0755 'ctdb/events.d/91.lvs'
+maybe chmod 0755 'ctdb/events.d/99.timeout'
+maybe chmod 0644 'ctdb/events.d/README'
+maybe chmod 0644 'ctdb/functions'
+maybe chmod 0755 'ctdb/gcore_trace.sh'
+maybe chmod 0755 'ctdb/nfs-checks.d'
+maybe chmod 0644 'ctdb/nfs-checks.d/00.portmapper.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/10.status.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/20.nfs.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/30.nlockmgr.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/40.mountd.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/50.rquotad.check'
+maybe chmod 0644 'ctdb/nfs-checks.d/README'
+maybe chmod 0755 'ctdb/nfs-linux-kernel-callout'
+maybe chmod 0755 'ctdb/notify.d'
+maybe chmod 0644 'ctdb/notify.d/README'
+maybe chmod 0755 'ctdb/notify.sh'
+maybe chmod 0755 'ctdb/statd-callout'
 maybe chgrp 'lp' 'cups'
 maybe chmod 0755 'cups'
 maybe chmod 0644 'cups/cups-browsed.conf'
@@ -1256,6 +1298,7 @@ maybe chmod 0644 'default/ufw'
 maybe chmod 0644 'default/useradd'
 maybe chmod 0644 'default/virtlockd'
 maybe chmod 0644 'default/virtlogd'
+maybe chmod 0644 'default/winbind'
 maybe chmod 0644 'deluser.conf'
 maybe chmod 0755 'depmod.d'
 maybe chmod 0644 'depmod.d/ubuntu.conf'
@@ -1873,6 +1916,7 @@ maybe chmod 0755 'init.d/console-setup.sh'
 maybe chmod 0755 'init.d/cron'
 maybe chmod 0755 'init.d/cryptdisks'
 maybe chmod 0755 'init.d/cryptdisks-early'
+maybe chmod 0755 'init.d/ctdb'
 maybe chmod 0755 'init.d/cups'
 maybe chmod 0755 'init.d/cups-browsed'
 maybe chmod 0755 'init.d/dbus'
@@ -1900,6 +1944,7 @@ maybe chmod 0755 'init.d/mysql'
 maybe chmod 0755 'init.d/netfilter-persistent'
 maybe chmod 0755 'init.d/network-manager'
 maybe chmod 0755 'init.d/networking'
+maybe chmod 0755 'init.d/nmbd'
 maybe chmod 0755 'init.d/openvpn'
 maybe chmod 0755 'init.d/plymouth'
 maybe chmod 0755 'init.d/plymouth-log'
@@ -1908,15 +1953,18 @@ maybe chmod 0755 'init.d/pppd-dns'
 maybe chmod 0755 'init.d/procps'
 maybe chmod 0755 'init.d/rsync'
 maybe chmod 0755 'init.d/rsyslog'
+maybe chmod 0755 'init.d/samba-ad-dc'
 maybe chmod 0755 'init.d/saned'
 maybe chmod 0755 'init.d/screen-cleanup'
 maybe chmod 0755 'init.d/smartmontools'
+maybe chmod 0755 'init.d/smbd'
 maybe chmod 0755 'init.d/speech-dispatcher'
 maybe chmod 0755 'init.d/ssh'
 maybe chmod 0755 'init.d/udev'
 maybe chmod 0755 'init.d/ufw'
 maybe chmod 0755 'init.d/uuidd'
 maybe chmod 0755 'init.d/virtlogd'
+maybe chmod 0755 'init.d/winbind'
 maybe chmod 0755 'init.d/x11-common'
 maybe chmod 0644 'init/anacron.conf'
 maybe chmod 0644 'init/lightdm.conf'
@@ -2145,6 +2193,7 @@ maybe chmod 0644 'logrotate.d/apt'
 maybe chmod 0644 'logrotate.d/aptitude'
 maybe chmod 0644 'logrotate.d/btmp'
 maybe chmod 0644 'logrotate.d/chrony'
+maybe chmod 0644 'logrotate.d/ctdb'
 maybe chmod 0644 'logrotate.d/cups-daemon'
 maybe chmod 0644 'logrotate.d/dpkg'
 maybe chmod 0644 'logrotate.d/libvirtd'
@@ -2158,8 +2207,10 @@ maybe chmod 0644 'logrotate.d/mysql-server'
 maybe chmod 0644 'logrotate.d/pm-utils'
 maybe chmod 0644 'logrotate.d/ppp'
 maybe chmod 0644 'logrotate.d/rsyslog'
+maybe chmod 0644 'logrotate.d/samba'
 maybe chmod 0644 'logrotate.d/speech-dispatcher'
 maybe chmod 0644 'logrotate.d/ufw'
+maybe chmod 0644 'logrotate.d/winbind'
 maybe chmod 0644 'logrotate.d/wtmp'
 maybe chmod 0644 'lsb-release'
 maybe chmod 0644 'ltrace.conf'
@@ -2732,6 +2783,7 @@ maybe chmod 0440 'sudoers'
 maybe chmod 0755 'sudoers.d'
 maybe chmod 0440 'sudoers.d/0pwfeedback'
 maybe chmod 0440 'sudoers.d/README'
+maybe chmod 0440 'sudoers.d/ctdb'
 maybe chmod 0440 'sudoers.d/mintupdate'
 maybe chmod 0644 'sysctl.conf'
 maybe chmod 0755 'sysctl.d'
@@ -2810,6 +2862,7 @@ maybe chmod 0644 'ufw/applications.d/bind9'
 maybe chmod 0644 'ufw/applications.d/cups'
 maybe chmod 0644 'ufw/applications.d/openssh-server'
 maybe chmod 0644 'ufw/applications.d/postfix'
+maybe chmod 0644 'ufw/applications.d/samba'
 maybe chmod 0640 'ufw/before.init'
 maybe chmod 0640 'ufw/before.rules'
 maybe chmod 0640 'ufw/before6.rules'
diff --git a/alternatives/tdbbackup b/alternatives/tdbbackup
new file mode 120000 (symlink)
index 0000000..3edf584
--- /dev/null
@@ -0,0 +1 @@
+/usr/bin/tdbbackup.tdbtools
\ No newline at end of file
diff --git a/alternatives/tdbbackup.8.gz b/alternatives/tdbbackup.8.gz
new file mode 120000 (symlink)
index 0000000..c910beb
--- /dev/null
@@ -0,0 +1 @@
+/usr/share/man/man8/tdbbackup.tdbtools.8.gz
\ No newline at end of file
diff --git a/cron.daily/samba b/cron.daily/samba
new file mode 100755 (executable)
index 0000000..42fc98d
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+#
+# cron script to save a backup copy of /etc/samba/smbpasswd in /var/backups.
+#
+# Written by Eloy A. Paris <peloy@debian.org> for the Debian project.
+#
+
+BAK=/var/backups
+
+umask 022
+if cd $BAK; then
+       # Make sure /etc/samba/smbpasswd exists
+       if [ -f /etc/samba/smbpasswd ]; then
+               cmp -s smbpasswd.bak /etc/samba/smbpasswd || cp -p /etc/samba/smbpasswd smbpasswd.bak
+       fi
+fi
diff --git a/ctdb/ctdb-crash-cleanup.sh b/ctdb/ctdb-crash-cleanup.sh
new file mode 100755 (executable)
index 0000000..22b0ce8
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# This script can be called from a cronjob to automatically drop/release
+# all public ip addresses if CTDBD has crashed or stopped running.
+#
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# If ctdb is running, just exit
+if service ctdb status >/dev/null 2>&1 ; then
+    exit 0
+fi
+
+loadconfig ctdb
+
+[ -n "$CTDB_PUBLIC_ADDRESSES" ] || \
+       CTDB_PUBLIC_ADDRESSES="$CTDB_BASE/public_addresses"
+
+[ -f "$CTDB_PUBLIC_ADDRESSES" ] || \
+    die "No public addresses file found. Can't clean up."
+
+drop_all_public_ips 2>&1 | script_log "ctdb-crash-cleanup.sh"
+
+if [ -n "$CTDB_NATGW_PUBLIC_IP" ] ; then
+    drop_ip "$CTDB_NATGW_PUBLIC_IP" "ctdb-crash-cleanup.sh"
+fi
diff --git a/ctdb/ctdbd.conf b/ctdb/ctdbd.conf
new file mode 100644 (file)
index 0000000..2d525c5
--- /dev/null
@@ -0,0 +1,32 @@
+# Options to ctdbd, read by ctdbd_wrapper(1)
+#
+# See ctdbd.conf(5) for more information about CTDB configuration variables.
+
+# Shared recovery lock file to avoid split brain.  No default.
+#
+# Do NOT run CTDB without a recovery lock file unless you know exactly
+# what you are doing.
+# CTDB_RECOVERY_LOCK=/some/place/on/shared/storage
+
+# List of nodes in the cluster.  Default is below.
+# CTDB_NODES=/etc/ctdb/nodes
+
+# List of public addresses for providing NAS services.  No default.
+# CTDB_PUBLIC_ADDRESSES=/etc/ctdb/public_addresses
+
+# What services should CTDB manage?  Default is none.
+# CTDB_MANAGES_SAMBA=yes
+# CTDB_MANAGES_WINBIND=yes
+# CTDB_MANAGES_NFS=yes
+
+# Raise the file descriptor limit for CTDB?
+# CTDB_MAX_OPEN_FILES=10000
+
+# Default is to use the log file below instead of syslog.
+# CTDB_LOGGING=file:/var/log/log.ctdb
+
+# Default log level is NOTICE.  Want less logging?
+# CTDB_DEBUGLEVEL=ERR
+
+# Set some CTDB tunable variables during CTDB startup?
+# CTDB_SET_TDBMutexEnabled=0
diff --git a/ctdb/debug-hung-script.sh b/ctdb/debug-hung-script.sh
new file mode 100755 (executable)
index 0000000..da988c2
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# This script only works on Linux.  Please modify (and submit patches)
+# for other operating systems.
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig ctdb
+
+# Testing hook
+if [ -n "$CTDB_DEBUG_HUNG_SCRIPT_LOGFILE" ] ; then
+    tmp="${CTDB_DEBUG_HUNG_SCRIPT_LOGFILE}.part"
+    exec >>"$tmp" 2>&1
+fi
+
+(
+    # No use running several of these in parallel if, say, "releaseip"
+    # event hangs for multiple IPs.  In that case the output would be
+    # interleaved in the log and would just be confusing.
+    flock --wait 2 9 || exit 1
+
+    echo "===== Start of hung script debug for PID=\"$1\", event=\"$2\" ====="
+
+    echo "pstree -p -a ${1}:"
+    out=$(pstree -p -a "$1")
+    echo "$out"
+
+    # Check for processes matching a regular expression and print
+    # stack staces.  This could help confirm that certain processes
+    # are stuck in certain places such as the cluster filesystem.  The
+    # regexp must separate items with "|" and must not contain
+    # parentheses.  The default pattern can be replaced for testing.
+    default_pat='exportfs|rpcinfo'
+    pat="${CTDB_DEBUG_HUNG_SCRIPT_STACKPAT:-${default_pat}}"
+    echo "$out" |
+    sed -r -n "s@.*-(.*(${pat}).*),([0-9]*).*@\3 \1@p" |
+    while read pid name ; do
+       trace=$(cat "/proc/${pid}/stack" 2>/dev/null)
+       if [ $? -eq 0 ] ; then
+           echo "---- Stack trace of interesting process ${pid}[${name}] ----"
+           echo "$trace"
+       fi
+    done
+
+    if [ "$2" != "init" ] ; then
+       echo "---- ctdb scriptstatus ${2}: ----"
+       $CTDB scriptstatus "$2"
+    fi
+
+    echo "===== End of hung script debug for PID=\"$1\", event=\"$2\" ====="
+
+    if [ -n "$CTDB_DEBUG_HUNG_SCRIPT_LOGFILE" ] ; then
+       mv "$tmp" "$CTDB_DEBUG_HUNG_SCRIPT_LOGFILE"
+    fi
+
+) 9>"${CTDB_SCRIPT_VARDIR}/debug-hung-script.lock"
diff --git a/ctdb/debug_locks.sh b/ctdb/debug_locks.sh
new file mode 100755 (executable)
index 0000000..f678724
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+# This script parses /proc/locks and finds the processes that are holding
+# locks on CTDB databases.  For all those processes the script dumps a
+# stack trace.
+#
+# This script can be used only if Samba is configured to use fcntl locks
+# rather than mutex locks.
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# Default fallback location for database directories.
+# These can be overwritten from CTDB configuration
+CTDB_DBDIR="${CTDB_VARDIR}"
+CTDB_DBDIR_PERSISTENT="${CTDB_VARDIR}/persistent"
+
+loadconfig ctdb
+
+(
+    flock -n 9 || exit 1
+
+    echo "===== Start of debug locks PID=$$ ====="
+
+    # Create sed expression to convert inodes to names.
+    # Filenames don't contain dashes and we want basenames
+    # shellcheck disable=SC2035
+    sed_cmd=$(cd "$CTDB_DBDIR" &&
+                 stat -c "s#[0-9a-f]*:[0-9a-f]*:%i #%n #" *.tdb.* 2>/dev/null ;
+             cd "$CTDB_DBDIR_PERSISTENT" &&
+                 stat -c "s#[0-9a-f]*:[0-9a-f]*:%i #%n #" *.tdb.* 2>/dev/null)
+
+    # Parse /proc/locks and extract following information
+    #    pid process_name tdb_name offsets [W]
+    out=$( grep -F "POSIX  ADVISORY  WRITE" /proc/locks |
+    awk '{ if($2 == "->") { print $6, $7, $8, $9, "W" } else { print $5, $6, $7, $8 } }' |
+    while read pid rest ; do
+       pname=$(readlink "/proc/${pid}/exe")
+       echo "$pid $pname $rest"
+    done | sed -e "$sed_cmd" | grep "\.tdb" )
+
+    if [ -n "$out" ]; then
+       # Log information about locks
+       echo "$out"
+
+       # Find processes that are waiting for locks
+       dbs=$(echo "$out" | grep "W$" | awk '{print $3}')
+       all_pids=""
+       for db in $dbs ; do
+           pids=$(echo "$out" | grep -v "W$" | grep "$db" | grep -v ctdbd | awk '{print $1}')
+           all_pids="$all_pids $pids"
+       done
+       # Use word splitting to squash whitespace
+       # shellcheck disable=SC2086
+       pids=$(echo $all_pids | tr " " "\n" | sort -u)
+
+       # For each process waiting, log stack trace
+       for pid in $pids ; do
+           echo "----- Stack trace for PID=$pid -----"
+           # x is intentionally ignored
+           # shellcheck disable=SC2034
+           read x x state x <"/proc/${pid}/stat"
+           if [ "$state" = "D" ] ; then
+               # Don't run gstack on a process in D state since
+               # gstack will hang until the process exits D state.
+               # Although it is possible for a process to transition
+               # to D state after this check, it is unlikely because
+               # if a process is stuck in D state then it is probably
+               # the reason why this script was called.  Note that a
+               # kernel stack almost certainly won't help diagnose a
+               # deadlock... but it will probably give us someone to
+               # blame!
+               echo "----- Process in D state, printing kernel stack only"
+               cat "/proc/${pid}/stack"
+           else
+               gstack "$pid"
+               # gcore -o /var/log/core-deadlock-ctdb $pid
+           fi
+       done
+    fi
+
+    echo "===== End of debug locks PID=$$ ====="
+)9>"${CTDB_SCRIPT_VARDIR}/debug_locks.lock" | script_log "ctdbd-lock"
+
+exit 0
diff --git a/ctdb/events.d/00.ctdb b/ctdb/events.d/00.ctdb
new file mode 100755 (executable)
index 0000000..d60b9aa
--- /dev/null
@@ -0,0 +1,151 @@
+#!/bin/sh
+
+# Event script for ctdb-specific setup and other things that don't fit
+# elsewhere.
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+############################################################
+
+# type is commonly supported and more portable than which(1)
+# shellcheck disable=SC2039
+select_tdb_checker ()
+{
+    # Find the best TDB consistency check available.
+    use_tdb_tool_check=false
+    type tdbtool >/dev/null 2>&1 && found_tdbtool=true
+    type tdbdump >/dev/null 2>&1 && found_tdbdump=true
+
+    if $found_tdbtool && echo "help" | tdbtool | grep -q check ; then
+           use_tdb_tool_check=true
+    elif $found_tdbtool && $found_tdbdump ; then
+           cat <<EOF
+WARNING: The installed 'tdbtool' does not offer the 'check' subcommand.
+ Using 'tdbdump' for database checks.
+ Consider updating 'tdbtool' for better checks!
+EOF
+    elif $found_tdbdump ; then
+       cat <<EOF
+WARNING: 'tdbtool' is not available.
+ Using 'tdbdump' to check the databases.
+ Consider installing a recent 'tdbtool' for better checks!
+EOF
+    else
+       cat <<EOF
+WARNING: Cannot check databases since neither
+ 'tdbdump' nor 'tdbtool check' is available.
+ Consider installing tdbtool or at least tdbdump!
+EOF
+        return 1
+    fi
+}
+
+check_tdb ()
+{
+    _db="$1"
+
+    if $use_tdb_tool_check ; then
+       # tdbtool always exits with 0  :-(
+       if timeout 10 tdbtool "$_db" check 2>/dev/null |
+           grep -q "Database integrity is OK" ; then
+           return 0
+       else
+           return 1
+       fi
+    else
+       timeout 10 tdbdump "$_db" >/dev/null 2>/dev/null
+       return $?
+    fi
+}
+
+check_persistent_databases ()
+{
+    _dir="${CTDB_DBDIR_PERSISTENT:-${CTDB_DBDIR:-${CTDB_VARDIR}}/persistent}"
+    [ -d "$_dir" ] || return 0
+
+    [ "${CTDB_MAX_PERSISTENT_CHECK_ERRORS:-0}" = "0" ] || return 0
+
+    for _db in "$_dir/"*.tdb.*[0-9] ; do
+       [ -r "$_db" ] || continue
+       check_tdb "$_db" || \
+           die "Persistent database $_db is corrupted! CTDB will not start."
+    done
+}
+
+check_non_persistent_databases ()
+{
+    _dir="${CTDB_DBDIR:-${CTDB_VARDIR}}"
+    [ -d "$_dir" ] || return 0
+
+    for _db in "${_dir}/"*.tdb.*[0-9] ; do
+       [ -r "$_db" ] || continue
+       check_tdb "$_db" || {
+           _backup="${_db}.$(date +'%Y%m%d.%H%M%S.%N').corrupt"
+           cat <<EOF
+WARNING: database ${_db} is corrupted.
+ Moving to backup ${_backup} for later analysis.
+EOF
+           mv "$_db" "$_backup"
+
+           # Now remove excess backups
+           _max="${CTDB_MAX_CORRUPT_DB_BACKUPS:-10}"
+           _bdb="${_db##*/}" # basename
+           find "$_dir" -name "${_bdb}.*.corrupt" |
+                   sort -r |
+                   tail -n +$((_max + 1)) |
+                   xargs rm -f
+       }
+    done
+}
+
+set_ctdb_variables ()
+{
+    # set any tunables from the config file
+    set | sed -n '/^CTDB_SET_/s/=.*//p' |
+    while read v; do
+       varname="${v#CTDB_SET_}"
+       value=$(eval echo "\$$v")
+       if $CTDB setvar "$varname" "$value" ; then
+           echo "Set $varname to $value"
+       else
+           echo "Invalid configuration: CTDB_SET_${varname}=${value}"
+           return 1
+       fi
+    done
+}
+
+############################################################
+
+ctdb_check_args "$@"
+
+case "$1" in
+init)
+        # make sure we have a blank state directory for the scripts to work with
+       rm -rf "$CTDB_SCRIPT_VARDIR"
+       mkdir -p "$CTDB_SCRIPT_VARDIR" || \
+           die "mkdir -p ${CTDB_SCRIPT_VARDIR} - failed - $?" $?
+
+       if select_tdb_checker ; then
+           check_persistent_databases || exit $?
+           check_non_persistent_databases
+       fi
+       ;;
+
+setup)
+       # Set any tunables from the config file
+       set_ctdb_variables || \
+           die "Aborting setup due to invalid configuration - fix typos, remove unknown tunables"
+       ;;
+
+startup)
+       $CTDB attach ctdb.tdb persistent
+       ;;
+esac
+
+# all OK
+exit 0
diff --git a/ctdb/events.d/01.reclock b/ctdb/events.d/01.reclock
new file mode 100755 (executable)
index 0000000..cceb672
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+# script to check accessibility to the reclock file on a node
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+# If CTDB_RECOVERY_LOCK specifies a helper then exit because this
+# script can't do anything useful.
+case "$CTDB_RECOVERY_LOCK" in
+!*) exit 0 ;;
+esac
+
+case "$1" in
+init)
+       ctdb_counter_init
+
+       if [ -n "$CTDB_RECOVERY_LOCK" ] ; then
+           d=$(dirname "$CTDB_RECOVERY_LOCK")
+           mkdir -vp "$d"
+       fi
+       ;;
+
+monitor)
+       # Early exit if not using a reclock file
+       [ -n "$CTDB_RECOVERY_LOCK" ] || exit 0
+
+       # Try to stat the reclock file as a background process so that
+       # we don't block in case the cluster filesystem is unavailable
+       (
+           if stat "$CTDB_RECOVERY_LOCK" ; then
+               # We could stat the file, reset the counter
+               ctdb_counter_init
+           fi
+       ) >/dev/null 2>&1 &
+
+       ctdb_counter_incr
+       num_fails=$(ctdb_counter_get)
+       if [ "$num_fails" -ge 200 ] ; then
+           echo "Reclock file \"$CTDB_RECOVERY_LOCK\" can not be accessed. Shutting down."
+           df
+           sleep 1
+           $CTDB shutdown
+           exit 1
+       elif [ "$num_fails" -ge 4 ] ; then
+               die "ERROR: ${num_fails} consecutive failures checking reclock"
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/05.system b/ctdb/events.d/05.system
new file mode 100755 (executable)
index 0000000..3931eac
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+# ctdb event script for checking local file system utilization
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+service_state_dir=$(ctdb_setup_service_state_dir "system-monitoring") || exit $?
+
+validate_percentage ()
+{
+    case "$1" in
+       "") return 1 ;;  # A failure that doesn't need a warning
+       [0-9]|[0-9][0-9]|100) return 0 ;;
+       *) echo "WARNING: ${1} is an invalid percentage${2:+ in \"}${2}${2:+\"} check"
+          return 1
+    esac
+}
+
+check_thresholds ()
+{
+    _thing="$1"
+    _thresholds="$2"
+    _usage="$3"
+    _unhealthy_callout="$4"
+
+    case "$_thresholds" in
+       *:*)
+           _warn_threshold="${_thresholds%:*}"
+           _unhealthy_threshold="${_thresholds#*:}"
+           ;;
+       *)
+           _warn_threshold="$_thresholds"
+           _unhealthy_threshold=""
+    esac
+
+    _t=$(echo "$_thing" | sed -e 's@/@SLASH_@g' -e 's@ @_@g')
+    _cache="${service_state_dir}/cache_${_t}"
+    if validate_percentage "$_unhealthy_threshold" "$_thing" ; then
+        if [ "$_usage" -ge "$_unhealthy_threshold" ] ; then
+           echo "ERROR: ${_thing} utilization ${_usage}% >= threshold ${_unhealthy_threshold}%"
+           eval "$_unhealthy_callout"
+           echo "$_usage" >"$_cache"
+           exit 1
+        fi
+    fi
+
+    if validate_percentage "$_warn_threshold" "$_thing" ; then
+        if [ "$_usage" -ge "$_warn_threshold" ] ; then
+           if [ -r "$_cache" ] ; then
+               read _prev <"$_cache"
+           else
+               _prev=""
+           fi
+           if [ "$_usage" != "$_prev" ] ; then
+               echo "WARNING: ${_thing} utilization ${_usage}% >= threshold ${_warn_threshold}%"
+               echo "$_usage" >"$_cache"
+           fi
+       else
+           if [ -r "$_cache" ] ; then
+               echo "NOTICE: ${_thing} utilization ${_usage}% < threshold ${_warn_threshold}%"
+           fi
+           rm -f "$_cache"
+        fi
+    fi
+}
+
+set_monitor_filsystem_usage_defaults ()
+{
+    _fs_defaults_cache="${service_state_dir}/cache_monitor_filsystem_usage_defaults"
+
+    if [ ! -r "$_fs_defaults_cache" ] ; then
+       # Determine filesystem for each database directory, generate
+       # an entry to warn at 90%, de-duplicate entries, put all items
+       # on 1 line (so the read below gets everything)
+       for _t in "${CTDB_DBDIR:-${CTDB_VARDIR}}" \
+                     "${CTDB_DBDIR_PERSISTENT:-${CTDB_VARDIR}/persistent}" \
+                     "${CTDB_DBDIR_STATE:-${CTDB_VARDIR}/state}" ; do
+           df -kP "$_t" | awk 'NR == 2 { printf "%s:90\n", $6 }'
+       done | sort -u | xargs >"$_fs_defaults_cache"
+    fi
+
+    read CTDB_MONITOR_FILESYSTEM_USAGE <"$_fs_defaults_cache"
+}
+
+monitor_filesystem_usage ()
+{
+    if [ -z "$CTDB_MONITOR_FILESYSTEM_USAGE" ] ; then
+       set_monitor_filsystem_usage_defaults
+    fi
+
+    # Check each specified filesystem, specified in format
+    # <fs_mount>:<fs_warn_threshold>[:fs_unhealthy_threshold]
+    for _fs in $CTDB_MONITOR_FILESYSTEM_USAGE ; do
+       _fs_mount="${_fs%%:*}"
+       _fs_thresholds="${_fs#*:}"
+
+        if [ ! -d "$_fs_mount" ]; then
+            echo "WARNING: Directory ${_fs_mount} does not exist"
+           continue
+        fi
+
+        # Get current utilization
+        _fs_usage=$(df -kP "$_fs_mount" | \
+                          sed -n -e 's@.*[[:space:]]\([[:digit:]]*\)%.*@\1@p')
+        if [ -z "$_fs_usage" ] ; then
+            echo "WARNING: Unable to get FS utilization for ${_fs_mount}"
+           continue
+        fi
+
+       check_thresholds "Filesystem ${_fs_mount}" \
+                        "$_fs_thresholds" \
+                        "$_fs_usage"
+    done
+}
+
+dump_memory_info ()
+{
+    get_proc "meminfo"
+    ps auxfww
+    set_proc "sysrq-trigger" "m"
+}
+
+monitor_memory_usage ()
+{
+    # Defaults
+    if [ -z "$CTDB_MONITOR_MEMORY_USAGE" ] ; then
+       CTDB_MONITOR_MEMORY_USAGE=80
+    fi
+    if [ -z "$CTDB_MONITOR_SWAP_USAGE" ] ; then
+       CTDB_MONITOR_SWAP_USAGE=25
+    fi
+
+    _meminfo=$(get_proc "meminfo")
+    # Intentional word splitting here
+    # shellcheck disable=SC2046
+    set -- $(echo "$_meminfo" | awk '
+$1 == "MemAvailable:" { memavail += $2 }
+$1 == "MemFree:"      { memfree  += $2 }
+$1 == "Cached:"       { memfree  += $2 }
+$1 == "Buffers:"      { memfree  += $2 }
+$1 == "MemTotal:"     { memtotal  = $2 }
+$1 == "SwapFree:"     { swapfree  = $2 }
+$1 == "SwapTotal:"    { swaptotal = $2 }
+END {
+    if (memavail != 0) { memfree = memavail ; }
+    if (memtotal != 0) { print int((memtotal - memfree) / memtotal * 100) ; } else { print 0 ; }
+    if (swaptotal != 0) { print int((swaptotal - swapfree) / swaptotal * 100) ; } else { print 0 ; }
+}')
+    _mem_usage="$1"
+    _swap_usage="$2"
+
+    check_thresholds "System memory" \
+                    "$CTDB_MONITOR_MEMORY_USAGE" \
+                    "$_mem_usage" \
+                    dump_memory_info
+
+    check_thresholds "System swap" \
+                    "$CTDB_MONITOR_SWAP_USAGE" \
+                    "$_swap_usage" \
+                    dump_memory_info
+}
+
+
+case "$1" in
+monitor)
+       monitor_filesystem_usage
+       monitor_memory_usage
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/06.nfs b/ctdb/events.d/06.nfs
new file mode 100755 (executable)
index 0000000..e59f265
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+# script to manage nfs in a clustered environment
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="nfs"
+
+loadconfig
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+######################################################################
+
+nfs_callout_pre ()
+{
+       _event="$1"
+       shift
+
+       nfs_callout "${_event}-pre" "$@"
+}
+
+######################################################################
+
+nfs_callout_init "$service_state_dir"
+
+is_ctdb_managed_service || exit 0
+
+case "$1" in
+takeip)
+       nfs_callout_pre "$@"
+       ;;
+
+releaseip)
+       nfs_callout_pre "$@"
+       ;;
+esac
diff --git a/ctdb/events.d/10.external b/ctdb/events.d/10.external
new file mode 100644 (file)
index 0000000..884357b
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# Eventscript for CTDB to cope with externally managed public IP addresses
+
+# If DisableIPFailover is set:
+#
+# * 10.interface must be disabled.
+# * For connection tracking/killing to work this script must be enabled.
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+if [ -z "$CTDB_PUBLIC_ADDRESSES" ] ; then
+    exit 0
+fi
+
+if [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] ; then
+    if [ "$1" = "init" ]; then
+       echo "No public addresses file found. Nothing to do for 10.interfaces.external"
+    fi
+    exit 0
+fi
+
+takeover_assigned_ips ()
+{
+    _pnn=$(ctdb_get_pnn)
+
+    $CTDB -X ip |
+    awk -F'|' '{print $2}' |
+    while read ip ; do
+       _ip_details=$(ip_maskbits_iface "$ip")
+       if [ -n "$_ip_details" ] ; then
+           echo "Assigning $ip to this node (${_pnn})"
+           $CTDB moveip "$ip" "$_pnn"
+       fi
+    done
+}
+
+ctdb_check_args "$@"
+
+case "$1" in
+    startup)
+       takeover_assigned_ips
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/10.interface b/ctdb/events.d/10.interface
new file mode 100755 (executable)
index 0000000..97227e8
--- /dev/null
@@ -0,0 +1,262 @@
+#!/bin/sh
+
+#################################
+# interface event script for ctdb
+# this adds/removes IPs from your 
+# public interface
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+[ -z "$CTDB_PUBLIC_ADDRESSES" ] && {
+       CTDB_PUBLIC_ADDRESSES="${CTDB_BASE}/public_addresses"
+}
+
+[ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
+       if [ "$1" = "init" ]; then
+               echo "No public addresses file found. Nothing to do for 10.interfaces"
+       fi
+       exit 0
+}
+
+# This sets $all_interfaces as a side-effect.
+get_all_interfaces ()
+{
+    # Get all the interfaces listed in the public_addresses file
+    all_interfaces=$(sed -e "s/^[^\t ]*[\t ]*//" \
+                        -e "s/,/ /g" \
+                        -e "s/[\t ]*$//" "$CTDB_PUBLIC_ADDRESSES")
+
+    # Add some special interfaces if they're defined
+    [ "$CTDB_PUBLIC_INTERFACE" ] && all_interfaces="$CTDB_PUBLIC_INTERFACE $all_interfaces"
+
+    # Get the interfaces for which CTDB has public IPs configured.
+    # That is, for all but the 1st line, get the 1st field.
+    ctdb_ifaces=$($CTDB -X ifaces | sed -e '1d' -e 's@^|@@' -e 's@|.*@@')
+
+    # Add $ctdb_interfaces and uniquify
+    # Use word splitting to squash whitespace
+    # shellcheck disable=SC2086
+    all_interfaces=$(echo $all_interfaces $ctdb_ifaces | tr ' ' '\n' | sort -u)
+}
+
+monitor_interfaces()
+{
+       get_all_interfaces
+
+       down_interfaces_found=false
+       up_interfaces_found=false
+
+       # Note that this loop must not exit early.  It must process
+       # all interfaces so that the correct state for each interface
+       # is set in CTDB using setifacelink.
+       for _iface in $all_interfaces ; do
+               if interface_monitor "$_iface" ; then
+                       up_interfaces_found=true
+                       $CTDB setifacelink "$_iface" up >/dev/null 2>&1
+               else
+                       down_interfaces_found=true
+                       $CTDB setifacelink "$_iface" down >/dev/null 2>&1
+               fi
+       done
+
+       if ! $down_interfaces_found ; then
+               return 0
+       fi
+
+       if ! $up_interfaces_found ; then
+               return 1
+       fi
+
+       if [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" != "yes" ]; then
+               return 1
+       fi
+
+       return 0
+}
+
+# Sets: iface, ip, maskbits
+get_iface_ip_maskbits ()
+{
+    _iface_in="$1"
+    ip="$2"
+    _maskbits_in="$3"
+
+    # Intentional word splitting here
+    # shellcheck disable=SC2046
+    set -- $(ip_maskbits_iface "$ip")
+    if [ -n "$1" ] ; then
+       maskbits="$1"
+       iface="$2"
+
+       if [ "$iface" != "$_iface_in" ] ; then
+           printf \
+               'WARNING: Public IP %s hosted on interface %s but VNN says %s\n' \
+               "$ip" "$iface" "$_iface_in"
+       fi
+       if [ "$maskbits" != "$_maskbits_in" ] ; then
+           printf \
+               'WARNING: Public IP %s has %s bit netmask but VNN says %s\n' \
+                   "$ip" "$maskbits" "$_maskbits_in"
+       fi
+    else
+       die "ERROR: Unable to determine interface for IP ${ip}"
+    fi
+}
+
+ip_block ()
+{
+       _ip="$1"
+       _iface="$2"
+
+       case "$_ip" in
+       *:*) _family="inet6" ;;
+       *)   _family="inet"  ;;
+       esac
+
+       # Extra delete copes with previously killed script
+       iptables_wrapper "$_family" \
+                        -D INPUT -i "$_iface" -d "$_ip" -j DROP 2>/dev/null
+       iptables_wrapper "$_family" \
+                        -I INPUT -i "$_iface" -d "$_ip" -j DROP
+}
+
+ip_unblock ()
+{
+       _ip="$1"
+       _iface="$2"
+
+       case "$_ip" in
+       *:*) _family="inet6" ;;
+       *)   _family="inet"  ;;
+       esac
+
+       iptables_wrapper "$_family" \
+                        -D INPUT -i "$_iface" -d "$_ip" -j DROP 2>/dev/null
+}
+
+ctdb_check_args "$@"
+
+case "$1" in
+init)
+       # make sure that we only respond to ARP messages from the NIC where
+       # a particular ip address is associated.
+       get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
+           set_proc sys/net/ipv4/conf/all/arp_filter 1
+       }
+
+       _promote="sys/net/ipv4/conf/all/promote_secondaries"
+       get_proc "$_promote" >/dev/null 2>&1 || \
+           die "Public IPs only supported if promote_secondaries is available"
+
+       # make sure we drop any ips that might still be held if
+       # previous instance of ctdb got killed with -9 or similar
+       drop_all_public_ips
+       ;;
+
+startup)
+       monitor_interfaces
+       ;;
+
+takeip)
+       iface=$2
+       ip=$3
+       maskbits=$4
+
+       add_ip_to_iface "$iface" "$ip" "$maskbits" || {
+               exit 1;
+       }
+
+       # In case a previous "releaseip" for this IP was killed...
+       ip_unblock "$ip" "$iface"
+
+       flush_route_cache
+       ;;
+
+releaseip)
+       # releasing an IP is a bit more complex than it seems. Once the IP
+       # is released, any open tcp connections to that IP on this host will end
+       # up being stuck. Some of them (such as NFS connections) will be unkillable
+       # so we need to use the killtcp ctdb function to kill them off. We also
+       # need to make sure that no new connections get established while we are
+       # doing this! So what we do is this:
+       # 1) firewall this IP, so no new external packets arrive for it
+       # 2) find existing connections, and kill them
+       # 3) remove the IP from the interface
+       # 4) remove the firewall rule
+       shift
+       get_iface_ip_maskbits "$@"
+
+       ip_block "$ip" "$iface"
+
+       kill_tcp_connections "$iface" "$ip"
+
+       delete_ip_from_iface "$iface" "$ip" "$maskbits" || {
+               ip_unblock "$ip" "$iface"
+               exit 1
+       }
+
+       ip_unblock "$ip" "$iface"
+
+       flush_route_cache
+       ;;
+
+updateip)
+       # moving an IP is a bit more complex than it seems.
+       # First we drop all traffic on the old interface.
+       # Then we try to add the ip to the new interface and before
+       # we finally remove it from the old interface.
+       #
+       # 1) firewall this IP, so no new external packets arrive for it
+       # 2) remove the IP from the old interface (and new interface, to be sure)
+       # 3) add the IP to the new interface
+       # 4) remove the firewall rule
+       # 5) use ctdb gratarp to propagate the new mac address
+       # 6) use netstat -tn to find existing connections, and tickle them
+       _oiface=$2
+       niface=$3
+       _ip=$4
+       _maskbits=$5
+
+       get_iface_ip_maskbits "$_oiface" "$_ip" "$_maskbits"
+       oiface="$iface"
+
+       # Could check maskbits too.  However, that should never change
+       # so we want to notice if it does.
+       if [ "$oiface" = "$niface" ] ; then
+               echo "Redundant \"updateip\" - ${ip} already on ${niface}"
+               exit 0
+       fi
+
+       ip_block "$ip" "$oiface"
+
+       delete_ip_from_iface "$oiface" "$ip" "$maskbits" 2>/dev/null
+       delete_ip_from_iface "$niface" "$ip" "$maskbits" 2>/dev/null
+
+       add_ip_to_iface "$niface" "$ip" "$maskbits" || {
+               ip_unblock "$ip" "$oiface"
+               exit 1
+       }
+
+       ip_unblock "$ip" "$oiface"
+
+       flush_route_cache
+
+       # propagate the new mac address
+       $CTDB gratarp "$ip" "$niface"
+
+       # tickle all existing connections, so that dropped packets
+       # are retransmited and the tcp streams work
+       tickle_tcp_connections "$ip"
+       ;;
+
+monitor)
+       monitor_interfaces || exit 1
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/11.natgw b/ctdb/events.d/11.natgw
new file mode 100755 (executable)
index 0000000..4dc023a
--- /dev/null
@@ -0,0 +1,246 @@
+#!/bin/sh
+# Script to set up one of the nodes as a NAT gateway for all other nodes.
+# This is used to ensure that all nodes in the cluster can still originate
+# traffic to the external network even if there are no public addresses
+# available.
+#
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name=natgw
+
+loadconfig
+
+[ -n "$CTDB_NATGW_NODES" ] || exit 0
+export CTDB_NATGW_NODES
+
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+natgw_cfg_new="${service_state_dir}/cfg_new"
+natgw_cfg_old="${service_state_dir}/cfg_old"
+natgw_master_old="${service_state_dir}/master_old"
+
+ctdb_natgw_slave_only ()
+{
+    _ip_address=$(ctdb_get_ip_address)
+
+    awk -v my_ip="$_ip_address" \
+       '$1 == my_ip { if ($2 ~ "slave-only") { exit 0 } else { exit 1 } }' \
+       "$CTDB_NATGW_NODES"
+}
+
+natgw_check_config ()
+{
+    [ -r "$CTDB_NATGW_NODES" ] || \
+       die "error: CTDB_NATGW_NODES=${CTDB_NATGW_NODES} unreadable"
+    if ! ctdb_natgw_slave_only ; then
+       [ -n "$CTDB_NATGW_PUBLIC_IP" ] || \
+           die "Invalid configuration: CTDB_NATGW_PUBLIC_IP not set"
+       [ -n "$CTDB_NATGW_PUBLIC_IFACE" ] || \
+           die "Invalid configuration: CTDB_NATGW_PUBLIC_IFACE not set"
+    fi
+    [ -n "$CTDB_NATGW_PRIVATE_NETWORK" ] || \
+           die "Invalid configuration: CTDB_NATGW_PRIVATE_NETWORK not set"
+
+    if [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" = "yes" ] ; then
+           die "Invalid configuration: CTDB_PARTIALLY_ONLINE_INTERFACES=yes incompatible with NAT gateway"
+    fi
+
+    # The default is to create a single default route
+    [ -n "$CTDB_NATGW_STATIC_ROUTES" ] || CTDB_NATGW_STATIC_ROUTES="0.0.0.0/0"
+}
+
+natgw_write_config ()
+{
+    _f="$1"
+
+    cat >"$_f" <<EOF
+CTDB_NATGW_NODES="$CTDB_NATGW_NODES"
+CTDB_NATGW_PUBLIC_IP="$CTDB_NATGW_PUBLIC_IP"
+CTDB_NATGW_PUBLIC_IFACE="$CTDB_NATGW_PUBLIC_IFACE"
+CTDB_NATGW_DEFAULT_GATEWAY="$CTDB_NATGW_DEFAULT_GATEWAY"
+CTDB_NATGW_PRIVATE_NETWORK="$CTDB_NATGW_PRIVATE_NETWORK"
+CTDB_NATGW_STATIC_ROUTES="$CTDB_NATGW_STATIC_ROUTES"
+EOF
+}
+
+natgw_config_has_changed ()
+{
+    natgw_write_config "$natgw_cfg_new"
+
+    # Non-existent old returns true, no log message
+    if [ ! -f "$natgw_cfg_old" ] ; then
+       return 0
+    fi
+
+    # Handle no change
+    if cmp "$natgw_cfg_old" "$natgw_cfg_new" >/dev/null 2>&1 ; then
+       return 1
+    fi
+
+    echo "NAT gateway configuration has changed"
+    return 0
+}
+
+_natgw_clear ()
+{
+    _ip="${CTDB_NATGW_PUBLIC_IP%/*}"
+    _maskbits="${CTDB_NATGW_PUBLIC_IP#*/}"
+
+    delete_ip_from_iface \
+       "$CTDB_NATGW_PUBLIC_IFACE" "$_ip" "$_maskbits" >/dev/null 2>&1
+    for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do
+       _net="${_net_gw%@*}"
+       ip route del "$_net" metric 10 >/dev/null 2>/dev/null
+    done
+
+    # Delete the masquerading setup from a previous iteration where we
+    # were the NAT-GW
+    iptables -D POSTROUTING -t nat \
+       -s "$CTDB_NATGW_PRIVATE_NETWORK" ! -d "$CTDB_NATGW_PRIVATE_NETWORK" \
+       -j MASQUERADE >/dev/null 2>/dev/null
+
+    iptables -D INPUT -p tcp --syn -d "${_ip}/32" -j REJECT 2>/dev/null
+}
+
+natgw_clear ()
+{
+    if [ -r "$natgw_cfg_old" ] ; then
+       (. "$natgw_cfg_old" ; _natgw_clear)
+    else
+       _natgw_clear
+    fi
+}
+
+natgw_set_master ()
+{
+    set_proc sys/net/ipv4/ip_forward 1
+    iptables -A POSTROUTING -t nat \
+       -s "$CTDB_NATGW_PRIVATE_NETWORK" ! -d "$CTDB_NATGW_PRIVATE_NETWORK" \
+       -j MASQUERADE
+
+    # block all incoming connections to the NATGW IP address
+    ctdb_natgw_public_ip_host="${CTDB_NATGW_PUBLIC_IP%/*}/32"
+    iptables -D INPUT -p tcp --syn \
+       -d "$ctdb_natgw_public_ip_host" -j REJECT 2>/dev/null
+    iptables -I INPUT -p tcp --syn \
+       -d "$ctdb_natgw_public_ip_host" -j REJECT 2>/dev/null
+
+    ip addr add "$CTDB_NATGW_PUBLIC_IP" dev "$CTDB_NATGW_PUBLIC_IFACE"
+    for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do
+       _net="${_net_gw%@*}"
+       if [ "$_net" != "$_net_gw" ] ; then
+           _gw="${_net_gw#*@}"
+       else
+           _gw="$CTDB_NATGW_DEFAULT_GATEWAY"
+       fi
+
+       [ -n "$_gw" ] || continue
+       ip route add "$_net" metric 10 via "$_gw"
+    done
+}
+
+natgw_set_slave ()
+{
+    _natgwip="$1"
+
+    for _net_gw in $CTDB_NATGW_STATIC_ROUTES ; do
+       _net="${_net_gw%@*}"
+       ip route add "$_net" via "$_natgwip" metric 10
+    done
+}
+
+natgw_ensure_master ()
+{
+    # Intentional word splitting here
+    # shellcheck disable=SC2046
+    set -- $("${CTDB_HELPER_BINDIR}/ctdb_natgw" master)
+    natgwmaster="${1:--1}" # Default is -1, for failure above
+    natgwip="$2"
+
+    if [ "$natgwmaster" = "-1" ]; then
+       # Fail...
+       die "There is no NATGW master node"
+    fi
+}
+
+natgw_master_has_changed ()
+{
+    if [ -r "$natgw_master_old" ] ; then
+       read _old_natgwmaster <"$natgw_master_old"
+    else
+       _old_natgwmaster=""
+    fi
+    [ "$_old_natgwmaster" != "$natgwmaster" ]
+}
+
+natgw_save_state ()
+{
+    echo "$natgwmaster" >"$natgw_master_old"
+    # Created by natgw_config_has_changed()
+    mv "$natgw_cfg_new" "$natgw_cfg_old"
+}
+
+
+case "$1" in
+setup)
+       natgw_check_config
+       ;;
+
+startup)
+       natgw_check_config
+
+       # Error if CTDB_NATGW_PUBLIC_IP is listed in public addresses
+       ip_pat=$(echo "$CTDB_NATGW_PUBLIC_IP" | sed -e 's@\.@\\.@g')
+       if grep -q "^${ip_pat}[[:space:]]" \
+           "${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}" ; then
+           die "ERROR: CTDB_NATGW_PUBLIC_IP same as a public address"
+       fi
+
+       # do not send out arp requests from loopback addresses
+       set_proc sys/net/ipv4/conf/all/arp_announce 2
+       ;;
+
+updatenatgw|ipreallocated)
+       natgw_check_config
+
+       natgw_ensure_master
+
+       natgw_config_has_changed || natgw_master_has_changed || exit 0
+
+       natgw_clear
+
+       pnn=$(ctdb_get_pnn)
+       if [ "$pnn" = "$natgwmaster" ]; then
+           natgw_set_master
+       else
+           natgw_set_slave "$natgwip"
+       fi
+
+       # flush our route cache
+       set_proc sys/net/ipv4/route/flush 1
+
+       # Only update saved state when NATGW successfully updated
+       natgw_save_state
+       ;;
+
+shutdown|removenatgw)
+       natgw_check_config
+       natgw_clear
+       ;;
+
+monitor)
+       natgw_check_config
+
+       if [ -n "$CTDB_NATGW_PUBLIC_IFACE" ] ; then
+           interface_monitor "$CTDB_NATGW_PUBLIC_IFACE" || exit 1
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/11.routing b/ctdb/events.d/11.routing
new file mode 100755 (executable)
index 0000000..c8d875c
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+# Attempt to add a set of static routes.
+#
+# Do this in "ipreallocated" rather than just "startup" because some
+# of the routes might be missing because the corresponding interface
+# has not previously had any IPs assigned or IPs were previously
+# released and corresponding routes were dropped.
+#
+# Addition of some routes might fail, errors go to /dev/null.
+#
+# Routes to add are defined in $CTDB_BASE/static-routes. Syntax is:
+#
+# IFACE NET/MASK GATEWAY
+#
+# Example:
+#
+# bond1 10.3.3.0/24 10.0.0.1
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+[ -f "${CTDB_BASE}/static-routes" ] || {
+    exit 0
+}
+
+case "$1" in
+ipreallocated)
+        while read iface dest gw; do
+            ip route add "$dest" via "$gw" dev "$iface" >/dev/null 2>&1
+        done <"${CTDB_BASE}/static-routes"
+        ;;
+
+updateip)
+       oiface=$2
+       niface=$3
+       while read iface dest gw; do
+           if [ "$niface" = "$iface" -o "$oiface" = "$iface" ] ; then
+               ip route add "$dest" via "$gw" dev "$iface" >/dev/null 2>&1
+           fi
+       done <"${CTDB_BASE}/static-routes"
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/13.per_ip_routing b/ctdb/events.d/13.per_ip_routing
new file mode 100755 (executable)
index 0000000..4fff73b
--- /dev/null
@@ -0,0 +1,442 @@
+#!/bin/sh
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name=per_ip_routing
+
+# Do nothing if unconfigured 
+[ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
+
+table_id_prefix="ctdb."
+
+[ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
+    die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
+
+[ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
+    die "error: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW] and/or CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] improperly configured"
+
+if [ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -le 253 -a \
+    255 -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
+    die "error: range CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW]..CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] must not include 253-255"
+fi
+
+have_link_local_config ()
+{
+    [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ]
+}
+
+if ! have_link_local_config && [ ! -r "$CTDB_PER_IP_ROUTING_CONF" ] ; then
+    die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
+fi
+
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+######################################################################
+
+ipv4_is_valid_addr()
+{
+    _ip="$1"
+
+    _count=0
+    # Get the shell to break up the address into 1 word per octet 
+    # Intentional word splitting here
+    # shellcheck disable=SC2086
+    for _o in $(export IFS="." ; echo $_ip) ; do
+       # The 2>/dev/null stops output from failures where an "octet"
+       # is not numeric.  The test will still fail.
+       if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
+           return 1
+       fi
+       _count=$((_count + 1))
+    done
+
+    # A valid IPv4 address has 4 octets
+    [ $_count -eq 4 ]
+}
+
+ensure_ipv4_is_valid_addr ()
+{
+    _event="$1"
+    _ip="$2"
+
+    ipv4_is_valid_addr "$_ip" || {
+       echo "$0: $_event not an ipv4 address skipping IP:$_ip"
+       exit 0
+    }
+}
+
+ipv4_host_addr_to_net ()
+{
+    _host="$1"
+    _maskbits="$2"
+
+    # Convert the host address to an unsigned long by splitting out
+    # the octets and doing the math.
+    _host_ul=0
+    # Intentional word splitting here
+    # shellcheck disable=SC2086
+    for _o in $(export IFS="." ; echo $_host) ; do
+       _host_ul=$(( (_host_ul << 8) + _o)) # work around Emacs color bug
+    done
+
+    # Calculate the mask and apply it.
+    _mask_ul=$(( 0xffffffff << (32 - _maskbits) ))
+    _net_ul=$(( _host_ul & _mask_ul ))
+    # Now convert to a network address one byte at a time.
+    _net=""
+    for _o in $(seq 1 4) ; do
+       _net="$((_net_ul & 255))${_net:+.}${_net}"
+       _net_ul=$((_net_ul >> 8))
+    done
+
+    echo "${_net}/${_maskbits}"
+}
+
+######################################################################
+
+ensure_rt_tables ()
+{
+    rt_tables="$CTDB_SYS_ETCDIR/iproute2/rt_tables"
+    rt_tables_lock="${service_state_dir}/rt_tables_lock"
+
+    # This file should always exist.  Even if this didn't exist on the
+    # system, adding a route will have created it.  What if we startup
+    # and immediately shutdown?  Let's be sure.
+    if [ ! -f "$rt_tables" ] ; then
+       mkdir -p "${rt_tables%/*}" # dirname
+       touch "$rt_tables"
+    fi
+}
+
+# Setup a table id to use for the given IP.  We don't need to know it,
+# it just needs to exist in /etc/iproute2/rt_tables.  Fail if no free
+# table id could be found in the configured range.
+ensure_table_id_for_ip ()
+{
+    _ip=$1
+
+    ensure_rt_tables
+
+    # Maintain a table id for each IP address we've ever seen in
+    # rt_tables.  We use a "ctdb." prefix on the label.
+    _label="${table_id_prefix}${_ip}"
+
+    # This finds either the table id corresponding to the label or a
+    # new unused one (that is greater than all the used ones in the
+    # range).
+    (
+       # Note that die() just gets us out of the subshell...
+       flock --timeout 30 9 || \
+           die "ensure_table_id_for_ip: failed to lock file $rt_tables"
+
+       _new="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
+       while read _t _l ; do
+           # Skip comments
+           case "$_t" in
+               \#*) continue ;;
+           esac
+           # Found existing: done
+           if [ "$_l" = "$_label" ] ; then
+               return 0
+           fi
+           # Potentially update the new table id to be used.  The
+           # redirect stops error spam for a non-numeric value.
+           if [ "$_new" -le "$_t" -a \
+               "$_t" -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null ; then
+               _new=$((_t + 1))
+           fi
+       done <"$rt_tables"
+
+       # If the new table id is legal then add it to the file and
+       # print it.
+       if [ "$_new" -le "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] ; then
+           printf "%d\t%s\n" "$_new" "$_label" >>"$rt_tables"
+           return 0
+       else
+           return 1
+       fi
+    ) 9>"$rt_tables_lock"
+}
+
+# Clean up all the table ids that we might own.
+clean_up_table_ids ()
+{
+    ensure_rt_tables
+
+    (
+       # Note that die() just gets us out of the subshell...
+       flock --timeout 30 9 || \
+           die "clean_up_table_ids: failed to lock file $rt_tables"
+
+       # Delete any items from the file that have a table id in our
+       # range or a label matching our label.  Preserve comments.
+       _tmp="${rt_tables}.$$.ctdb"
+       awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
+           -v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
+           -v pre="$table_id_prefix" \
+           '/^#/ ||
+            !(min <= $1 && $1 <= max) &&
+            !(index($2, pre) == 1) {
+               print $0 }' "$rt_tables" >"$_tmp"
+
+       mv "$_tmp" "$rt_tables"
+    ) 9>"$rt_tables_lock"
+}
+
+######################################################################
+
+# This prints the config for an IP, which is either relevant entries
+# from the config file or, if set to the magic link local value, some
+# link local routing config for the IP.
+get_config_for_ip ()
+{
+    _ip="$1"
+
+    if have_link_local_config ; then
+       # When parsing public_addresses also split on '/'.  This means
+       # that we get the maskbits as item #2 without further parsing.
+       while IFS="/$IFS" read _i _maskbits _x ; do
+           if [ "$_ip" = "$_i" ] ; then
+               printf "%s" "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
+           fi
+       done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
+    else
+       while read _i _rest ; do
+           if [ "$_ip" = "$_i" ] ; then
+               printf "%s\t%s\n" "$_ip" "$_rest"
+           fi
+       done <"$CTDB_PER_IP_ROUTING_CONF"
+    fi
+}
+
+ip_has_configuration ()
+{
+    _ip="$1"
+
+    _conf=$(get_config_for_ip "$_ip")
+    [ -n "$_conf" ]
+}
+
+add_routing_for_ip ()
+{
+    _iface="$1"
+    _ip="$2"
+
+    # Do nothing if no config for this IP.
+    ip_has_configuration "$_ip" || return 0
+
+    ensure_table_id_for_ip "$_ip" || \
+       die "add_routing_for_ip: out of table ids in range $CTDB_PER_IP_ROUTING_TABLE_ID_LOW - $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
+
+    _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+    _table_id="${table_id_prefix}${_ip}"
+
+    del_routing_for_ip "$_ip" >/dev/null 2>&1
+
+    ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
+       die "add_routing_for_ip: failed to add rule for $_ip"
+
+    # Add routes to table for any lines matching the IP.
+    get_config_for_ip "$_ip" |
+    while read _i _dest _gw ; do
+       _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
+       # Intentionally unquoted multi-word value here
+       # shellcheck disable=SC2086
+       ip route add $_r || \
+           die "add_routing_for_ip: failed to add route: $_r"
+    done
+}
+
+del_routing_for_ip ()
+{
+    _ip="$1"
+
+    _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+    _table_id="${table_id_prefix}${_ip}"
+
+    # Do this unconditionally since we own any matching table ids.
+    # However, print a meaningful message if something goes wrong.
+    _cmd="ip rule del from $_ip pref $_pref table $_table_id"
+    _out=$($_cmd 2>&1) || \
+       cat <<EOF
+WARNING: Failed to delete policy routing rule
+  Command "$_cmd" failed:
+  $_out
+EOF
+    # This should never usually fail, so don't redirect output.
+    # However, it can fail when deleting a rogue IP, since there will
+    # be no routes for that IP.  In this case it should only fail when
+    # the rule deletion above has already failed because the table id
+    # is invalid.  Therefore, go to a little bit of trouble to indent
+    # the failure message so that it is associated with the above
+    # warning message and doesn't look too nasty.
+    ip route flush table "$_table_id" 2>&1 | sed -e 's@^.@  &@'
+}
+
+######################################################################
+
+flush_rules_and_routes ()
+{
+       ip rule show |
+       while read _p _x _i _x _t ; do
+           # Remove trailing colon after priority/preference.
+           _p="${_p%:}"
+           # Only remove rules that match our priority/preference.
+           [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+
+           echo "Removing ip rule for public address $_i for routing table $_t"
+           ip rule del from "$_i" table "$_t" pref "$_p"
+           ip route flush table "$_t" 2>/dev/null
+       done
+}
+
+# Add any missing routes.  Some might have gone missing if, for
+# example, all IPs on the network were removed (possibly if the
+# primary was removed).  If $1 is "force" then (re-)add all the
+# routes.
+add_missing_routes ()
+{
+    $CTDB ip -v -X | {
+       read _x # skip header line
+
+       # Read the rest of the lines.  We're only interested in the
+       # "IP" and "ActiveInterface" columns.  The latter is only set
+       # for addresses local to this node, making it easy to skip
+       # non-local addresses.  For each IP local address we check if
+       # the relevant routing table is populated and populate it if
+       # not.
+       while IFS="|" read _x _ip _x _iface _x ; do
+           [ -n "$_iface" ] || continue
+
+           _table_id="${table_id_prefix}${_ip}"
+           if [ -z "$(ip route show table "$_table_id" 2>/dev/null)" -o \
+               "$1" = "force" ]  ; then
+               add_routing_for_ip "$_iface" "$_ip"
+           fi
+       done
+    } || exit $?
+}
+
+# Remove rules/routes for addresses that we're not hosting.  If a
+# releaseip event failed in an earlier script then we might not have
+# had a chance to remove the corresponding rules/routes.
+remove_bogus_routes ()
+{
+    # Get a IPs current hosted by this node, each anchored with '@'.
+    _ips=$($CTDB ip -v -X | awk -F'|' 'NR > 1 && $4 != "" {printf "@%s@\n", $2}')
+
+    # x is intentionally ignored
+    # shellcheck disable=SC2034
+    ip rule show |
+    while read _p _x _i _x _t ; do
+       # Remove trailing colon after priority/preference.
+       _p="${_p%:}"
+       # Only remove rules that match our priority/preference.
+       [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+       # Only remove rules for which we don't have an IP.  This could
+       # be done with grep, but let's do it with shell prefix removal
+       # to avoid unnecessary processes.  This falls through if
+       # "@${_i}@" isn't present in $_ips.
+       [ "$_ips" = "${_ips#*@${_i}@}" ] || continue
+
+       echo "Removing ip rule/routes for unhosted public address $_i"
+       del_routing_for_ip "$_i"
+    done
+}
+
+######################################################################
+
+service_reconfigure ()
+{
+    add_missing_routes "force"
+    remove_bogus_routes
+
+    # flush our route cache
+    set_proc sys/net/ipv4/route/flush 1
+}
+
+######################################################################
+
+ctdb_check_args "$@"
+
+case "$1" in
+startup)
+       flush_rules_and_routes
+
+       # make sure that we only respond to ARP messages from the NIC
+       # where a particular ip address is associated.
+       get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
+           set_proc sys/net/ipv4/conf/all/arp_filter 1
+       }
+       ;;
+
+shutdown)
+       flush_rules_and_routes
+       clean_up_table_ids
+       ;;
+
+takeip)
+       iface=$2
+       ip=$3
+       # maskbits included here so argument order is obvious
+       # shellcheck disable=SC2034
+       maskbits=$4
+
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       add_routing_for_ip "$iface" "$ip"
+
+       # flush our route cache
+       set_proc sys/net/ipv4/route/flush 1
+
+       $CTDB gratarp "$ip" "$iface"
+       ;;
+
+updateip)
+       # oiface, maskbits included here so argument order is obvious
+       # shellcheck disable=SC2034
+       oiface=$2
+       niface=$3
+       ip=$4
+       # shellcheck disable=SC2034
+       maskbits=$5
+
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       add_routing_for_ip "$niface" "$ip"
+
+       # flush our route cache
+       set_proc sys/net/ipv4/route/flush 1
+
+       $CTDB gratarp "$ip" "$niface"
+       tickle_tcp_connections "$ip"
+       ;;
+
+releaseip)
+       iface=$2
+       ip=$3
+       # maskbits included here so argument order is obvious
+       # shellcheck disable=SC2034
+       maskbits=$4
+
+       ensure_ipv4_is_valid_addr "$1" "$ip"
+       del_routing_for_ip "$ip"
+       ;;
+
+ipreallocated)
+       add_missing_routes
+       remove_bogus_routes
+       ;;
+
+reconfigure)
+       ctdb_service_reconfigure
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/20.multipathd b/ctdb/events.d/20.multipathd
new file mode 100755 (executable)
index 0000000..3dd58da
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/sh
+# ctdb event script for monitoring the multipath daemon
+#
+# Configure monitporing of multipath devices by listing the device serials
+# in /etc/ctdb/multipathd :
+#   CTDB_MONITOR_MPDEVICES="device1 device2 ..."
+#
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="multipathd"
+
+loadconfig
+
+[ -n "$CTDB_MONITOR_MPDEVICES" ] || exit 0
+
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+multipath_fail="${service_state_dir}/fail"
+
+multipathd_check_background()
+{
+    for _device in $CTDB_MONITOR_MPDEVICES; do
+       # Check multipath knows about the device
+       _out=$(multipath -ll "$_device")
+       if [ -z "$_out" ] ; then
+           echo "ERROR: device \"${_device}\" not known to multipathd" \
+                >"$multipath_fail"
+           exit 1
+       fi
+
+       # Check for at least 1 active path
+       if ! echo "$_out" | grep 'prio=.* status=active' >/dev/null 2>&1 ; then
+           echo "ERROR: multipath device \"${_device}\" has no active paths" \
+                >"$multipath_fail"
+           exit 1
+       fi
+    done
+    exit 0
+}
+
+multipathd_check()
+{
+    # Run the actual check in the background since the call to
+    # multipath may block
+    multipathd_check_background </dev/null >/dev/null 2>&1 &
+    _pid="$!"
+    _timeleft=10
+
+    while [ $_timeleft -gt 0 ]; do
+       _timeleft=$((_timeleft - 1))
+
+       # see if the process still exists
+       kill -0 $_pid >/dev/null 2>&1 || {
+           if wait $_pid ; then
+               return 0
+           else
+               cat "$multipath_fail"
+               rm -f "$multipath_fail"
+               return 1
+           fi
+       }
+       sleep 1
+    done
+
+    echo "ERROR: callout to multipath checks hung"
+    # If hung then this probably won't work, but worth trying...
+    kill -9 $_pid >/dev/null 2>&1
+    return 1
+}
+
+case "$1" in
+monitor)
+       multipathd_check || die "multipath monitoring failed"
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/31.clamd b/ctdb/events.d/31.clamd
new file mode 100755 (executable)
index 0000000..1b76365
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# event script to manage clamd in a cluster environment
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+detect_init_style
+
+case $CTDB_INIT_STYLE in
+redhat)
+       service_name="clamd"
+       # service_config is used by loadconfig()
+       # shellcheck disable=SC2034
+       service_config="clamd"
+       ;;
+*)
+       service_name="clamav"
+       # service_config is used by loadconfig()
+       # shellcheck disable=SC2034
+       service_config="clamav"
+       ;;
+esac
+
+loadconfig
+
+is_ctdb_managed_service || exit 0
+
+case "$1" in
+startup)
+       service "$service_name" stop > /dev/null 2>&1
+       service "$service_name" start || exit $?
+       ;;
+
+shutdown)
+       service "$service_name"_stop
+       ;;
+
+monitor)
+        ctdb_check_unix_socket "$CTDB_CLAMD_SOCKET" || exit $?
+        ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/40.vsftpd b/ctdb/events.d/40.vsftpd
new file mode 100755 (executable)
index 0000000..f3c4848
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+# event strict to manage vsftpd in a cluster environment
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+service_name="vsftpd"
+
+service_reconfigure ()
+{
+    service $service_name restart
+}
+
+loadconfig
+
+is_ctdb_managed_service || exit 0
+
+case "$1" in
+startup)
+       service "$service_name" stop > /dev/null 2>&1
+       service "$service_name" start
+       ctdb_counter_init
+       ;;
+
+shutdown)
+       service "$service_name" stop
+       ;;
+
+takeip|releaseip)
+       ctdb_service_set_reconfigure
+       ;;
+
+ipreallocated)
+       if ctdb_service_needs_reconfigure ; then
+               ctdb_service_reconfigure
+       fi
+       ;;
+
+monitor)
+       if ctdb_check_tcp_ports 21 ; then
+               ctdb_counter_init
+       else
+               ctdb_counter_incr
+               num_fails=$(ctdb_counter_get)
+               if [ "$num_fails" -ge 2 ] ; then
+                       die "ERROR: ${num_fails} consecutive failures for vsftpd, marking node unhealthy"
+               elif [ "$num_fails" -eq 1 ] ; then
+                       echo "WARNING: vsftpd not listening but less than 2 consecutive failures, not unhealthy yet"
+               fi
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/41.httpd b/ctdb/events.d/41.httpd
new file mode 100755 (executable)
index 0000000..b9aa7e1
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+# event script to manage httpd in a cluster environment
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+detect_init_style
+
+case $CTDB_INIT_STYLE in
+redhat)
+       service_name="httpd"
+       # service_config is used by loadconfig()
+       # shellcheck disable=SC2034
+       service_config="http"
+       ;;
+suse|debian|*)
+       service_name="apache2"
+       # service_config is used by loadconfig()
+       # shellcheck disable=SC2034
+       service_config="apache2"
+       ;;
+esac
+
+# RHEL5 sometimes use a SIGKILL to terminate httpd, which then leaks
+# semaphores.  This is a hack to clean them up.
+cleanup_httpd_semaphore_leak() {
+    killall -q -0 "$service_name" ||
+    for i in $(ipcs -s | awk '$3 == "apache" { print $2 }') ; do
+       ipcrm -s "$i"
+    done
+}
+
+##########
+
+service_start ()
+{
+    cleanup_httpd_semaphore_leak
+    service $service_name start
+}
+service_stop ()
+{
+    service $service_name stop
+    killall -q -9 $service_name || true
+}
+
+loadconfig
+
+is_ctdb_managed_service || exit 0
+
+case "$1" in
+startup)
+       service_start
+       ctdb_counter_int
+       ;;
+
+shutdown)
+       service_stop
+       ;;
+
+monitor)
+       if ctdb_check_tcp_ports 80 >/dev/null 2>/dev/null ; then
+               ctdb_counter_init
+       else
+               ctdb_counter_incr
+               num_fails=$(ctdb_counter_get)
+               if [ "$num_fails" -eq 2 ] ; then
+                       echo "HTTPD is not running. Trying to restart HTTPD."
+                       service_stop
+                       service_start
+                       exit 0
+               elif [ "$num_fails" -ge 5 ] ; then
+                       echo "HTTPD is not running. Trying to restart HTTPD."
+                       service_stop
+                       service_start
+                       exit 1
+               fi
+       fi
+       ;;
+esac
+
+exit 0
+
diff --git a/ctdb/events.d/49.winbind b/ctdb/events.d/49.winbind
new file mode 100755 (executable)
index 0000000..5e93737
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+# ctdb event script for winbind
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+CTDB_SERVICE_WINBIND=${CTDB_SERVICE_WINBIND:-winbind}
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="winbind"
+
+loadconfig
+
+service_start ()
+{
+    service "$CTDB_SERVICE_WINBIND" stop >/dev/null 2>&1
+    killall -0 -q winbindd && {
+       sleep 1
+        # make absolutely sure winbindd is dead
+       killall -q -9 winbindd
+    }
+
+    service "$CTDB_SERVICE_WINBIND" start || \
+       die "Failed to start winbind"
+}
+
+service_stop ()
+{
+    service "$CTDB_SERVICE_WINBIND" stop
+}
+
+###########################
+
+is_ctdb_managed_service || exit 0
+
+###########################
+
+case "$1" in
+startup)
+       service_start
+       ;;
+
+shutdown)
+       service_stop
+       ;;
+
+monitor)
+       if ! out=$(wbinfo -p 2>&1) ; then
+               echo "ERROR: wbinfo -p returned error"
+               echo "$out"
+               exit 1
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/50.samba b/ctdb/events.d/50.samba
new file mode 100755 (executable)
index 0000000..4c32e4e
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+# ctdb event script for Samba
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+detect_init_style
+
+case $CTDB_INIT_STYLE in
+       suse)
+               CTDB_SERVICE_SMB=${CTDB_SERVICE_SMB:-smb}
+               CTDB_SERVICE_NMB=${CTDB_SERVICE_NMB:-nmb}
+               ;;
+       debian)
+               CTDB_SERVICE_SMB=${CTDB_SERVICE_SMB:-smbd}
+               CTDB_SERVICE_NMB=${CTDB_SERVICE_NMB:-nmbd}
+               ;;
+       *)
+               # Use redhat style as default:
+               CTDB_SERVICE_SMB=${CTDB_SERVICE_SMB:-smb}
+               CTDB_SERVICE_NMB=${CTDB_SERVICE_NMB:-""}
+               ;;
+esac
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="samba"
+
+loadconfig
+
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+service_start ()
+{
+    # make sure samba is not already started
+    service "$CTDB_SERVICE_SMB" stop > /dev/null 2>&1
+    if [ -n "$CTDB_SERVICE_NMB" ] ; then
+       service "$CTDB_SERVICE_NMB" stop > /dev/null 2>&1
+    fi
+    killall -0 -q smbd && {
+       sleep 1
+       # make absolutely sure samba is dead
+       killall -q -9 smbd
+    }
+    killall -0 -q nmbd && {
+       sleep 1
+       # make absolutely sure samba is dead
+       killall -q -9 nmbd
+    }
+
+    # start Samba service. Start it reniced, as under very heavy load
+    # the number of smbd processes will mean that it leaves few cycles
+    # for anything else
+    net serverid wipe
+
+    if [ -n "$CTDB_SERVICE_NMB" ] ; then
+       nice_service "$CTDB_SERVICE_NMB" start || die "Failed to start nmbd"
+    fi
+
+    nice_service "$CTDB_SERVICE_SMB" start || die "Failed to start samba"
+}
+
+service_stop ()
+{
+    service "$CTDB_SERVICE_SMB" stop
+    program_stack_traces "smbd" 5
+    if [ -n "$CTDB_SERVICE_NMB" ] ; then
+       service "$CTDB_SERVICE_NMB" stop
+    fi
+}
+
+######################################################################
+# Show the testparm output using a cached smb.conf to avoid delays due
+# to registry access.
+
+smbconf_cache="$service_state_dir/smb.conf.cache"
+
+testparm_foreground_update ()
+{
+    _timeout="$1"
+
+    # No need to remove these temporary files, since there are only 2
+    # of them.
+    _out="${smbconf_cache}.out"
+    _err="${smbconf_cache}.err"
+
+    timeout "$_timeout" testparm -v -s >"$_out" 2>"$_err"
+    case $? in
+       0) : ;;
+       124)
+           if [ -f "$smbconf_cache" ] ; then
+               echo "WARNING: smb.conf cache update timed out - using old cache file"
+               return 1
+           else
+               echo "ERROR: smb.conf cache create failed - testparm command timed out"
+               exit 1
+           fi
+           ;;
+       *)
+           if [ -f "$smbconf_cache" ] ; then
+               echo "WARNING: smb.conf cache update failed - using old cache file"
+               cat "$_err"
+               return 1
+           else
+               echo "ERROR: smb.conf cache create failed - testparm failed with:"
+               cat "$_err"
+               exit 1
+           fi
+    esac
+
+    # Only using $$ here to avoid a collision.  This is written into
+    # CTDB's own state directory so there is no real need for a secure
+    # temporary file.
+    _tmpfile="${smbconf_cache}.$$"
+    # Patterns to exclude...
+    _pat='^[[:space:]]+(registry[[:space:]]+shares|include|copy|winbind[[:space:]]+separator)[[:space:]]+='
+    grep -Ev "$_pat" <"$_out" >"$_tmpfile"
+    mv "$_tmpfile" "$smbconf_cache" # atomic
+
+    return 0
+}
+
+testparm_background_update ()
+{
+    _timeout="$1"
+
+    testparm_foreground_update "$_timeout" >/dev/null 2>&1 </dev/null &
+}
+
+testparm_cat ()
+{
+    testparm -s "$smbconf_cache" "$@" 2>/dev/null
+}
+
+list_samba_shares ()
+{
+    testparm_cat |
+    sed -n -e 's@^[[:space:]]*path[[:space:]]*=[[:space:]]@@p' |
+    sed -e 's/"//g'
+}
+
+list_samba_ports ()
+{
+    testparm_cat --parameter-name="smb ports" |
+    sed -e 's@,@ @g'
+}
+
+###########################
+
+is_ctdb_managed_service || exit 0
+
+###########################
+
+case "$1" in
+startup)
+       service_start
+       ;;
+
+shutdown)
+       service_stop
+       ;;
+
+monitor)
+       testparm_foreground_update 10
+       ret=$?
+
+       smb_ports="$CTDB_SAMBA_CHECK_PORTS"
+       if [ -z "$smb_ports" ] ; then
+           smb_ports=$(list_samba_ports)
+           [ -n "$smb_ports" ] || die "Failed to set smb ports"
+       fi
+       # Intentionally unquoted multi-word value here
+       # shellcheck disable=SC2086
+       ctdb_check_tcp_ports $smb_ports || exit $?
+
+       if [ "$CTDB_SAMBA_SKIP_SHARE_CHECK" != "yes" ] ; then
+           list_samba_shares | ctdb_check_directories || exit $?
+       fi
+
+       if [ $ret -ne 0 ] ; then
+           testparm_background_update 10
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/60.nfs b/ctdb/events.d/60.nfs
new file mode 100755 (executable)
index 0000000..9b64d6e
--- /dev/null
@@ -0,0 +1,300 @@
+#!/bin/sh
+# script to manage nfs in a clustered environment
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="nfs"
+
+loadconfig
+service_state_dir=$(ctdb_setup_service_state_dir) || exit $?
+
+######################################################################
+
+service_reconfigure ()
+{
+    # Restart lock manager, notify clients
+    if [ -x "${CTDB_BASE}/statd-callout" ] ; then
+       "${CTDB_BASE}/statd-callout" notify &
+    fi >/dev/null 2>&1
+}
+
+######################################################################
+
+######################################################
+# Check the health of NFS services
+#
+# Use .check files in $CTDB_NFS_CHECKS_DIR.
+# Default is "${CTDB_BASE}/nfs-checks.d/"
+######################################################
+nfs_check_services ()
+{
+    _dir="${CTDB_NFS_CHECKS_DIR:-${CTDB_BASE}/nfs-checks.d}"
+
+    # Files must end with .check - avoids editor backups, RPM fu, ...
+    for _f in "$_dir"/[0-9][0-9].*.check ; do
+       [ -r "$_f" ] || continue
+
+       _t="${_f%.check}"
+       _progname="${_t##*/[0-9][0-9].}"
+
+       nfs_check_service "$_progname" <"$_f"
+    done
+}
+
+######################################################
+# Check the health of an NFS service
+#
+# $1 - progname, passed to rpcinfo (looked up in /etc/rpc)
+#
+# Reads variables from stdin
+#
+# Variables are:
+#
+# * family             - "tcp" or "udp" or space separated list
+#                        default: tcp, not used with "service_check_cmd"
+# * version            - optional, RPC service version number
+#                        default is to omit to check for any version,
+#                        not used with "service_check_cmd"
+# * unhealthy_after    - number of check fails before unhealthy
+#                        default: 1
+# * restart_every      - number of check fails before restart
+#                        default: 0, meaning no restart
+# * service_stop_cmd   - command to stop service
+#                        default: no default, must be provided if
+#                                 restart_every > 0
+# * service_start_cmd  - command to start service
+#                        default: no default, must be provided if
+#                                 restart_every > 0
+# * service_check_cmd  - command to check health of service
+#                        default is to check RPC service using rpcinfo
+# * service_debug_cmd  - command to debug a service after trying to stop it;
+#                        for example, it can be useful to print stack
+#                        traces of threads that have not exited, since
+#                        they may be stuck doing I/O;
+#                        no default, see also function program_stack_traces()
+#
+# Quoting in values is not preserved
+#
+######################################################
+nfs_check_service ()
+{
+    _progname="$1"
+
+    # This sub-shell is created to intentionally limit the scope of
+    # variable values read from the .check files.
+    # shellcheck disable=SC2030
+    (
+       # Subshell to restrict scope variables...
+
+       # Defaults
+       family="tcp"
+       version=""
+       unhealthy_after=1
+       restart_every=0
+       service_stop_cmd=""
+       service_start_cmd=""
+       service_check_cmd=""
+       service_debug_cmd=""
+
+       # Eval line-by-line.  Expands variable references in values.
+       # Also allows variable name checking, which seems useful.
+       while read _line ; do
+           case "$_line" in
+               \#*|"") : ;; # Ignore comments, blank lines
+
+               family=*|version=*|\
+               unhealthy_after=*|restart_every=*|\
+               service_stop_cmd=*|service_start_cmd=*|\
+               service_check_cmd=*|service_debug_cmd=*)
+
+                   eval "$_line"
+                   ;;
+               *)
+                   echo "ERROR: Unknown variable for ${_progname}: ${_line}"
+                   exit 1
+           esac
+       done
+
+       _service_name="nfs_${_progname}"
+
+       _ok=false
+       if [ -n "$service_check_cmd" ] ; then
+           # Using eval means variables can contain semicolon separated commands
+           if eval "$service_check_cmd" ; then
+               _ok=true
+           else
+               _err="monitoring service \"${_progname}\" failed"
+           fi
+       else
+           if nfs_check_rpcinfo \
+                  "$_progname" "$version" "$family" >/dev/null ; then
+               _ok=true
+           else
+               _err="$ctdb_check_rpc_out"
+           fi
+       fi
+
+       if $_ok ; then
+           if [ $unhealthy_after -ne 1 -o $restart_every -ne 0 ] ; then
+               ctdb_counter_init "$_service_name"
+           fi
+           exit 0
+       fi
+
+       ctdb_counter_incr "$_service_name"
+       _failcount=$(ctdb_counter_get "$_service_name")
+
+       _unhealthy=false
+       if [ "$unhealthy_after" -gt 0 ] ; then
+           if [ "$_failcount" -ge "$unhealthy_after" ] ; then
+               _unhealthy=true
+               echo "ERROR: $_err"
+           fi
+       fi
+
+       if [ "$restart_every" -gt 0 ] ; then
+           if [ $((_failcount % restart_every)) -eq 0 ] ; then
+               if ! $_unhealthy ; then
+                   echo "WARNING: $_err"
+               fi
+               nfs_restart_service
+           fi
+       fi
+
+       if $_unhealthy ; then
+           exit 1
+       fi
+
+       return 0
+    ) || exit 1
+}
+
+# Uses: service_stop_cmd, service_start_cmd, service_debug_cmd
+# This function is called within the sub-shell that shellcheck thinks
+# loses the above variable values.
+# shellcheck disable=SC2031
+nfs_restart_service ()
+{
+    if [ -z "$service_stop_cmd" -o -z "$service_start_cmd" ] ; then
+       die "ERROR: Can not restart service \"${_progname}\" without corresponding service_start_cmd/service_stop_cmd settings"
+    fi
+
+    echo "Trying to restart service \"${_progname}\"..."
+    # Using eval means variables can contain semicolon separated commands
+    eval "$service_stop_cmd"
+    if [ -n "$service_debug_cmd" ] ; then
+       eval "$service_debug_cmd"
+    fi
+    background_with_logging eval "$service_start_cmd"
+}
+
+######################################################
+# Check an RPC service with rpcinfo
+######################################################
+ctdb_check_rpc ()
+{
+    _progname="$1"        # passed to rpcinfo (looked up in /etc/rpc)
+    _version="$2"         # optional, not passed if empty/unset
+    _family="${3:-tcp}"   # optional, default is "tcp"
+
+    case "$_family" in
+       tcp6|udp6)
+           _localhost="${CTDB_RPCINFO_LOCALHOST6:-::1}"
+           ;;
+       *)
+           _localhost="${CTDB_RPCINFO_LOCALHOST:-127.0.0.1}"
+    esac
+
+    # $_version is not quoted because it is optional
+    # shellcheck disable=SC2086
+    if ! ctdb_check_rpc_out=$(rpcinfo -T "$_family" "$_localhost" \
+                                     "$_progname" $_version 2>&1) ; then
+       ctdb_check_rpc_out="$_progname failed RPC check:
+$ctdb_check_rpc_out"
+       echo "$ctdb_check_rpc_out"
+       return 1
+    fi
+}
+
+nfs_check_rpcinfo ()
+{
+    _progname="$1"        # passed to rpcinfo (looked up in /etc/rpc)
+    _versions="$2"        # optional, space separated, not passed if empty/unset
+    _families="${3:-tcp}" # optional, space separated, default is "tcp"
+
+    for _family in $_families ; do
+       if [ -n "$_versions" ] ; then
+           for _version in $_versions ; do
+               ctdb_check_rpc "$_progname" "$_version" "$_family" || return $?
+           done
+       else
+           ctdb_check_rpc "$_progname" "" "$_family" || return $?
+       fi
+    done
+}
+
+##################################################################
+# use statd-callout to update NFS lock info
+##################################################################
+nfs_update_lock_info ()
+{
+    if [ -x "$CTDB_BASE/statd-callout" ] ; then
+       "$CTDB_BASE/statd-callout" update
+    fi
+}
+
+######################################################################
+
+nfs_callout_init "$service_state_dir"
+
+is_ctdb_managed_service || exit 0
+
+case "$1" in
+startup)
+       nfs_callout "$@" || exit $?
+       ;;
+
+shutdown)
+       nfs_callout "$@" || exit $?
+       ;;
+
+takeip)
+       nfs_callout "$@" || exit $?
+       ctdb_service_set_reconfigure
+       ;;
+
+releaseip)
+       nfs_callout "$@" || exit $?
+       ctdb_service_set_reconfigure
+       ;;
+
+ipreallocated)
+       if ctdb_service_needs_reconfigure ; then
+               ctdb_service_reconfigure
+       fi
+       ;;
+
+monitor)
+       nfs_callout "monitor-pre" || exit $?
+
+       # Check that directories for shares actually exist
+       if [ "$CTDB_NFS_SKIP_SHARE_CHECK" != "yes" ] ; then
+           nfs_callout "monitor-list-shares" | ctdb_check_directories || \
+               exit $?
+       fi
+
+       update_tickles 2049
+       nfs_update_lock_info
+
+       nfs_check_services
+
+       nfs_callout "monitor-post" || exit $?
+               ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/70.iscsi b/ctdb/events.d/70.iscsi
new file mode 100755 (executable)
index 0000000..8851c59
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+# CTDB event script for TGTD based iSCSI
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# service_name is used by various functions
+# shellcheck disable=SC2034
+service_name="iscsi"
+
+loadconfig
+
+is_ctdb_managed_service || exit 0
+
+[ -z "$CTDB_START_ISCSI_SCRIPTS" ] && {
+       echo "No iscsi start script directory found"
+       exit 0
+}
+
+case "$1" in
+ipreallocated)
+       all_ips=$($CTDB -X ip | tail -n +2)
+
+       # Block the iSCSI port.  Only block for the address families
+       # we have configured.  This copes with, for example, ip6tables
+       # being unavailable on an IPv4-only system.
+       have_ipv4=false
+       have_ipv6=false
+       # x is intentionally ignored
+       # shellcheck disable=SC2034
+       while IFS='|' read x ip pnn x ; do
+           case "$ip" in
+               *:*) have_ipv6=true ;;
+               *)   have_ipv4=true ;;
+               esac
+       done <<EOF
+$all_ips
+EOF
+       if $have_ipv4 ; then
+           iptables -I INPUT 1 -p tcp --dport 3260 -j DROP
+       fi
+       if $have_ipv6 ; then
+           ip6tables -I INPUT 1 -p tcp --dport 3260 -j DROP
+       fi
+
+       # Stop iSCSI daemon
+       killall -9 tgtd >/dev/null 2>/dev/null
+
+       pnn=$(ctdb_get_pnn)
+       [ -n "$pnn" ] || die "Failed to get node pnn"
+
+       # Start iSCSI daemon
+       tgtd >/dev/null 2>&1
+
+       # Run a script for each currently hosted public IP address
+       ips=$(echo "$all_ips" | awk -F'|' -v pnn="$pnn" '$3 == pnn {print $2}')
+       for ip in $ips ; do
+           script="${CTDB_START_ISCSI_SCRIPTS}/${ip}.sh"
+           if [ -x "$script" ] ; then
+               echo "Starting iSCSI service for public address ${ip}"
+               "$script"
+           fi
+       done
+
+       # Unblock iSCSI port.  These can be unconditional (compared to
+       # blocking above), since errors are redirected.
+       while iptables -D INPUT -p tcp --dport 3260 -j DROP >/dev/null 2>&1 ; do
+           :
+       done
+       while ip6tables -D INPUT -p tcp --dport 3260 -j DROP >/dev/null 2>&1 ; do
+           :
+       done
+
+       ;;
+
+shutdown)
+       # Shutdown iSCSI daemon when ctdb goes down
+       killall -9 tgtd >/dev/null 2>&1
+       ;;
+
+monitor)
+       ctdb_check_tcp_ports 3260 || exit $?
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/91.lvs b/ctdb/events.d/91.lvs
new file mode 100755 (executable)
index 0000000..9725ee8
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/sh
+# script to manage the lvs ip multiplexer for a single public address cluster
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig ctdb
+
+[ -n "$CTDB_LVS_NODES" ] || exit 0
+export CTDB_LVS_NODES
+
+# type is commonly supported and more portable than which(1)
+# shellcheck disable=SC2039
+if ! type ipvsadm >/dev/null 2>&1 ; then
+       echo "LVS configured but ipvsadm not found"
+       exit 0
+fi
+
+
+lvs_slave_only ()
+{
+       _ip_address=$(ctdb_get_ip_address)
+       awk -v my_ip="$_ip_address" \
+           '$1 == my_ip { if ($2 ~ "slave-only") { exit 0 } else { exit 1 } }' \
+           "$CTDB_LVS_NODES"
+}
+
+lvs_check_config ()
+{
+       [ -r "$CTDB_LVS_NODES" ] || \
+               die "error: CTDB_LVS_NODES=${CTDB_LVS_NODES} unreadable"
+       [ -n "$CTDB_LVS_PUBLIC_IP" ] || \
+               die "Invalid configuration: CTDB_LVS_PUBLIC_IP not set"
+       if ! lvs_slave_only ; then
+               [ -n "$CTDB_LVS_PUBLIC_IFACE" ] || \
+                       die "Invalid configuration: CTDB_LVS_PUBLIC_IFACE not set"
+       fi
+
+       if [ "$CTDB_PARTIALLY_ONLINE_INTERFACES" = "yes" ] ; then
+               die "Invalid configuration: CTDB_PARTIALLY_ONLINE_INTERFACES=yes incompatible with LVS"
+       fi
+}
+
+case "$1" in
+setup)
+       lvs_check_config
+       ;;
+startup)
+       lvs_check_config
+
+       ipvsadm -D -t "$CTDB_LVS_PUBLIC_IP" >/dev/null 2>&1
+       ipvsadm -D -u "$CTDB_LVS_PUBLIC_IP" >/dev/null 2>&1
+
+       ip addr add "${CTDB_LVS_PUBLIC_IP}/32" dev lo scope host
+
+       # do not respond to ARPs that are for ip addresses with scope 'host'
+       set_proc_maybe sys/net/ipv4/conf/all/arp_ignore 3
+       # do not send out arp requests from loopback addresses
+       set_proc_maybe sys/net/ipv4/conf/all/arp_announce 2
+       ;;
+
+shutdown)
+       lvs_check_config
+
+       ipvsadm -D -t "$CTDB_LVS_PUBLIC_IP"
+       ipvsadm -D -u "$CTDB_LVS_PUBLIC_IP"
+
+       ip addr del "${CTDB_LVS_PUBLIC_IP}/32" dev lo >/dev/null 2>&1
+
+       flush_route_cache
+       ;;
+
+ipreallocated)
+       lvs_check_config
+
+       # Kill connections
+       ipvsadm -D -t "$CTDB_LVS_PUBLIC_IP" >/dev/null 2>&1
+       ipvsadm -D -u "$CTDB_LVS_PUBLIC_IP" >/dev/null 2>&1
+       kill_tcp_connections_local_only \
+               "$CTDB_LVS_PUBLIC_IFACE" "$CTDB_LVS_PUBLIC_IP"
+
+       pnn=$(ctdb_get_pnn)
+       lvsmaster=$("${CTDB_HELPER_BINDIR}/ctdb_lvs" master)
+       if [ "$pnn" != "$lvsmaster" ] ; then
+           # This node is not the LVS master so change the IP address
+           # to have scope "host" so this node won't respond to ARPs
+           ip addr del "${CTDB_LVS_PUBLIC_IP}/32" dev lo >/dev/null 2>&1
+           ip addr add "${CTDB_LVS_PUBLIC_IP}/32" dev lo scope host
+           exit 0
+       fi
+
+       # Change the scope so this node starts responding to ARPs
+       ip addr del "${CTDB_LVS_PUBLIC_IP}/32" dev lo >/dev/null 2>&1
+       ip addr add "${CTDB_LVS_PUBLIC_IP}/32" dev lo >/dev/null 2>&1
+
+       ipvsadm -A -t "$CTDB_LVS_PUBLIC_IP" -p 1999999 -s lc
+       ipvsadm -A -u "$CTDB_LVS_PUBLIC_IP" -p 1999999 -s lc
+
+       # Add all nodes (except this node) as LVS servers
+       "${CTDB_HELPER_BINDIR}/ctdb_lvs" list |
+       awk -v pnn="$pnn" '$1 != pnn { print $2 }' |
+       while read ip ; do
+               ipvsadm -a -t "$CTDB_LVS_PUBLIC_IP" -r "$ip" -g
+               ipvsadm -a -u "$CTDB_LVS_PUBLIC_IP" -r "$ip" -g
+       done
+
+       # Add localhost too...
+       ipvsadm -a -t "$CTDB_LVS_PUBLIC_IP" -r 127.0.0.1
+       ipvsadm -a -u "$CTDB_LVS_PUBLIC_IP" -r 127.0.0.1
+
+       $CTDB gratarp \
+            "$CTDB_LVS_PUBLIC_IP" "$CTDB_LVS_PUBLIC_IFACE" >/dev/null 2>&1
+
+       flush_route_cache
+       ;;
+
+monitor)
+       lvs_check_config
+
+       if [ -n "$CTDB_LVS_PUBLIC_IFACE" ] ; then
+               interface_monitor "$CTDB_LVS_PUBLIC_IFACE" || exit 1
+       fi
+       ;;
+esac
+
+exit 0
diff --git a/ctdb/events.d/99.timeout b/ctdb/events.d/99.timeout
new file mode 100755 (executable)
index 0000000..4aa5940
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Event script to just sleep longer than the timeout
+# in the monitor action. The purpose is to trigger
+# the event timeout mechanism.
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; dirname "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+loadconfig ctdb
+
+[ "$CTDB_RUN_TIMEOUT_MONITOR" = "yes" ] || exit 0
+
+case "$1" in
+monitor)
+       TIMEOUT=$($CTDB getvar EventScriptTimeout | awk '{print $3}')
+       echo "sleeping for $((TIMEOUT * 2)) seconds..."
+       sleep $((TIMEOUT * 2))
+       ;;
+esac
+
+exit 0
+
diff --git a/ctdb/events.d/README b/ctdb/events.d/README
new file mode 100644 (file)
index 0000000..69f5904
--- /dev/null
@@ -0,0 +1,193 @@
+The events.d/ directory contains event scripts used by CTDB.  Event
+scripts are triggered on certain events, such as startup, monitoring
+or public IP allocation.  Scripts may be specific to services,
+networking or internal CTDB operations.
+
+All event scripts start with the prefix 'NN.' where N is a digit.  The
+event scripts are run in sequence based on NN.  Thus 10.interface will
+be run before 60.nfs.  It is recommended to keep each NN unique.
+However, scripts with the same NN prefix will be executed in
+alphanumeric sort order.
+
+As a special case, any eventscript that ends with a '~' character will be
+ignored since this is a common postfix that some editors will append to
+older versions of a file.  Similarly, any eventscript with multiple '.'s
+will be ignored as package managers can create copies with additional
+suffix starting with '.' (e.g. .rpmnew, .dpkg-dist).
+
+Only executable event scripts are run by CTDB.  Any event script that
+does not have execute permission is ignored.
+
+The eventscripts are called with varying number of arguments.  The
+first argument is the event name and the rest of the arguments depend
+on the event name.
+
+Event scripts must return 0 for success and non-zero for failure.
+
+Output of event scripts is logged.  On failure the output of the
+failing event script is included in the output of "ctdb scriptstatus".
+
+The following events are supported (with arguments shown):
+
+init
+
+       This event is triggered once when CTDB is starting up.  This
+       event is used to do some basic cleanup and initialisation.
+
+       During the "init" event CTDB is not listening on its Unix
+       domain socket, so the "ctdb" CLI will not work.
+
+       Failure of this event will cause CTDB to terminate.
+
+       Example: 00.ctdb creates $CTDB_SCRIPT_VARDIR
+
+setup
+
+       This event is triggered once, after the "init" event has
+       completed.
+
+       For this and any subsequent events the CTDB Unix domain socket
+       is available, so the "ctdb" CLI will work.
+
+       Failure of this event will cause CTDB to terminate.
+
+       Example: 00.ctdb processes tunables defined in the CTDB
+        configuration using CTDB_SET_<TunableName>=<TunableValue>.
+
+startup
+
+       This event is triggered after the "setup" event has completed
+       and CTDB has finished its initial database recovery.
+
+       This event starts all services that are managed by CTDB.  Each
+       service that is managed by CTDB should implement this event
+       and use it to (re)start the service.
+
+       If the "startup" event fails then CTDB will retry it until it
+       succeeds.  There is no limit on the number of retries.
+
+       Example: 50.samba uses this event to start the Samba daemon if
+       CTDB_MANAGES_SAMBA=yes.
+
+shutdown
+
+       This event is triggered when CTDB is shutting down.
+
+       This event shuts down all services that are managed by CTDB.
+       Each service that is managed by CTDB should implement this
+       event and use it to stop the service.
+
+       Example: 50.samba uses this event to shut down the Samba
+       daemon if CTDB_MANAGES_SAMBA=yes.
+
+monitor
+
+       This event is run periodically.  The interval between
+       successive "monitor" events is configured using the
+       MonitorInterval tunable, which defaults to 15 seconds.
+
+       This event is triggered by CTDB to continuously monitor that
+       all managed services are healthy.  If all event scripts
+       complete then the monitor event successfully then the node is
+       marked HEALTHY.  If any event script fails then no subsequent
+       scripts will be run for that event and the node is marked
+       UNHEALTHY.
+
+       Each service that is managed by CTDB should implement this
+       event and use it to monitor the service.
+
+       Example: 10.interface checks that each configured interface
+       for public IP addresses has a physical link established.
+
+startrecovery
+
+       This event is triggered every time a database recovery process
+       is started.
+
+       This is rarely used.
+
+recovered
+
+       This event is triggered every time a database recovery process
+       is completed.
+
+       This is rarely used.
+
+takeip <interface> <ip-address> <netmask-bits>
+
+       This event is triggered for each public IP address taken by a
+       node during IP address (re)assignment.  Multiple "takeip"
+       events can be run in parallel if multiple IP addresses are
+       being assigned.
+
+       Example: In 10.interface the "ip" command (from the Linux
+       iproute2 package) is used to add the specified public IP
+       address to the specified interface.  The "ip" command can
+       safely be run concurrently.  However, the "iptables" command
+       cannot be run concurrently so a wrapper is used to serialise
+       runs using exclusive locking.
+
+       If substantial work is required to reconfigure a service when
+       a public IP address is taken over it can be better to defer
+       service reconfiguration to the "ipreallocated" event, after
+       all IP addresses have been assigned.
+
+       Example: 60.nfs uses ctdb_service_set_reconfigure() to flag
+       that public IP addresses have changed so that service
+       reconfiguration will occur in the "ipreallocated" event.
+
+releaseip <interface> <ip-address> <netmask-bits>
+
+       This event is triggered for each public IP address released by
+       a node during IP address (re)assignment.  Multiple "releaseip"
+       events can be run in parallel if multiple IP addresses are
+       being unassigned.
+
+       In all other regards, this event is analogous to the "takeip"
+       event above.
+
+updateip <old-interface> <new-interface> <ip-address> <netmask-bits>
+
+       This event is triggered for each public IP address moved
+       between interfaces on a node during IP address (re)assignment.
+       Multiple "updateip" events can be run in parallel if multiple
+       IP addresses are being moved.
+
+        This event is only used if multiple interfaces are capable of
+        hosting an IP address, as specified in the public addresses
+        configuration file.
+
+       This event is similar to the "takeip" event above.
+
+ipreallocated
+
+       This event is triggered after "releaseip", "takeip" and
+       "updateip" events during public IP address (re)assignment.
+
+       This event is used to reconfigure services.
+
+        This event runs even if public IP addresses on a node have not
+       been changed.  This allows reconfiguration to depend on the
+       states of other nodes rather that just IP addresses.
+
+       Example: 11.natgw recalculates the NAT gateway master and
+       updates the relevant network configuration on each node if the
+       NAT gateway master has changed.
+
+Additional notes for "takeip", "releaseip", "updateip",
+ipreallocated":
+
+* Failure of any of these events causes IP allocation to be retried.
+
+* The "ipreallocated" event is run on all nodes.  It is even run if no
+  "takeip", "releaseip" or "updateip" events were triggered.
+
+* An event script can use ctdb_service_set_reconfigure() in "takeip"
+  or "releaseip" events to flag that its service needs to be
+  reconfigured.  The event script can then define a
+  service_reconfigure() function, which will be implicitly run before
+  the "ipreallocated" event.  This is a useful way of performing
+  reconfiguration that is conditional upon public IP address changes.
+
+  This means an explicit "ipreallocated" event handler is usually not
+  necessary.
diff --git a/ctdb/functions b/ctdb/functions
new file mode 100644 (file)
index 0000000..3c2a962
--- /dev/null
@@ -0,0 +1,1020 @@
+# Hey Emacs, this is a -*- shell-script -*- !!!
+
+# utility functions for ctdb event scripts
+
+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="/var/lib/ctdb"
+ctdb_rundir="/var/run/ctdb"
+
+CTDB="${CTDB:-/usr/bin/ctdb}"
+
+# Only (and always) override these variables in test code
+
+if [ -z "$CTDB_SCRIPT_VARDIR" ] ; then
+    CTDB_SCRIPT_VARDIR="/var/lib/ctdb/state"
+fi
+
+if [ -z "$CTDB_SYS_ETCDIR" ] ; then
+    CTDB_SYS_ETCDIR="/etc"
+fi
+
+if [ -z "$CTDB_HELPER_BINDIR" ] ; then
+    CTDB_HELPER_BINDIR="/usr/lib/x86_64-linux-gnu/ctdb"
+fi
+
+#######################################
+# pull in a system config file, if any
+
+rewrite_ctdb_options ()
+{
+    case "$CTDB_DBDIR" in
+       tmpfs|tmpfs:*)
+           _opts_defaults="mode=700"
+           # Get any extra options specified after colon
+           if [ "$CTDB_DBDIR" = "tmpfs" ] ; then
+               _opts=""
+           else
+               _opts="${CTDB_DBDIR#tmpfs:}"
+           fi
+           # It is OK to repeat mount options - last value wins.
+           # CTDB_DBDIR_TMPFS_OPTIONS is used by ctdbd_wrapper
+           # shellcheck disable=SC2034
+           CTDB_DBDIR_TMPFS_OPTIONS="${_opts_defaults}${_opts:+,}${_opts}"
+
+           CTDB_DBDIR="${ctdb_rundir}/CTDB_DBDIR"
+           ;;
+       *)
+           # shellcheck disable=SC2034
+           CTDB_DBDIR_TMPFS_OPTIONS=""
+    esac
+}
+
+_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_SYS_ETCDIR}/sysconfig/$1" ]; then
+       . "${CTDB_SYS_ETCDIR}/sysconfig/$1"
+    elif [ -f "${CTDB_SYS_ETCDIR}/default/$1" ]; then
+       . "${CTDB_SYS_ETCDIR}/default/$1"
+    elif [ -f "${CTDB_BASE}/sysconfig/$1" ]; then
+       . "${CTDB_BASE}/sysconfig/$1"
+    fi
+
+    if [ "$1" = "ctdb" ] ; then
+       _config="${CTDBD_CONF:-${CTDB_BASE}/ctdbd.conf}"
+       if [ -r "$_config" ] ; then
+           . "$_config"
+       fi
+       rewrite_ctdb_options
+    fi
+}
+
+loadconfig () {
+    _loadconfig "$@"
+}
+
+##############################################################
+
+die ()
+{
+    _msg="$1"
+    _rc="${2:-1}"
+
+    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 ()
+{
+    _tag="$1" ; shift
+
+    case "$CTDB_LOGGING" in
+       file:*|"")
+           if [ -n "$CTDB_LOGGING" ] ; then
+               _file="${CTDB_LOGGING#file:}"
+           else
+               _file="/var/log/ctdb/log.ctdb"
+           fi
+           {
+               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
+}
+
+# When things are run in the background in an eventscript then logging
+# output might get lost.  This is the "solution".  :-)
+background_with_logging ()
+{
+    (
+       "$@" 2>&1 </dev/null |
+       script_log "${script_name}&"
+    )&
+
+    return 0
+}
+
+##############################################################
+# check number of args for different events
+ctdb_check_args ()
+{
+    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
+}
+
+##############################################################
+# 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
+}
+
+######################################################
+# simulate /sbin/service on platforms that don't have it
+# _service() makes it easier to hook the service() function for
+# testing.
+_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 /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 "$@"
+}
+
+######################################################
+# simulate /sbin/service (niced) on platforms that don't have it
+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?
+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 ()
+{
+    _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
+
+    # ip_address is used by caller
+    # shellcheck disable=SC2034
+    cat "$_ip_addr_file"
+}
+
+######################################################
+# 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 ()
+{
+    echo "$2" >"/proc/$1"
+}
+
+set_proc_maybe ()
+{
+    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 ()
+{
+    cat "/proc/$1"
+}
+
+######################################################
+# Print up to $_max kernel stack traces for processes named $_program
+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
+}
+
+######################################################
+# Ensure $service_name is set
+assert_service_name ()
+{
+    [ -n "$service_name" ] || die "INTERNAL ERROR: \$service_name not set"
+}
+
+######################################################
+# check a set of directories is available
+# return 1 on a missing directory
+# directories are read from stdin
+######################################################
+ctdb_check_directories_probe()
+{
+    while IFS="" read d ; do
+       case "$d" in
+           *%*)
+               continue
+               ;;
+           *)
+               [ -d "${d}/." ] || return 1
+       esac
+    done
+}
+
+######################################################
+# check a set of directories is available
+# directories are read from stdin
+######################################################
+ctdb_check_directories()
+{
+    ctdb_check_directories_probe || {
+       echo "ERROR: $service_name directory \"$d\" not available"
+       exit 1
+    }
+}
+
+######################################################
+# check a set of tcp ports
+# usage: ctdb_check_tcp_ports <ports...>
+######################################################
+
+# 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)
+                       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
+       return 0
+}
+
+######################################################
+# check a unix socket
+# usage: ctdb_check_unix_socket SERVICE_NAME <socket_path>
+######################################################
+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
+}
+
+################################################
+# kill off any TCP connections with the given IP
+################################################
+kill_tcp_connections ()
+{
+    _iface="$1"
+    _ip="$2"
+
+    _oneway=false
+    if [ "$3" = "oneway" ] ; then
+       _oneway=true
+    fi
+
+    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
+
+           _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
+
+       echo "$_connections" | \
+               "${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
+
+       _actually_killed=$((_killcount - _remaining))
+
+       _t="${_actually_killed}/${_killcount}"
+       echo "Killed ${_t} TCP connections to released IP $_ip"
+
+       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 "$@" "oneway"
+}
+
+##################################################################
+# tickle any TCP connections with the given IP
+##################################################################
+tickle_tcp_connections ()
+{
+    _ip="$1"
+
+    # Get connections, both directions
+    _conns=$(get_tcp_connections_for_ip "$_ip" | \
+                   awk '{ print $1, $2 ; print $2, $1 }')
+
+    echo "$_conns" | awk '{ print "Tickle TCP connection", $1, $2 }'
+    echo "$_conns" | ctdb tickle
+}
+
+get_tcp_connections_for_ip ()
+{
+    _ip="$1"
+
+    ss -tn state established "src [$_ip]" | awk 'NR > 1 {print $3, $4}'
+}
+
+########################################################
+
+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
+
+    # 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"
+       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
+
+       # 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
+    }
+}
+
+# If the given IP is hosted then print 2 items: maskbits and iface
+ip_maskbits_iface ()
+{
+    _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 ()
+{
+    _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 ()
+{
+       # _x is intentionally ignored
+       # shellcheck disable=SC2034
+       while read _ip _x ; do
+               drop_ip "$_ip"
+       done <"${CTDB_PUBLIC_ADDRESSES:-/dev/null}"
+}
+
+flush_route_cache ()
+{
+    set_proc_maybe sys/net/ipv4/route/flush 1
+    set_proc_maybe sys/net/ipv6/route/flush 1
+}
+
+########################################################
+# Interface monitoring
+
+# If the interface is a virtual one (e.g. VLAN) then get the
+# underlying interface
+interface_get_real ()
+{
+    # Output of "ip link show <iface>"
+    _iface_info="$1"
+
+    # Extract the full interface description to see if it is a VLAN
+    _t=$(echo "$_iface_info" |
+               awk 'NR == 1 { iface = $2; sub(":$", "", iface) ;
+                              print iface }')
+    case "$_t" in
+       *@*)
+           # VLAN: use the underlying interface, after the '@'
+           echo "${_t##*@}"
+           ;;
+       *)
+           # Not a regular VLAN.  For backward compatibility, assume
+           # there is some other sort of VLAN that doesn't have the
+           # '@' in the output and only use what is before a '.'.  If
+           # there is no '.' then this will be the whole interface
+           # name.
+           echo "${_t%%.*}"
+    esac
+}
+
+# Check whether an interface is operational
+interface_monitor ()
+{
+    _iface="$1"
+
+    _iface_info=$(ip 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
+       # 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
+}
+
+########################################################
+# Simple counters
+_ctdb_counter_common () {
+    _service_name="${1:-${service_name:-${script_name}}}"
+    _counter_file="${CTDB_SCRIPT_VARDIR}/failcount/${_service_name}"
+    mkdir -p "${_counter_file%/*}" # dirname
+}
+# Some code passes an argument
+# shellcheck disable=SC2120
+ctdb_counter_init () {
+    _ctdb_counter_common "$1"
+
+    >"$_counter_file"
+}
+ctdb_counter_incr () {
+    _ctdb_counter_common "$1"
+
+    # unary counting using newlines!
+    echo >>"$_counter_file"
+}
+ctdb_counter_get () {
+    _ctdb_counter_common "$1"
+    # unary counting!
+    stat -c "%s" "$_counter_file" 2>/dev/null || echo 0
+}
+
+########################################################
+
+ctdb_setup_service_state_dir ()
+{
+       _s="${1:-${service_name}}"
+
+       _service_state_dir="${CTDB_SCRIPT_VARDIR}/service_state/${_s}"
+       mkdir -p "$_service_state_dir" ||
+               die "Error creating state dir \"${_service_state_dir}\""
+
+       echo "$_service_state_dir"
+}
+
+##################################################################
+# Reconfigure a service on demand
+
+_ctdb_service_reconfigure_common ()
+{
+    _d="${CTDB_SCRIPT_VARDIR}/service_status/${service_name}"
+    mkdir -p "$_d"
+    _ctdb_service_reconfigure_flag="$_d/reconfigure"
+}
+
+ctdb_service_needs_reconfigure ()
+{
+    _ctdb_service_reconfigure_common
+    [ -e "$_ctdb_service_reconfigure_flag" ]
+}
+
+ctdb_service_set_reconfigure ()
+{
+    _ctdb_service_reconfigure_common
+    >"$_ctdb_service_reconfigure_flag"
+}
+
+ctdb_service_unset_reconfigure ()
+{
+    _ctdb_service_reconfigure_common
+    rm -f "$_ctdb_service_reconfigure_flag"
+}
+
+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
+}
+
+# Default service_reconfigure() function does nothing.
+service_reconfigure ()
+{
+    :
+}
+
+##################################################################
+# Does CTDB manage this service?
+
+ctdb_compat_managed_service ()
+{
+    if [ "$1" = "yes" -a "$2" = "$service_name" ] ; then
+       CTDB_MANAGED_SERVICES="$CTDB_MANAGED_SERVICES $2"
+    fi
+}
+
+is_ctdb_managed_service ()
+{
+    assert_service_name
+
+    # $t is used just for readability and to allow better accurate
+    # matching via leading/trailing spaces
+    t=" $CTDB_MANAGED_SERVICES "
+
+    # 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}" ]
+}
+
+# Default service_start() and service_stop() functions.
+# These may be overridden in an eventscript.
+service_start ()
+{
+    service "$service_name" start
+}
+
+service_stop ()
+{
+    service "$service_name" stop
+}
+
+##################################################################
+
+# This exists only for backward compatibility with 3rd party scripts
+# that call it
+ctdb_standard_event_handler ()
+{
+    :
+}
+
+iptables_wrapper ()
+{
+    _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_SCRIPT_VARDIR}/iptables.flock" "$_iptables_cmd" "$@"
+}
+
+# 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 | \
+           md5sum | \
+           sed -e 's@\(..........\).*@\1@')
+       _t="${_d}/tmp.${_hex10}"
+       (
+           umask 077
+           if $_dir ; then
+               mkdir "$_t"
+           else
+               >"$_t"
+           fi
+       )
+       echo "$_t"
+    }
+fi
+
+######################################################################
+# NFS callout handling
+
+nfs_callout_init ()
+{
+       _state_dir="$1"
+
+       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
+
+       # If the callout wants to use this then it must create it
+       export CTDB_NFS_CALLOUT_STATE_DIR="${_state_dir}/callout-state"
+
+       # 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
+
+       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"
+}
+
+nfs_callout_register ()
+{
+    mkdir -p "$nfs_callout_cache_ops"
+    rm -f "$nfs_callout_cache_ops"/*
+
+    echo "$CTDB_NFS_CALLOUT" >"$nfs_callout_cache_callout"
+
+    _t=$(eval "$CTDB_NFS_CALLOUT" "register")
+    if [ -n "$_t" ] ; then
+       echo "$_t" |
+           while IFS="" read _op ; do
+               touch "${nfs_callout_cache_ops}/${_op}"
+           done
+    else
+       touch "${nfs_callout_cache_ops}/ALL"
+    fi
+}
+
+nfs_callout ()
+{
+    # Re-run registration if $CTDB_NFS_CALLOUT has changed
+    _prev=""
+    if [ -r "$nfs_callout_cache_callout" ] ; then
+       read _prev <"$nfs_callout_cache_callout"
+    fi
+    if [ "$CTDB_NFS_CALLOUT" != "$_prev" ] ; then
+       nfs_callout_register
+    fi
+
+    # Run the operation if it is registered...
+    if [ -e "${nfs_callout_cache_ops}/${1}" ] || \
+          [ -e "${nfs_callout_cache_ops}/ALL" ]; then
+       eval "$CTDB_NFS_CALLOUT" "$@"
+    fi
+}
+
+########################################################
+# tickle handling
+########################################################
+
+update_tickles ()
+{
+       _port="$1"
+
+       tickledir="${CTDB_SCRIPT_VARDIR}/tickles"
+       mkdir -p "$tickledir"
+
+       # What public IPs do I hold?
+       _pnn=$(ctdb_get_pnn)
+       _ips=$($CTDB -X ip | awk -F'|' -v pnn="$_pnn" '$3 == pnn {print $2}')
+
+       # 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!
+       ss -tn state established \
+          "${_ip_filter:+( ${_ip_filter} )}" \
+          "${_port_filter:+( ${_port_filter} )}" |
+       awk 'NR > 1 {print $4, $3}' |
+       sort >"$_my_connections"
+
+       # Record our current tickles in a temporary file
+       _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"
+
+       # Add tickles for connections that we haven't already got tickles for
+       comm -23 "$_my_connections" "$_my_tickles" | \
+               $CTDB addtickle
+
+       # Remove tickles for connections that are no longer there
+       comm -13 "$_my_connections" "$_my_tickles" | \
+               $CTDB deltickle
+
+       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"
+}
+
+[ -d "${CTDB_BASE}/rc.local.d" ] && {
+       for i in "${CTDB_BASE}/rc.local.d"/* ; do
+               [ -x "$i" ] && . "$i"
+       done
+}
+
+script_name="${0##*/}"       # basename
diff --git a/ctdb/gcore_trace.sh b/ctdb/gcore_trace.sh
new file mode 100755 (executable)
index 0000000..4d3e1d1
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+gcore -o "/var/log/core" "$1" 2>&1 | logger -t "ctdb:gcore_trace"
diff --git a/ctdb/nfs-checks.d/00.portmapper.check b/ctdb/nfs-checks.d/00.portmapper.check
new file mode 100644 (file)
index 0000000..24def35
--- /dev/null
@@ -0,0 +1,2 @@
+# portmapper
+unhealthy_after=1
diff --git a/ctdb/nfs-checks.d/10.status.check b/ctdb/nfs-checks.d/10.status.check
new file mode 100644 (file)
index 0000000..dfa5c59
--- /dev/null
@@ -0,0 +1,7 @@
+# status
+version="1"
+restart_every=2
+unhealthy_after=6
+service_stop_cmd="killall -q -9 rpc.statd"
+service_start_cmd="rpc.statd ${STATD_HA_CALLOUT:+-H} $STATD_HA_CALLOUT ${STATD_HOSTNAME:+-n} $STATD_HOSTNAME ${STATD_PORT:+-p} $STATD_PORT ${STATD_OUTGOING_PORT:+-o} $STATD_OUTGOING_PORT"
+service_debug_cmd="program_stack_traces rpc.statd 5"
diff --git a/ctdb/nfs-checks.d/20.nfs.check b/ctdb/nfs-checks.d/20.nfs.check
new file mode 100644 (file)
index 0000000..dad1cdc
--- /dev/null
@@ -0,0 +1,7 @@
+# nfs
+version="3"
+restart_every=10
+unhealthy_after=2
+service_stop_cmd="$CTDB_NFS_CALLOUT stop nfs"
+service_start_cmd="$CTDB_NFS_CALLOUT start nfs"
+service_debug_cmd="program_stack_traces nfsd 5"
diff --git a/ctdb/nfs-checks.d/30.nlockmgr.check b/ctdb/nfs-checks.d/30.nlockmgr.check
new file mode 100644 (file)
index 0000000..6660ca0
--- /dev/null
@@ -0,0 +1,6 @@
+# nlockmgr
+version="4"
+restart_every=2
+unhealthy_after=6
+service_stop_cmd="$CTDB_NFS_CALLOUT stop nlockmgr"
+service_start_cmd="$CTDB_NFS_CALLOUT start nlockmgr"
diff --git a/ctdb/nfs-checks.d/40.mountd.check b/ctdb/nfs-checks.d/40.mountd.check
new file mode 100644 (file)
index 0000000..56b3fd2
--- /dev/null
@@ -0,0 +1,7 @@
+# mountd
+version="1"
+restart_every=2
+unhealthy_after=6
+service_stop_cmd="killall -q -9 rpc.mountd"
+service_start_cmd="rpc.mountd $RPCMOUNTDOPTS ${MOUNTD_PORT:+-p} $MOUNTD_PORT"
+service_debug_cmd="program_stack_traces rpc.mountd 5"
diff --git a/ctdb/nfs-checks.d/50.rquotad.check b/ctdb/nfs-checks.d/50.rquotad.check
new file mode 100644 (file)
index 0000000..b7bd9d2
--- /dev/null
@@ -0,0 +1,7 @@
+# rquotad
+version="1"
+restart_every=2
+unhealthy_after=6
+service_stop_cmd="killall -q -9 rpc.rquotad"
+service_start_cmd="rpc.rquotad ${RQUOTAD_PORT:+-p} $RQUOTAD_PORT"
+service_debug_cmd="program_stack_traces rpc.rquotad 5"
diff --git a/ctdb/nfs-checks.d/README b/ctdb/nfs-checks.d/README
new file mode 100644 (file)
index 0000000..044067a
--- /dev/null
@@ -0,0 +1,31 @@
+NFS check configuration files.
+
+Files are named NN.RPCSERVICE.check.  Files without a .check suffix
+are ignored.
+
+Supported variables are:
+
+* family             - "tcp" or "udp" or space separated list
+                       default: tcp, not used with "service_check_cmd"
+* version            - optional, RPC service version number
+                       default is to omit to check for any version,
+                       not used with "service_check_cmd"
+* unhealthy_after    - number of check fails before unhealthy
+                       default: 1
+* restart_every      - number of check fails before restart
+                       default: 0, meaning no restart
+* service_stop_cmd   - command to stop service
+                       default: no default, must be provided if
+                                restart_every > 0
+* service_start_cmd  - command to start service
+                       default: no default, must be provided if
+                                restart_every > 0
+* service_check_cmd  - command to check health of service
+                       default is to check RPC service using rpcinfo
+* service_debug_cmd  - command to debug a service after trying to stop it;
+                       for example, it can be useful to print stack
+                       traces of threads that have not exited, since
+                       they may be stuck doing I/O;
+                       no default, see also function program_stack_traces()
+
+Quoting inside values is not preserved.
diff --git a/ctdb/nfs-linux-kernel-callout b/ctdb/nfs-linux-kernel-callout
new file mode 100755 (executable)
index 0000000..9b72446
--- /dev/null
@@ -0,0 +1,253 @@
+#!/bin/sh
+
+# Exit on 1st error
+set -e
+
+# NFS exports file.  Some code below keeps a cache of output derived
+# from exportfs(8).  When this file is updated the cache is invalid
+# and needs to be regenerated.
+#
+# To change the file, edit the default value below.  Do not set
+# CTDB_NFS_EXPORTS_FILE - it isn't a configuration variable, just a
+# hook for testing.
+nfs_exports_file="${CTDB_NFS_EXPORTS_FILE:-/var/lib/nfs/etab}"
+
+# Red Hat
+nfs_service="nfs"
+nfslock_service="nfslock"
+nfs_config="/etc/sysconfig/nfs"
+
+# SUSE
+#nfs_service="nfsserver"
+#nfslock_service=""
+#nfs_config="/etc/sysconfig/nfs"
+
+# Debian
+#nfs_service="nfs-kernel-server"
+#nfslock_service=""
+#nfs_config="/etc/default/nfs-kernel-server"
+
+# Override for unit testing
+if [ -z "$PROCFS_PATH" ] ; then
+    PROCFS_PATH="/proc"
+fi
+
+##################################################
+
+usage ()
+{
+    _c=$(basename "$0")
+    cat <<EOF
+usage: $_c { shutdown | startup }
+       $_c { stop | start } { nfs | nlockmgr }
+       $_c { monitor-list-shares | monitor-post }
+       $_c { register }
+EOF
+    exit 1
+}
+
+
+##################################################
+# Basic service stop and start
+
+basic_stop ()
+{
+    case "$1" in
+       nfs)
+           service "$nfs_service" stop
+           if [ -n "$nfslock_service" ] ; then
+               service "$nfslock_service" stop
+           fi
+           ;;
+       nfslock)
+           if [ -n "$nfslock_service" ] ; then
+               service "$nfslock_service" stop
+           else
+               service "$nfs_service" stop
+           fi
+           ;;
+       *)
+           usage
+    esac
+}
+
+basic_start ()
+{
+    case "$1" in
+       nfs)
+           if [ -n "$nfslock_service" ] ; then
+               service "$nfslock_service" start
+           fi
+           service "$nfs_service" start
+           ;;
+       nfslock)
+           if [ -n "$nfslock_service" ] ; then
+               service "$nfslock_service" start
+           else
+               service "$nfs_service" start
+           fi
+           ;;
+       *)
+           usage
+    esac
+}
+
+##################################################
+# service "stop" and "start" options for restarting
+
+service_stop ()
+{
+    case "$1" in
+       nfs)
+           echo 0 >"${PROCFS_PATH}/fs/nfsd/threads"
+           basic_stop "nfs" >/dev/null 2>&1 || true
+           pkill -9 nfsd
+           ;;
+       nlockmgr)
+           basic_stop "nfslock" >/dev/null 2>&1 || true
+           ;;
+       *)
+           usage
+    esac
+}
+
+service_start ()
+{
+    case "$1" in
+       nfs)
+           basic_start "nfs"
+           ;;
+       nlockmgr)
+           basic_start "nfslock"
+           ;;
+       *)
+           usage
+    esac
+}
+
+##################################################
+# service init startup and final shutdown
+
+nfs_shutdown ()
+{
+    basic_stop "nfs"
+}
+
+nfs_startup ()
+{
+    basic_stop "nfs" || true
+    basic_start "nfs"
+    _f="${PROCFS_PATH}/sys/net/ipv4/tcp_tw_recycle"
+    if [ "$_f" ] ; then
+       echo 1 >"$_f"
+    fi
+}
+
+##################################################
+# monitor-post support
+
+nfs_check_thread_count ()
+{
+    # Load NFS configuration to get desired number of threads.
+    if [ -r "$nfs_config" ] ; then
+       . "$nfs_config"
+    fi
+
+    # If $RPCNFSDCOUNT/$USE_KERNEL_NFSD_NUMBER isn't set then we could
+    # guess the default from the initscript.  However, let's just
+    # assume that those using the default don't care about the number
+    # of threads and that they have switched on this feature in error.
+    _configured_threads="${RPCNFSDCOUNT:-${USE_KERNEL_NFSD_NUMBER}}"
+    [ -n "$_configured_threads" ] || return 0
+
+    _threads_file="${PROCFS_PATH}/fs/nfsd/threads"
+
+    # nfsd should be running the configured number of threads.  If
+    # there are a different number of threads then tell nfsd the
+    # correct number.
+    read _running_threads <"$_threads_file" || {
+           echo "WARNING: Reading \"${_threads_file}\" unexpectedly failed"
+           exit 0
+    }
+
+    # Intentionally not arithmetic comparison - avoids extra errors
+    # when above read fails in an unexpected way...
+    if [ "$_running_threads" != "$_configured_threads" ] ; then
+       echo "Attempting to correct number of nfsd threads from ${_running_threads} to ${_configured_threads}"
+       echo "$_configured_threads" >"$_threads_file"
+    fi
+}
+
+##################################################
+# list share directories
+
+nfs_monitor_list_shares ()
+{
+    _cache_file="${CTDB_NFS_CALLOUT_STATE_DIR}/list_shares_cache"
+    # -nt operator is well supported in Linux: dash, bash, ksh, ...
+    # shellcheck disable=SC2039
+    if  [ ! -r "$nfs_exports_file" ] || [ ! -r "$_cache_file" ] || \
+           [ "$nfs_exports_file" -nt "$_cache_file" ] ; then
+       mkdir -p "$CTDB_NFS_CALLOUT_STATE_DIR"
+       # We could just use the contents of $nfs_exports_file.
+       # However, let's regard that file as internal to NFS and use
+       # exportfs, which is the public API.
+       if ! _exports=$(exportfs -v) ; then
+           echo "WARNING: failed to run exportfs to list NFS shares" >&2
+           return
+       fi
+
+       echo "$_exports" |
+           grep '^/' |
+           sed -e 's@[[:space:]][[:space:]]*[^[:space:]()][^[:space:]()]*([^[:space:]()][^[:space:]()]*)$@@' |
+           sort -u >"$_cache_file"
+    fi
+
+    cat "$_cache_file"
+}
+
+##################################################
+
+nfs_register ()
+{
+    cat <<EOF
+shutdown
+startup
+stop
+start
+monitor-list-shares
+monitor-post
+EOF
+}
+
+##################################################
+
+case "$1" in
+    shutdown)
+       nfs_shutdown
+       ;;
+    startup)
+       nfs_startup
+       ;;
+    stop)
+       service_stop "$2"
+       ;;
+    start)
+       service_start "$2"
+       ;;
+    monitor-list-shares)
+       nfs_monitor_list_shares
+       ;;
+    monitor-post)
+       nfs_check_thread_count
+       ;;
+    register)
+       nfs_register
+       ;;
+    monitor-pre|releaseip|takeip|releaseip-pre|takeip-pre)
+       # Not required/implemented
+       :
+       ;;
+    *)
+       usage
+esac
diff --git a/ctdb/notify.d/README b/ctdb/notify.d/README
new file mode 100644 (file)
index 0000000..ffce7fa
--- /dev/null
@@ -0,0 +1,44 @@
+This directory should contain executable programs to handle CTDB event
+notifications.  The first and only argument passed to each program is
+the event, which is one of:
+
+  init, setup, startup, unhealthy, healthy
+
+To use notifications with this directory then you need to set:
+
+  CTDB_NOTIFY_SCRIPT=/etc/ctdb/notify.sh
+
+in your CTDB configuration file.
+
+An example script that sends SNMP traps for unhealthy/healthy might
+look like this:
+
+  #!/bin/sh
+
+  case "$1" in
+      unhealthy)
+          # Send an SNMP trap saying that the node is unhealthy:
+          snmptrap -m ALL -v 1 -c public 10.1.1.105 ctdb \
+              $(hostname) 0 0 $(date +"%s") ctdb.nodeHealth.0 i 1
+          ;;
+      healthy)
+          # Send an SNMP trap saying that the node is healthy again:
+          snmptrap -m ALL -v 1 -c public 10.1.1.105 ctdb \
+             $(hostname) 0 0 $(date +"%s") ctdb.nodeHealth.0 i 0
+         ;;
+  esac
+
+Alternatively, email could be sent:
+
+  #!/bin/sh
+
+  case "$1" in
+      unhealthy)
+          mail -s "$(hostname) is UNHEALTHY" foo@example.com </dev/null >/dev/null 2>&1
+          ;;
+      healthy)
+          mail -s "$(hostname) is HEALTHY" foo@example.com </dev/null >/dev/null 2>&1
+          ;;
+  esac
+
+When adding programs please note the exclusion patterns in notify.sh.
diff --git a/ctdb/notify.sh b/ctdb/notify.sh
new file mode 100755 (executable)
index 0000000..d98f046
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# This script is activated by setting CTDB_NOTIFY_SCRIPT=/etc/ctdb/notify.sh
+# in /etc/sysconfig/ctdb
+
+# This is script is invoked from ctdb when certain events happen.  See
+# /etc/ctdb/notify.d/README for more details.
+
+d=$(dirname "$0")
+nd="${d}/notify.d"
+
+ok=true
+
+for i in "${nd}/"* ; do
+    # Don't run files matching basename
+    case "${i##*/}" in
+       *~|*,|*.rpm*|*.swp|README) continue ;;
+    esac
+
+    # Files must be executable
+    [ -x "$i" ] || continue
+
+    # Flag failures
+    "$i" "$1" || ok=false
+done
+
+$ok
diff --git a/ctdb/statd-callout b/ctdb/statd-callout
new file mode 100755 (executable)
index 0000000..38f847b
--- /dev/null
@@ -0,0 +1,221 @@
+#!/bin/sh
+
+# This must run as root as CTDB tool commands need to access CTDB socket
+[ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
+
+# this script needs to be installed so that statd points to it with the -H 
+# command line argument. The easiest way to do that is to put something like this in 
+# /etc/sysconfig/nfs:
+#   STATD_HOSTNAME="myhostname -H /etc/ctdb/statd-callout"
+
+[ -n "$CTDB_BASE" ] || \
+    CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
+
+. "${CTDB_BASE}/functions"
+
+# Overwrite this so we get some logging
+die ()
+{
+    script_log "statd-callout" "$@"
+    exit 1
+}
+
+loadconfig ctdb
+loadconfig nfs
+
+[ -n "$NFS_HOSTNAME" ] || \
+    die "NFS_HOSTNAME is not configured. statd-callout failed"
+
+# A handy newline
+nl="
+"
+
+service_state_dir=$(ctdb_setup_service_state_dir "statd-callout") || exit $?
+
+cd "$service_state_dir" || \
+    die "Failed to change directory to \"${service_state_dir}\""
+
+pnn=$(ctdb_get_pnn)
+
+case "$1" in
+    # Keep a single file to keep track of the last "add-client" or
+    # "del-client'.  These get pushed to ctdb.tdb during "update",
+    # which will generally be run once each "monitor" cycle.  In this
+    # way we avoid scalability problems with flood of persistent
+    # transactions after a "notify" when all the clients re-take their
+    # locks.
+
+    add-client)
+       # statd does not tell us to which IP the client connected so
+       # we must add it to all the IPs that we serve
+       cip="$2"
+       date=$(date '+%s')
+       # x is intentionally ignored
+       # shellcheck disable=SC2034
+       $CTDB ip -X |
+       tail -n +2 |
+       while IFS="|" read x sip node x ; do
+           [ "$node" = "$pnn" ] || continue # not us
+           key="statd-state@${sip}@${cip}"
+           echo "\"${key}\" \"${date}\"" >"$key"
+       done
+       ;;
+
+    del-client)
+       # statd does not tell us from which IP the client disconnected
+       # so we must add it to all the IPs that we serve
+       cip="$2"
+       # x is intentionally ignored
+       # shellcheck disable=SC2034
+       $CTDB ip -X |
+       tail -n +2 |
+       while IFS="|" read x sip node x ; do
+           [ "$node" = "$pnn" ] || continue # not us
+           key="statd-state@${sip}@${cip}"
+           echo "\"${key}\" \"\"" >"$key"
+       done
+       ;;
+
+    update)
+        files=$(echo statd-state@*)
+       if [ "$files" = "statd-state@*" ] ; then
+           # No files!
+           exit 0
+       fi
+       # Filter out lines for any IP addresses that are not currently
+       # hosted public IP addresses.
+       ctdb_ips=$($CTDB ip | tail -n +2)
+       sed_expr=$(echo "$ctdb_ips" |
+           awk -v pnn="$pnn" 'pnn == $2 {
+                ip = $1; gsub(/\./, "\\.", ip);
+                printf "/statd-state@%s@/p\n", ip }')
+       # Intentional multi-word expansion for multiple files
+       # shellcheck disable=SC2086
+        if sed -n "$sed_expr" $files | $CTDB ptrans "ctdb.tdb" ; then
+            rm $files
+       fi
+        ;;
+
+    notify)
+       # we must restart the lockmanager (on all nodes) so that we get
+       # a clusterwide grace period (so other clients don't take out
+       # conflicting locks through other nodes before all locks have been
+       # reclaimed)
+
+       # we need these settings to make sure that no tcp connections survive
+       # across a very fast failover/failback
+       #echo 10 > /proc/sys/net/ipv4/tcp_fin_timeout
+       #echo 0 > /proc/sys/net/ipv4/tcp_max_tw_buckets
+       #echo 0 > /proc/sys/net/ipv4/tcp_max_orphans
+
+       # Delete the notification list for statd, we don't want it to 
+       # ping any clients
+       rm -f /var/lib/nfs/statd/sm/*
+       rm -f /var/lib/nfs/statd/sm.bak/*
+
+       # we must keep a monotonically increasing state variable for the entire
+       # cluster  so state always increases when ip addresses fail from one
+       # node to another
+       # We use epoch and hope the nodes are close enough in clock.
+       # Even numbers mean service is shut down, odd numbers mean
+       # service is started.
+       # Intentionally round to an even number
+       # shellcheck disable=SC2017
+       state_even=$(( $(date '+%s') / 2 * 2))
+
+       # We must also let some time pass between stopping and
+       # restarting the lock manager.  Otherwise there is a window
+       # where the lock manager will respond "strangely" immediately
+       # after restarting it, which causes clients to fail to reclaim
+       # their locks.
+       nfs_callout_init
+       "$CTDB_NFS_CALLOUT" "stop" "nlockmgr" >/dev/null 2>&1
+        sleep 2
+       "$CTDB_NFS_CALLOUT" "start" "nlockmgr" >/dev/null 2>&1
+
+       # we now need to send out additional statd notifications to ensure
+       # that clients understand that the lockmanager has restarted.
+       # we have three cases:
+       # 1, clients that ignore the ip address the stat notification came from
+       #    and ONLY care about the 'name' in the notify packet.
+       #    these clients ONLY work with lock failover IFF that name
+       #    can be resolved into an ipaddress that matches the one used
+       #    to mount the share.  (==linux clients)
+       #    This is handled when starting lockmanager above,  but those
+       #    packets are sent from the "wrong" ip address, something linux
+       #    clients are ok with, buth other clients will barf at.
+       # 2, Some clients only accept statd packets IFF they come from the
+       #    'correct' ip address.
+       # 2a,Send out the notification using the 'correct' ip address and also
+       #    specify the 'correct' hostname in the statd packet.
+       #    Some clients require both the correct source address and also the
+       #    correct name. (these clients also ONLY work if the ip addresses
+       #    used to map the share can be resolved into the name returned in
+       #    the notify packet.)
+       # 2b,Other clients require that the source ip address of the notify
+       #    packet matches the ip address used to take out the lock.
+       #    I.e. that the correct source address is used.
+       #    These clients also require that the statd notify packet contains
+       #    the name as the ip address used when the lock was taken out.
+       #
+       # Both 2a and 2b are commonly used in lockmanagers since they maximize
+       # probability that the client will accept the statd notify packet and
+       # not just ignore it.
+       # For all IPs we serve, collect info and push to the config database
+
+       # Construct a sed expression to take catdb output and produce pairs of:
+       #   server-IP client-IP
+       # but only for the server-IPs that are hosted on this node.
+       ctdb_all_ips=$($CTDB ip all | tail -n +2)
+       sed_expr=$(echo "$ctdb_all_ips" |
+           awk -v pnn="$pnn" 'pnn == $2 {
+                ip = $1; gsub(/\./, "\\.", ip);
+                printf "s/^key.*=.*statd-state@\\(%s\\)@\\([^\"]*\\).*/\\1 \\2/p\n", ip }')
+
+       statd_state=$($CTDB catdb ctdb.tdb | sed -n "$sed_expr" | sort)
+       [ -n "$statd_state" ] || exit 0
+
+       smnotify="${CTDB_HELPER_BINDIR}/smnotify"
+       prev=""
+       echo "$statd_state" | {
+           # This all needs to be in the same command group at the
+           # end of the pipe so it doesn't get lost when the loop
+           # completes.
+           items=""
+           while read sip cip ; do
+               # Collect item to delete from the DB
+               key="statd-state@${sip}@${cip}"
+               item="\"${key}\" \"\""
+               items="${items}${items:+${nl}}${item}"
+
+               # NOTE: Consider optimising smnotify to read all the
+               # data from stdin and then run it in the background.
+
+               # Reset stateval for each serverip
+               [ "$sip" = "$prev" ] || stateval="$state_even"
+               # Send notifies for server shutdown
+               "$smnotify" --client="$cip" --ip="$sip" \
+                           --server="$sip" --stateval="$stateval"
+               "$smnotify" --client="$cip" --ip="$sip" \
+                           --server="$NFS_HOSTNAME" --stateval="$stateval"
+               # Send notifies for server startup
+               stateval=$((stateval + 1))
+               "$smnotify" --client="$cip" --ip="$sip" \
+                           --server="$sip" --stateval="$stateval"
+               "$smnotify" --client="$cip" --ip="$sip" \
+                           --server="$NFS_HOSTNAME" --stateval="$stateval"
+           done
+
+           echo "$items" | $CTDB ptrans "ctdb.tdb"
+       }
+
+       # Remove any stale touch files (i.e. for IPs not currently
+       # hosted on this node and created since the last "update").
+       # There's nothing else we can do with them at this stage.
+       echo "$ctdb_all_ips" |
+           awk -v pnn="$pnn" 'pnn != $2 { print $1 }' |
+           while read sip ; do
+               rm -f "statd-state@${sip}@"*
+           done
+       ;;
+esac
diff --git a/default/winbind b/default/winbind
new file mode 100644 (file)
index 0000000..3ef6e88
--- /dev/null
@@ -0,0 +1,11 @@
+# Defaults for winbind initscript
+# sourced by /etc/init.d/winbind
+#
+
+#
+# This is a POSIX shell fragment
+#
+
+
+# Winbind configuration
+#WINBINDD_OPTS="-n"
diff --git a/group b/group
index 8f52e69adc59fddcd88c1ea36f35a4faa583d73a..7b66700849a56fb556e7bc80fe4f549094e3f338 100644 (file)
--- a/group
+++ b/group
@@ -77,3 +77,4 @@ libvirt:x:136:frank
 libvirt-qemu:x:64055:libvirt-qemu
 libvirt-dnsmasq:x:137:
 mysql:x:138:
+winbindd_priv:x:139:
diff --git a/group- b/group-
index a952acc71288531192f5b194bb635770c5e39aab..8f52e69adc59fddcd88c1ea36f35a4faa583d73a 100644 (file)
--- a/group-
+++ b/group-
@@ -76,3 +76,4 @@ gdm:x:118:
 libvirt:x:136:frank
 libvirt-qemu:x:64055:libvirt-qemu
 libvirt-dnsmasq:x:137:
+mysql:x:138:
diff --git a/gshadow b/gshadow
index a6adaca3198977fc293463455e82dbab7333c099..abef6e4a0601da90f6eedd22667d845a7fbb9b43 100644 (file)
--- a/gshadow
+++ b/gshadow
@@ -77,3 +77,4 @@ libvirt:!::frank
 libvirt-qemu:!::libvirt-qemu
 libvirt-dnsmasq:!::
 mysql:!::
+winbindd_priv:!::
index 4384bceefee747f3f1d241d2baad5cf380d181b9..a6adaca3198977fc293463455e82dbab7333c099 100644 (file)
--- a/gshadow-
+++ b/gshadow-
@@ -76,3 +76,4 @@ gdm:!::
 libvirt:!::frank
 libvirt-qemu:!::libvirt-qemu
 libvirt-dnsmasq:!::
+mysql:!::
diff --git a/init.d/ctdb b/init.d/ctdb
new file mode 100755 (executable)
index 0000000..b9c0a6d
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+
+# Start and stop CTDB (Clustered TDB daemon)
+#
+# chkconfig: - 90 01
+#
+# description: Starts and stops CTDB
+# pidfile: /var/run/ctdb/ctdbd.pid
+# config: /etc/sysconfig/ctdb
+
+### BEGIN INIT INFO
+# Provides:            ctdb
+# Required-Start:      $local_fs $syslog $network $remote_fs
+# Required-Stop:       $local_fs $syslog $network $remote_fs
+# Default-Start:       2 3 4 5
+# Default-Stop:        0 1 6
+# Short-Description:   start and stop ctdb service
+# Description:         Start and stop CTDB (Clustered TDB daemon)
+### END INIT INFO
+
+# Source function library.
+if [ -f /etc/init.d/functions ] ; then
+    # Red Hat
+    . /etc/init.d/functions
+elif [ -f /etc/rc.d/init.d/functions ] ; then
+    # Red Hat
+    . /etc/rc.d/init.d/functions
+elif [ -f /etc/rc.status ] ; then
+    # SUSE
+    . /etc/rc.status
+    rc_reset
+    LC_ALL=en_US.UTF-8
+elif [ -f /lib/lsb/init-functions ] ; then
+    # Debian
+    . /lib/lsb/init-functions
+fi
+
+# Avoid using root's TMPDIR
+unset TMPDIR
+
+[ -n "$CTDB_BASE" ] || export CTDB_BASE="/etc/ctdb"
+
+. "${CTDB_BASE}/functions"
+loadconfig "network"
+loadconfig "ctdb"
+
+# check networking is up (for redhat)
+if [ "$NETWORKING" = "no" ] ; then
+    exit 0
+fi
+
+detect_init_style
+export CTDB_INIT_STYLE
+
+ctdbd="${CTDBD:-/usr/sbin/ctdbd}"
+ctdbd_wrapper="${CTDBD_WRAPPER:-/usr/sbin/ctdbd_wrapper}"
+pidfile="${CTDB_PIDFILE:-/var/run/ctdb/ctdbd.pid}"
+
+############################################################
+
+start()
+{
+    printf "Starting ctdbd service: "
+
+    case "$CTDB_INIT_STYLE" in
+       suse)
+           startproc \
+               "$ctdbd_wrapper" "$pidfile" "start"
+           rc_status -v
+           ;;
+       redhat)
+           daemon --pidfile "$pidfile" \
+               "$ctdbd_wrapper" "$pidfile" "start"
+           RETVAL=$?
+           echo
+           [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ctdb || RETVAL=1
+           return $RETVAL
+           ;;
+       debian)
+           eval start-stop-daemon --start --quiet --background --exec \
+               "$ctdbd_wrapper" "$pidfile" "start"
+           ;;
+    esac
+}
+
+stop()
+{
+    printf "Shutting down ctdbd service: "
+
+    case "$CTDB_INIT_STYLE" in
+       suse)
+           "$ctdbd_wrapper" "$pidfile" "stop"
+           rc_status -v
+           ;;
+       redhat)
+           "$ctdbd_wrapper" "$pidfile" "stop"
+           RETVAL=$?
+           # Common idiom in Red Hat init scripts - success() always
+           # succeeds so this does behave like if-then-else
+           # shellcheck disable=SC2015
+            [ $RETVAL -eq 0 ] && success || failure
+           echo ""
+           [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ctdb
+           return $RETVAL
+           ;;
+       debian)
+           "$ctdbd_wrapper" "$pidfile" "stop"
+           log_end_msg $?
+           ;;
+    esac
+}
+
+restart()
+{
+    stop
+    start
+}
+
+check_status ()
+{
+    # Backward compatibility.  When we arrange to pass --pidfile to
+    # ctdbd we also create the directory that will contain it.  If
+    # that directory is missing then we don't use the pidfile to check
+    # status.  Note that this probably won't work if
+    # $CTDB_VALGRIND="yes" but this doesn't need full backward
+    # compatibility because it is a debug option.
+    _d=$(dirname "$pidfile")
+    if [ -d "$_d" ] ; then
+       _pf_opt="-p $pidfile"
+    else
+       _pf_opt=""
+    fi
+
+    case "$CTDB_INIT_STYLE" in
+       suse)
+           checkproc $_pf_opt "$ctdbd"
+           rc_status -v
+           ;;
+       redhat)
+           status $_pf_opt -l "ctdb" "$ctdbd"
+           ;;
+       debian)
+           status_of_proc $_pf_opt "$ctdbd" "ctdb"
+           ;;
+    esac
+}
+
+############################################################
+
+case "$1" in
+    start)
+       start
+       ;;
+    stop)
+       stop
+       ;;
+    restart|reload|force-reload)
+       restart
+       ;;
+    status)
+       check_status
+       ;;
+    condrestart|try-restart)
+       if check_status >/dev/null ; then
+           restart
+       fi
+       ;;
+    cron)
+       # used from cron to auto-restart ctdb
+       check_status >/dev/null 2>&1 || restart
+       ;;
+    *)
+       echo "Usage: $0 {start|stop|restart|reload|force-reload|status|cron|condrestart|try-restart}"
+       exit 1
+esac
diff --git a/init.d/nmbd b/init.d/nmbd
new file mode 100755 (executable)
index 0000000..c9105ef
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          nmbd
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $network $local_fs $remote_fs
+# X-Start-Before:    smbd
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Samba NetBIOS nameserver (nmbd)
+# Description: NetBIOS name server to provide NetBIOS over IP naming services
+#              to clients
+### END INIT INFO
+
+
+PIDDIR=/var/run/samba
+NMBDPID=$PIDDIR/nmbd.pid
+
+# clear conflicting settings from the environment
+unset TMPDIR
+
+# See if the daemons are there
+test -x /usr/sbin/nmbd || exit 0
+
+. /lib/lsb/init-functions
+
+case $1 in
+       start)
+               SERVER_ROLE=`samba-tool testparm --parameter-name="server role"  2>/dev/null | tail -1`
+               if [ "$SERVER_ROLE" = "active directory domain controller" ]; then
+                   exit 0
+               fi
+
+               if [ -n `which testparm` ]
+               then
+                       NMBD_DISABLED=`testparm -s --parameter-name='disable netbios' 2>/dev/null`
+               fi
+               if [ "$NMBD_DISABLED" != Yes ]; then
+                       log_daemon_msg "Starting NetBIOS name server" nmbd
+                       # Make sure we have our PIDDIR, even if it's on a tmpfs
+                       install -o root -g root -m 755 -d $PIDDIR
+
+                       if ! start-stop-daemon --start --quiet --oknodo --exec /usr/sbin/nmbd --pidfile $NMBDPID -- -D
+                       then
+                               log_end_msg 1
+                               exit 1
+                       fi
+                       log_end_msg 0
+               fi
+
+               ;;
+       stop)
+
+               log_daemon_msg "Stopping NetBIOS name server" nmbd
+
+               start-stop-daemon --stop --quiet --pidfile $NMBDPID
+               # Wait a little and remove stale PID file
+               sleep 1
+               if [ -f $NMBDPID ] && ! ps h `cat $NMBDPID` > /dev/null
+               then
+                       # Stale PID file (nmbd was succesfully stopped),
+                       # remove it (should be removed by nmbd itself IMHO.)
+                       rm -f $NMBDPID
+               fi
+
+               log_end_msg 0
+
+               ;;
+       restart|force-reload)
+               $0 stop
+               sleep 1
+               $0 start
+               ;;
+        status)
+               status_of_proc -p $NMBDPID /usr/sbin/nmbd nmbd
+               exit $?
+               ;;
+       *)
+               echo "Usage: /etc/init.d/nmbd {start|stop|restart|force-reload|status}"
+               exit 1
+               ;;
+esac
+
+exit 0
diff --git a/init.d/samba-ad-dc b/init.d/samba-ad-dc
new file mode 100755 (executable)
index 0000000..ba4a7d0
--- /dev/null
@@ -0,0 +1,93 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides:          samba-ad-dc
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $network $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Samba daemons for the AD DC
+# Description:  Meta-service to provide AD and SMB/CIFS services to clients
+### END INIT INFO
+
+#
+# Start/stops the Samba daemon (samba).
+# Adapted from the Samba 3 packages.
+#
+
+PIDDIR=/var/run/samba
+SAMBAPID=$PIDDIR/samba.pid
+
+# clear conflicting settings from the environment
+unset TMPDIR
+
+# See if the daemon and the config file are there
+test -x /usr/sbin/samba -a -r /etc/samba/smb.conf || exit 0
+
+. /lib/lsb/init-functions
+
+case "$1" in
+       start)
+               SERVER_ROLE=`samba-tool testparm --parameter-name="server role"  2>/dev/null | tail -1`
+               if [ "$SERVER_ROLE" != "active directory domain controller" ]; then
+                   exit 0
+               fi
+
+
+               # CVE-2013-4475
+               KEYFILE=/var/lib/samba/private/tls/key.pem
+               if [ -e $KEYFILE ]
+               then
+                               KEYPERMS=`stat -c %a $KEYFILE`
+                               if [ "$KEYPERMS" != "600" ]
+                               then
+                                               echo "wrong permission on $KEYFILE, must be 600"
+                                               echo "samba will not start (CVE-2013-4475)"
+                                               echo "Removing all tls .pem files will cause an auto-regeneration with the correct permissions."
+                                               exit 1
+                               fi
+               fi
+
+               log_daemon_msg "Starting Samba AD DC daemon" "samba"
+               # Make sure we have our PIDDIR, even if it's on a tmpfs
+               install -o root -g root -m 755 -d $PIDDIR
+
+               if ! start-stop-daemon --start --quiet --oknodo --exec /usr/sbin/samba --pidfile $SAMBAPID -- -D; then
+                       log_end_msg 1
+                       exit 1
+               fi
+
+               log_end_msg 0
+               ;;
+       stop)
+               log_daemon_msg "Stopping Samba AD DC daemon" "samba"
+
+               start-stop-daemon --stop --quiet --pidfile $SAMBAPID
+               # Wait a little and remove stale PID file
+               sleep 1
+               if [ -f $SAMBAPID ] && ! ps h `cat $SAMBAPID` > /dev/null
+               then
+                       # Stale PID file (samba was succesfully stopped),
+                       # remove it (should be removed by samba itself IMHO.)
+                       rm -f $SAMBAPID
+               fi
+
+               log_end_msg 0
+
+               ;;
+       restart|force-reload)
+               $0 stop
+               sleep 1
+               $0 start
+               ;;
+       status)
+               status_of_proc -p $SAMBAPID /usr/sbin/samba samba
+               exit $?
+               ;;
+       *)
+               echo "Usage: /etc/init.d/samba-ad-dc {start|stop|restart|force-reload|status}"
+               exit 1
+               ;;
+esac
+
+exit 0
diff --git a/init.d/smbd b/init.d/smbd
new file mode 100755 (executable)
index 0000000..41b6e11
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          smbd
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $network $local_fs $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Should-Start:      slapd cups
+# Should-Stop:       slapd cups
+# Short-Description: Samba SMB/CIFS daemon (smbd)
+# Description: server to provide SMB/CIFS services to clients
+### END INIT INFO
+
+
+PIDDIR=/var/run/samba
+SMBDPID=$PIDDIR/smbd.pid
+
+# clear conflicting settings from the environment
+unset TMPDIR
+
+# See if the daemons are there
+test -x /usr/sbin/smbd || exit 0
+
+. /lib/lsb/init-functions
+
+case $1 in
+       start)
+               SERVER_ROLE=`samba-tool testparm --parameter-name="server role"  2>/dev/null | tail -1`
+               if [ "$SERVER_ROLE" = "active directory domain controller" ]; then
+                   exit 0
+               fi
+
+               log_daemon_msg "Starting SMB/CIFS daemon" smbd
+               # Make sure we have our PIDDIR, even if it's on a tmpfs
+               install -o root -g root -m 755 -d $PIDDIR
+
+               if ! start-stop-daemon --start --quiet --oknodo --exec /usr/sbin/smbd --pidfile $SMBDPID -- -D; then
+                       log_end_msg 1
+                       exit 1
+               fi
+
+               log_end_msg 0
+               ;;
+       stop)
+
+               log_daemon_msg "Stopping SMB/CIFS daemon" smbd
+
+               start-stop-daemon --stop --quiet --pidfile $SMBDPID
+               # Wait a little and remove stale PID file
+               sleep 1
+               if [ -f $SMBDPID ] && ! ps h `cat $SMBDPID` > /dev/null
+               then
+                       # Stale PID file, remove it (should be removed by
+                       # smbd itself IMHO).
+                       rm -f $SMBDPID
+               fi
+
+               log_end_msg 0
+
+               ;;
+       reload)
+               log_daemon_msg "Reloading /etc/samba/smb.conf" smbd
+
+               start-stop-daemon --stop --quiet --signal HUP --pidfile $SMBDPID
+
+               log_end_msg 0
+               ;;
+       restart|force-reload)
+               $0 stop
+               sleep 1
+               $0 start
+               ;;
+        status)
+               status_of_proc -p $SMBDPID /usr/sbin/smbd smbd
+               exit $?
+               ;;
+       *)
+               echo "Usage: /etc/init.d/smbd {start|stop|reload|restart|force-reload|status}"
+               exit 1
+               ;;
+esac
+
+exit 0
diff --git a/init.d/winbind b/init.d/winbind
new file mode 100755 (executable)
index 0000000..717d1f8
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          winbind
+# Required-Start:    $network $remote_fs $syslog
+# Required-Stop:     $network $remote_fs $syslog
+# Should-Start:      samba
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Samba Winbind daemon
+# Description: Name Service Switch daemon for resolving names from NT servers
+### END INIT INFO
+
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+
+[ -r /etc/default/winbind ] && . /etc/default/winbind
+
+DAEMON=/usr/sbin/winbindd
+PIDDIR=/var/run/samba
+WINBINDPID=$PIDDIR/winbindd.pid
+
+# clear conflicting settings from the environment
+unset TMPDIR
+
+# See if the daemon is there
+test -x $DAEMON || exit 0
+
+SERVER_ROLE=`samba-tool testparm --parameter-name="server role"  2>/dev/null | tail -1`
+if [ "$SERVER_ROLE" = "active directory domain controller" ]; then
+    exit 0
+fi
+
+. /lib/lsb/init-functions
+
+case "$1" in
+       start)
+               log_daemon_msg "Starting the Winbind daemon" "winbind"
+
+               start-stop-daemon --start --quiet --oknodo --exec $DAEMON --pidfile $WINBINDPID -- $WINBINDD_OPTS
+
+               log_end_msg $?
+               ;;
+
+       stop)
+               log_daemon_msg "Stopping the Winbind daemon" "winbind"
+               start-stop-daemon --stop --quiet --oknodo --exec $DAEMON --pidfile $WINBINDPID
+               log_end_msg $?
+               ;;
+
+       restart|force-reload)
+               $0 stop && sleep 2 && $0 start
+               ;;
+
+       status)
+               status_of_proc -p $WINBINDPID $DAEMON winbind && exit 0 || exit $?
+               ;;
+       *)
+               echo "Usage: /etc/init.d/winbind {start|stop|restart|force-reload|status}"
+               exit 1
+               ;;
+esac
diff --git a/logrotate.d/ctdb b/logrotate.d/ctdb
new file mode 100644 (file)
index 0000000..c1bf28a
--- /dev/null
@@ -0,0 +1,9 @@
+/var/log/ctdb/log.ctdb {
+       weekly
+       missingok
+       rotate 7
+       copytruncate
+       compress
+       delaycompress
+       notifempty
+}
diff --git a/logrotate.d/samba b/logrotate.d/samba
new file mode 100644 (file)
index 0000000..8a9632b
--- /dev/null
@@ -0,0 +1,40 @@
+/var/log/samba/log.smbd {
+       weekly
+       missingok
+       rotate 7
+       postrotate
+               [ ! -x /usr/bin/smbcontrol ] || /usr/bin/smbcontrol smbd reload-config
+       endscript
+       compress
+       delaycompress
+       notifempty
+}
+
+/var/log/samba/log.nmbd {
+       weekly
+       missingok
+       rotate 7
+       postrotate
+               [ ! -x /usr/bin/smbcontrol ] || /usr/bin/smbcontrol nmbd reload-config
+       endscript
+       compress
+       delaycompress
+       notifempty
+}
+
+/var/log/samba/log.samba {
+       weekly
+       missingok
+       rotate 7
+       postrotate
+               if [ -d /run/systemd/system ] && command systemctl >/dev/null 2>&1 && systemctl is-active --quiet samba-ad-dc; then
+                       systemctl kill --kill-who all --signal=SIGHUP samba-ad-dc
+               elif [ -f /var/run/samba/samba.pid ]; then
+                       # This only sends to main pid, See #803924
+                       kill -HUP `cat /var/run/samba/samba.pid`
+               fi
+       endscript
+       compress
+       delaycompress
+       notifempty
+}
diff --git a/logrotate.d/winbind b/logrotate.d/winbind
new file mode 100644 (file)
index 0000000..43ae1af
--- /dev/null
@@ -0,0 +1,15 @@
+/var/log/samba/log.winbindd {
+       weekly
+       missingok
+       rotate 7
+       postrotate
+               if [ -x /usr/bin/smbcontrol ]; then
+                       /usr/bin/smbcontrol winbindd reload-config
+               elif [ -f /var/run/samba/winbindd.pid ]; then
+                       kill -HUP `cat /var/run/samba/winbindd.pid`
+               fi
+       endscript
+       compress
+       delaycompress
+       notifempty
+}
diff --git a/rc0.d/K01ctdb b/rc0.d/K01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc0.d/K01nmbd b/rc0.d/K01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc0.d/K01samba-ad-dc b/rc0.d/K01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc0.d/K01smbd b/rc0.d/K01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc0.d/K01winbind b/rc0.d/K01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc1.d/K01ctdb b/rc1.d/K01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc1.d/K01nmbd b/rc1.d/K01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc1.d/K01samba-ad-dc b/rc1.d/K01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc1.d/K01smbd b/rc1.d/K01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc1.d/K01winbind b/rc1.d/K01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc2.d/S01ctdb b/rc2.d/S01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc2.d/S01nmbd b/rc2.d/S01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc2.d/S01samba-ad-dc b/rc2.d/S01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc2.d/S01smbd b/rc2.d/S01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc2.d/S01winbind b/rc2.d/S01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc3.d/S01ctdb b/rc3.d/S01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc3.d/S01nmbd b/rc3.d/S01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc3.d/S01samba-ad-dc b/rc3.d/S01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc3.d/S01smbd b/rc3.d/S01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc3.d/S01winbind b/rc3.d/S01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc4.d/S01ctdb b/rc4.d/S01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc4.d/S01nmbd b/rc4.d/S01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc4.d/S01samba-ad-dc b/rc4.d/S01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc4.d/S01smbd b/rc4.d/S01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc4.d/S01winbind b/rc4.d/S01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc5.d/S01ctdb b/rc5.d/S01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc5.d/S01nmbd b/rc5.d/S01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc5.d/S01samba-ad-dc b/rc5.d/S01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc5.d/S01smbd b/rc5.d/S01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc5.d/S01winbind b/rc5.d/S01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/rc6.d/K01ctdb b/rc6.d/K01ctdb
new file mode 120000 (symlink)
index 0000000..659fb4e
--- /dev/null
@@ -0,0 +1 @@
+../init.d/ctdb
\ No newline at end of file
diff --git a/rc6.d/K01nmbd b/rc6.d/K01nmbd
new file mode 120000 (symlink)
index 0000000..1d81ff4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/nmbd
\ No newline at end of file
diff --git a/rc6.d/K01samba-ad-dc b/rc6.d/K01samba-ad-dc
new file mode 120000 (symlink)
index 0000000..fd0c8b9
--- /dev/null
@@ -0,0 +1 @@
+../init.d/samba-ad-dc
\ No newline at end of file
diff --git a/rc6.d/K01smbd b/rc6.d/K01smbd
new file mode 120000 (symlink)
index 0000000..a691ef6
--- /dev/null
@@ -0,0 +1 @@
+../init.d/smbd
\ No newline at end of file
diff --git a/rc6.d/K01winbind b/rc6.d/K01winbind
new file mode 120000 (symlink)
index 0000000..132ebe4
--- /dev/null
@@ -0,0 +1 @@
+../init.d/winbind
\ No newline at end of file
diff --git a/sudoers.d/ctdb b/sudoers.d/ctdb
new file mode 100644 (file)
index 0000000..1c6619b
--- /dev/null
@@ -0,0 +1,3 @@
+Defaults!/etc/ctdb/statd-callout       !requiretty
+
+rpcuser                ALL=(ALL)       NOPASSWD: /etc/ctdb/statd-callout
diff --git a/systemd/system/multi-user.target.wants/ctdb.service b/systemd/system/multi-user.target.wants/ctdb.service
new file mode 120000 (symlink)
index 0000000..2378aa0
--- /dev/null
@@ -0,0 +1 @@
+/lib/systemd/system/ctdb.service
\ No newline at end of file
diff --git a/systemd/system/multi-user.target.wants/nmbd.service b/systemd/system/multi-user.target.wants/nmbd.service
new file mode 120000 (symlink)
index 0000000..d99ad38
--- /dev/null
@@ -0,0 +1 @@
+/lib/systemd/system/nmbd.service
\ No newline at end of file
diff --git a/systemd/system/multi-user.target.wants/smbd.service b/systemd/system/multi-user.target.wants/smbd.service
new file mode 120000 (symlink)
index 0000000..8f4dc75
--- /dev/null
@@ -0,0 +1 @@
+/lib/systemd/system/smbd.service
\ No newline at end of file
diff --git a/systemd/system/multi-user.target.wants/winbind.service b/systemd/system/multi-user.target.wants/winbind.service
new file mode 120000 (symlink)
index 0000000..f492fd5
--- /dev/null
@@ -0,0 +1 @@
+/lib/systemd/system/winbind.service
\ No newline at end of file
diff --git a/systemd/system/samba-ad-dc.service b/systemd/system/samba-ad-dc.service
new file mode 120000 (symlink)
index 0000000..dc1dc0c
--- /dev/null
@@ -0,0 +1 @@
+/dev/null
\ No newline at end of file
diff --git a/ufw/applications.d/samba b/ufw/applications.d/samba
new file mode 100644 (file)
index 0000000..37bcfe2
--- /dev/null
@@ -0,0 +1,4 @@
+[Samba]
+title=LanManager-like file and printer server for Unix
+description=The Samba software suite is a collection of programs that implements the SMB/CIFS protocol for unix systems, allowing you to serve files and printers to Windows, NT, OS/2 and DOS clients. This protocol is sometimes also referred to as the LanManager or NetBIOS protocol.
+ports=137,138/udp|139,445/tcp