summaryrefslogtreecommitdiffstats
path: root/ctdb/tests/UNIT/eventscripts/stubs
diff options
context:
space:
mode:
Diffstat (limited to 'ctdb/tests/UNIT/eventscripts/stubs')
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb481
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb-config2
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs53
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw34
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/date7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/df38
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ethtool12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/exportfs13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/gstack19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/id3
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ip833
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ip6tables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/iptables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ipvsadm154
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/kill7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/killall7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/multipath36
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/net5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/nfsconf5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/pidof17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/pkill7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ps48
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rm6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.lockd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.mountd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.statd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpcinfo78
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/service65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/sleep9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/smnotify65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ss206
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/stat71
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdbdump9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdbtool36
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/testparm84
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/timeout8
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/wbinfo7
41 files changed, 2494 insertions, 0 deletions
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb b/ctdb/tests/UNIT/eventscripts/stubs/ctdb
new file mode 100755
index 0000000..20135eb
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb
@@ -0,0 +1,481 @@
+#!/bin/sh
+
+prog="ctdb"
+
+# Print a message and exit.
+die()
+{
+ echo "$1" >&2
+ exit "${2:-1}"
+}
+
+not_implemented_exit_code=1
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $prog [-X] cmd
+
+A fake CTDB stub that prints items depending on the variables
+FAKE_CTDB_PNN (default 0) depending on command-line options.
+EOF
+ exit 1
+}
+
+not_implemented()
+{
+ echo "${prog}: command \"$1\" not implemented in stub" >&2
+ exit $not_implemented_exit_code
+}
+
+verbose=false
+machine_readable=false
+nodespec=""
+
+args=""
+
+# Options and command argument can appear in any order, so when
+# getopts thinks it is done, process any non-option arguments and go
+# around again.
+while [ $# -gt 0 ]; do
+ while getopts "Xvhn:?" opt; do
+ case "$opt" in
+ X) machine_readable=true ;;
+ v) verbose=true ;;
+ n) nodespec="$OPTARG" ;;
+ \? | *) usage ;;
+ esac
+ done
+ shift $((OPTIND - 1))
+
+ # Anything left over must be a non-option arg
+ if [ $# -gt 0 ]; then
+ args="${args}${args:+ }${1}"
+ shift
+ fi
+done
+
+[ -n "$args" ] || usage
+# Want word splitting
+# shellcheck disable=SC2086
+set -- $args
+
+setup_tickles()
+{
+ # Make sure tickles file exists.
+ tickles_file="${CTDB_TEST_TMP_DIR}/fake-ctdb/tickles"
+ mkdir -p "$(dirname "$tickles_file")"
+ touch "$tickles_file"
+}
+
+ctdb_gettickles()
+{
+ _ip="$1"
+ _port="$2"
+
+ setup_tickles
+
+ echo "|source ip|port|destination ip|port|"
+ while read -r _src _dst; do
+ if [ -z "$_ip" ] || [ "$_ip" = "${_dst%:*}" ]; then
+ if [ -z "$_port" ] || [ "$_port" = "${_dst##*:}" ]; then
+ echo "|${_src%:*}|${_src##*:}|${_dst%:*}|${_dst##*:}|"
+ fi
+ fi
+ done <"$tickles_file"
+}
+
+ctdb_addtickle()
+{
+ _src="$1"
+ _dst="$2"
+
+ setup_tickles
+
+ if [ -n "$_dst" ]; then
+ echo "${_src} ${_dst}" >>"$tickles_file"
+ else
+ cat >>"$tickles_file"
+ fi
+}
+
+ctdb_deltickle()
+{
+ _src="$1"
+ _dst="$2"
+
+ setup_tickles
+
+ if [ -n "$_dst" ]; then
+ _t=$(grep -F -v "${_src} $${_dst}" "$tickles_file")
+ else
+ _t=$(cat "$tickles_file")
+ while read -r _src _dst; do
+ _t=$(echo "$_t" | grep -F -v "${_src} ${_dst}")
+ done
+ fi
+ echo "$_t" >"$tickles_file"
+}
+
+parse_nodespec()
+{
+ if [ "$nodespec" = "all" ]; then
+ nodes="$(seq 0 $((FAKE_CTDB_NUMNODES - 1)))"
+ elif [ -n "$nodespec" ]; then
+ nodes="$(echo "$nodespec" | sed -e 's@,@ @g')"
+ else
+ nodes=$(ctdb_pnn)
+ fi
+}
+
+# For testing backward compatibility...
+for i in $CTDB_NOT_IMPLEMENTED; do
+ if [ "$i" = "$1" ]; then
+ not_implemented "$i"
+ fi
+done
+
+ctdb_pnn()
+{
+ # Defaults to 0
+ echo "${FAKE_CTDB_PNN:-0}"
+}
+
+######################################################################
+
+FAKE_CTDB_NODE_STATE="$FAKE_CTDB_STATE/node-state"
+FAKE_CTDB_NODES_DISABLED="$FAKE_CTDB_NODE_STATE/0x4"
+
+######################################################################
+
+# NOTE: all nodes share public addresses file
+
+FAKE_CTDB_IP_LAYOUT="$FAKE_CTDB_STATE/ip-layout"
+
+ip_reallocate()
+{
+ touch "$FAKE_CTDB_IP_LAYOUT"
+
+ # ShellCheck doesn't understand this flock pattern
+ # shellcheck disable=SC2094
+ (
+ flock 0
+
+ _pa="${CTDB_BASE}/public_addresses"
+
+ if [ ! -s "$FAKE_CTDB_IP_LAYOUT" ]; then
+ sed -n -e 's@^\([^#][^/]*\)/.*@\1 -1@p' \
+ "$_pa" >"$FAKE_CTDB_IP_LAYOUT"
+ fi
+
+ _t="${FAKE_CTDB_IP_LAYOUT}.new"
+
+ _flags=""
+ for _i in $(seq 0 $((FAKE_CTDB_NUMNODES - 1))); do
+ if ls "$FAKE_CTDB_STATE/node-state/"*"/$_i" >/dev/null 2>&1; then
+ # Have non-zero flags
+ _this=0
+ for _j in "$FAKE_CTDB_STATE/node-state/"*"/$_i"; do
+ _tf="${_j%/*}" # dirname
+ _f="${_tf##*/}" # basename
+ _this=$((_this | _f))
+ done
+ else
+ _this="0"
+ fi
+ _flags="${_flags}${_flags:+,}${_this}"
+ done
+ CTDB_TEST_LOGLEVEL=NOTICE \
+ "ctdb_takeover_tests" \
+ "ipalloc" "$_flags" <"$FAKE_CTDB_IP_LAYOUT" |
+ sort >"$_t"
+ mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
+ ) <"$FAKE_CTDB_IP_LAYOUT"
+}
+
+ctdb_ip()
+{
+ # If nobody has done any IP-fu then generate a layout.
+ [ -f "$FAKE_CTDB_IP_LAYOUT" ] || ip_reallocate
+
+ _mypnn=$(ctdb_pnn)
+
+ if $machine_readable; then
+ if $verbose; then
+ echo "|Public IP|Node|ActiveInterface|AvailableInterfaces|ConfiguredInterfaces|"
+ else
+ echo "|Public IP|Node|"
+ fi
+ else
+ echo "Public IPs on node ${_mypnn}"
+ fi
+
+ # Join public addresses file with $FAKE_CTDB_IP_LAYOUT, and
+ # process output line by line...
+ _pa="${CTDB_BASE}/public_addresses"
+ sed -e 's@/@ @' "$_pa" | sort | join - "$FAKE_CTDB_IP_LAYOUT" |
+ while read -r _ip _ _ifaces _pnn; do
+ if $verbose; then
+ # If more than 1 interface, assume all addresses are on the 1st.
+ _first_iface="${_ifaces%%,*}"
+ # Only show interface if address is on this node.
+ _my_iface=""
+ if [ "$_pnn" = "$_mypnn" ]; then
+ _my_iface="$_first_iface"
+ fi
+ if $machine_readable; then
+ echo "|${_ip}|${_pnn}|${_my_iface}|${_first_iface}|${_ifaces}|"
+ else
+ echo "${_ip} node[${_pnn}] active[${_my_iface}] available[${_first_iface}] configured[[${_ifaces}]"
+ fi
+ else
+ if $machine_readable; then
+ echo "|${_ip}|${_pnn}|"
+ else
+ echo "${_ip} ${_pnn}"
+ fi
+ fi
+ done
+}
+
+ctdb_moveip()
+{
+ _ip="$1"
+ _target="$2"
+
+ ip_reallocate # should be harmless and ensures we have good state
+
+ # ShellCheck doesn't understand this flock pattern
+ # shellcheck disable=SC2094
+ (
+ flock 0
+
+ _t="${FAKE_CTDB_IP_LAYOUT}.new"
+
+ while read -r _i _pnn; do
+ if [ "$_ip" = "$_i" ]; then
+ echo "$_i $_target"
+ else
+ echo "$_i $_pnn"
+ fi
+ done | sort >"$_t"
+ mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
+ ) <"$FAKE_CTDB_IP_LAYOUT"
+}
+
+######################################################################
+
+ctdb_enable()
+{
+ parse_nodespec
+
+ for _i in $nodes; do
+ rm -f "${FAKE_CTDB_NODES_DISABLED}/${_i}"
+ done
+
+ ip_reallocate
+}
+
+ctdb_disable()
+{
+ parse_nodespec
+
+ for _i in $nodes; do
+ mkdir -p "$FAKE_CTDB_NODES_DISABLED"
+ touch "${FAKE_CTDB_NODES_DISABLED}/${_i}"
+ done
+
+ ip_reallocate
+}
+
+######################################################################
+
+ctdb_shutdown()
+{
+ echo "CTDB says BYE!"
+}
+
+######################################################################
+
+# This is only used by the NAT and LVS gateway code at the moment, so
+# use a hack. Assume that $CTDB_NATGW_NODES or $CTDB_LVS_NODES
+# contains all nodes in the cluster (which is what current tests
+# assume). Use the PNN to find the address from this file. The NAT
+# gateway code only used the address, so just mark the node healthy.
+ctdb_nodestatus()
+{
+ echo '|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode|'
+ _line=$((FAKE_CTDB_PNN + 1))
+ _ip=$(sed -e "${_line}p" "${CTDB_NATGW_NODES:-${CTDB_LVS_NODES}}")
+ echo "|${FAKE_CTDB_PNN}|${_ip}|0|0|0|0|0|0|0|Y|"
+}
+
+######################################################################
+
+_t_setup()
+{
+ _t_dir="${CTDB_TEST_TMP_DIR}/fake-ctdb/fake-tdb/$1"
+ mkdir -p "$_t_dir"
+}
+
+_t_put()
+{
+ echo "$2" >"${_t_dir}/$1"
+}
+
+_t_get()
+{
+ cat "${_t_dir}/$1"
+}
+
+_t_del()
+{
+ rm -f "${_t_dir}/$1"
+}
+
+ctdb_pstore()
+{
+ _t_setup "$1"
+ _t_put "$2" "$3"
+}
+
+ctdb_pdelete()
+{
+ _t_setup "$1"
+ _t_del "$2"
+}
+
+ctdb_pfetch()
+{
+ _t_setup "$1"
+ _t_get "$2" >"$3" 2>/dev/null
+}
+
+ctdb_ptrans()
+{
+ _t_setup "$1"
+
+ while IFS="" read -r _line; do
+ _k=$(echo "$_line" | sed -n -e 's@^"\([^"]*\)" "[^"]*"$@\1@p')
+ _v=$(echo "$_line" | sed -e 's@^"[^"]*" "\([^"]*\)"$@\1@')
+ [ -n "$_k" ] || die "ctdb ptrans: bad line \"${_line}\""
+ if [ -n "$_v" ]; then
+ _t_put "$_k" "$_v"
+ else
+ _t_del "$_k"
+ fi
+ done
+}
+
+ctdb_catdb()
+{
+ _t_setup "$1"
+
+ # This will break on keys with spaces but we don't have any of
+ # those yet.
+ _count=0
+ for _i in "${_t_dir}/"*; do
+ [ -r "$_i" ] || continue
+ _k="${_i##*/}" # basename
+ _v=$(_t_get "$_k")
+ _kn=$(printf '%s' "$_k" | wc -c)
+ _vn=$(printf '%s' "$_v" | wc -c)
+ cat <<EOF
+key(${_kn}) = "${_k}"
+dmaster: 0
+rsn: 1
+data(${_vn}) = "${_v}"
+
+EOF
+ _count=$((_count + 1))
+ done
+
+ echo "Dumped ${_count} records"
+}
+
+######################################################################
+
+FAKE_CTDB_IFACES_DOWN="${FAKE_CTDB_STATE}/ifaces-down"
+rm -f "${FAKE_CTDB_IFACES_DOWN}"/*
+
+ctdb_ifaces()
+{
+ _f="${CTDB_BASE}/public_addresses"
+
+ if [ ! -f "$_f" ]; then
+ die "Public addresses file \"${_f}\" not found"
+ fi
+
+ # Assume -Y.
+ echo "|Name|LinkStatus|References|"
+ while read -r _ip _iface; do
+ case "$_ip" in
+ \#*) : ;;
+ *)
+ _status=1
+ # For now assume _iface contains only 1.
+ if [ -f "{FAKE_CTDB_IFACES_DOWN}/${_iface}" ]; then
+ _status=0
+ fi
+ # Nobody looks at references
+ echo "|${_iface}|${_status}|0|"
+ ;;
+ esac
+ done <"$_f" |
+ sort -u
+}
+
+ctdb_setifacelink()
+{
+ _iface="$1"
+ _state="$2"
+
+ mkdir -p "$FAKE_CTDB_IFACES_DOWN"
+
+ # Existence of file means CTDB thinks interface is down.
+ _f="${FAKE_CTDB_IFACES_DOWN}/${_iface}"
+
+ case "$_state" in
+ up) rm -f "$_f" ;;
+ down) touch "$_f" ;;
+ *) die "ctdb setifacelink: unsupported interface status ${_state}" ;;
+ esac
+}
+
+######################################################################
+
+ctdb_checktcpport()
+{
+ _port="$1"
+
+ for _i in $FAKE_TCP_LISTEN; do
+ if [ "$_port" = "$_i" ]; then
+ exit 98
+ fi
+ done
+
+ exit 0
+}
+
+ctdb_gratarp()
+{
+ # Do nothing for now
+ :
+}
+
+######################################################################
+
+cmd="$1"
+shift
+
+func="ctdb_${cmd}"
+
+# This could inadvertently run an external function instead of a local
+# function. However, this can only happen if testing a script
+# containing a new ctdb command that is not implemented, so this is
+# unlikely to do harm.
+if type "$func" >/dev/null 2>&1; then
+ "$func" "$@"
+else
+ not_implemented "$cmd"
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config b/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config
new file mode 100755
index 0000000..818e3db
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec $VALGRIND "${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" "$@"
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp
new file mode 100755
index 0000000..2a4bac4
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Only supports reading from stdin
+
+# shellcheck disable=SC2034
+iface="$1" # ignored
+
+while read -r src dst; do
+ sed -i -e "/^${dst} ${src}\$/d" "$FAKE_NETSTAT_TCP_ESTABLISHED_FILE"
+done
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs
new file mode 100755
index 0000000..31f56e8
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+prog="ctdb_lvs"
+
+# Print a message and exit.
+die()
+{
+ echo "$1" >&2
+ exit "${2:-1}"
+}
+
+not_implemented_exit_code=1
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $prog { leader | list }
+EOF
+ exit 1
+}
+
+not_implemented()
+{
+ echo "${prog}: command \"$1\" not implemented in stub" >&2
+ exit $not_implemented_exit_code
+}
+
+ctdb_lvs_leader()
+{
+ if [ -n "$FAKE_CTDB_LVS_LEADER" ]; then
+ echo "$FAKE_CTDB_LVS_LEADER"
+ return 0
+ else
+ return 255
+ fi
+}
+
+ctdb_lvs_list()
+{
+ _pnn=0
+ while read -r _ip _; do
+ echo "${_pnn} ${_ip}"
+ _pnn=$((_pnn + 1))
+ done <"$CTDB_LVS_NODES"
+}
+
+######################################################################
+
+case "$1" in
+leader) ctdb_lvs_leader "$@" ;;
+list) ctdb_lvs_list "$@" ;;
+*) not_implemented "$1" ;;
+esac
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw
new file mode 100755
index 0000000..22a2191
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+prog="ctdb_natgw"
+
+not_implemented_exit_code=1
+
+not_implemented()
+{
+ echo "${prog}: command \"$1\" not implemented in stub" >&2
+ exit $not_implemented_exit_code
+}
+
+ctdb_natgw_leader()
+{
+ [ -r "$CTDB_NATGW_NODES" ] ||
+ die "error: missing CTDB_NATGW_NODES=${CTDB_NATGW_NODES}"
+
+ # Determine the leader node
+ _leader="-1 0.0.0.0"
+ _pnn=0
+ while read -r _ip; do
+ if [ "$FAKE_CTDB_NATGW_LEADER" = "$_ip" ]; then
+ _leader="${_pnn} ${_ip}"
+ break
+ fi
+ _pnn=$((_pnn + 1))
+ done <"$CTDB_NATGW_NODES"
+ echo "$_leader"
+}
+
+case "$1" in
+leader) ctdb_natgw_leader "$@" ;;
+*) not_implemented "$1" ;;
+esac
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/date b/ctdb/tests/UNIT/eventscripts/stubs/date
new file mode 100755
index 0000000..8319c9c
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/date
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if [ "$FAKE_DATE_OUTPUT" ]; then
+ echo "$FAKE_DATE_OUTPUT"
+else
+ /bin/date "$@"
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/df b/ctdb/tests/UNIT/eventscripts/stubs/df
new file mode 100755
index 0000000..858f0ef
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/df
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+usage()
+{
+ echo "usage: df [-kP] [<mount-point>]"
+ exit 1
+}
+
+if [ "$1" = "-kP" ]; then
+ shift
+fi
+
+case "$1" in
+-*) usage ;;
+esac
+
+fs="${1:-/}"
+
+# Anything starting with CTDB_DBDIR_BASE gets canonicalised to
+# CTDB_DBDIR_BASE. This helps with the setting of defaults for the
+# filesystem checks.
+if [ "${fs#"${CTDB_DBDIR_BASE}"}" != "$fs" ]; then
+ fs="$CTDB_DBDIR_BASE"
+fi
+
+# A default, for tests that don't initialise this...
+if [ -z "$FAKE_FS_USE" ]; then
+ FAKE_FS_USE=10
+fi
+
+echo "Filesystem 1024-blocks Used Available Capacity Mounted on"
+
+blocks="1000000"
+used=$((blocks * FAKE_FS_USE / 100))
+available=$((blocks - used))
+
+printf "%-36s %10d %10d %10d %10d%% %s\n" \
+ "/dev/sda1" "$blocks" "$used" "$available" "$FAKE_FS_USE" "$fs"
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ethtool b/ctdb/tests/UNIT/eventscripts/stubs/ethtool
new file mode 100755
index 0000000..3d4b889
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ethtool
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+link="yes"
+
+if [ -f "${FAKE_ETHTOOL_LINK_DOWN}/${1}" ]; then
+ link="no"
+fi
+
+# Expect to add more fields later.
+cat <<EOF
+ Link detected: ${link}
+EOF
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/exportfs b/ctdb/tests/UNIT/eventscripts/stubs/exportfs
new file mode 100755
index 0000000..e0970c5
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/exportfs
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+opts="10.0.0.0/16(rw,async,insecure,no_root_squash,no_subtree_check)"
+
+for i in $FAKE_SHARES; do
+ # Directories longer than 15 characters are printed on their own
+ # line.
+ if [ ${#i} -ge 15 ]; then
+ printf '%s\n\t\t%s\n' "$i" "$opts"
+ else
+ printf '%s\t%s\n' "$i" "$opts"
+ fi
+done
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/gstack b/ctdb/tests/UNIT/eventscripts/stubs/gstack
new file mode 100755
index 0000000..1dec235
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/gstack
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+pid="$1"
+
+if [ -n "$FAKE_PS_MAP" ]; then
+ command=$(echo "$FAKE_PS_MAP" |
+ awk -v pid="$pid" '$1 == pid { print $2 }')
+fi
+
+if [ -z "$command" ]; then
+ command="smbd"
+fi
+
+cat <<EOF
+Thread 1 (Thread 0x7f688fbfb180 (LWP ${pid}) "${command}"):
+#0 0x00007f688ff7a076 in open (FAKE ARGS...) at FAKE PLACE
+....
+#3 0x000055cd368ead72 in main (argc=<optimized out>, argv=<optimized out>) at ${command}.c
+EOF
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/id b/ctdb/tests/UNIT/eventscripts/stubs/id
new file mode 100755
index 0000000..1ecd2f8
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/id
@@ -0,0 +1,3 @@
+#!/bin/sh
+# Make statd-callout happy
+echo 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ip b/ctdb/tests/UNIT/eventscripts/stubs/ip
new file mode 100755
index 0000000..090afae
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ip
@@ -0,0 +1,833 @@
+#!/bin/sh
+
+FAKE_IP_STATE="${FAKE_NETWORK_STATE}/ip-state"
+mkdir -p "$FAKE_IP_STATE"
+
+promote_secondaries=true
+
+not_implemented()
+{
+ echo "ip stub command: \"$1\" not implemented"
+ exit 127
+}
+
+######################################################################
+
+ip_link()
+{
+ case "$1" in
+ set)
+ shift
+ # iface="$1"
+ case "$2" in
+ up) ip_link_set_up "$1" ;;
+ down) ip_link_down_up "$1" ;;
+ *) not_implemented "\"$2\" in \"$orig_args\"" ;;
+ esac
+ ;;
+ show)
+ shift
+ ip_link_show "$@"
+ ;;
+ add*)
+ shift
+ ip_link_add "$@"
+ ;;
+ del*)
+ shift
+ ip_link_delete "$@"
+ ;;
+ *) not_implemented "$*" ;;
+ esac
+}
+
+ip_link_add()
+{
+ _link=""
+ _name=""
+ _type=""
+
+ while [ -n "$1" ]; do
+ case "$1" in
+ link)
+ _link="$2"
+ shift 2
+ ;;
+ name)
+ _name="$2"
+ shift 2
+ ;;
+ type)
+ if [ "$2" != "vlan" ]; then
+ not_implemented "link type $1"
+ fi
+ _type="$2"
+ shift 2
+ ;;
+ id) shift 2 ;;
+ *) not_implemented "$1" ;;
+ esac
+ done
+
+ case "$_type" in
+ vlan)
+ if [ -z "$_name" ] || [ -z "$_link" ]; then
+ not_implemented "ip link add with null name or link"
+ fi
+
+ mkdir -p "${FAKE_IP_STATE}/interfaces-vlan"
+ echo "$_link" >"${FAKE_IP_STATE}/interfaces-vlan/${_name}"
+ ip_link_set_down "$_name"
+ ;;
+ esac
+}
+
+ip_link_delete()
+{
+ mkdir -p "${FAKE_IP_STATE}/interfaces-deleted"
+ touch "${FAKE_IP_STATE}/interfaces-deleted/$1"
+ rm -f "${FAKE_IP_STATE}/interfaces-vlan/$1"
+}
+
+ip_link_set_up()
+{
+ rm -f "${FAKE_IP_STATE}/interfaces-down/$1"
+ rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
+}
+
+ip_link_set_down()
+{
+ rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1"
+ mkdir -p "${FAKE_IP_STATE}/interfaces-down"
+ touch "${FAKE_IP_STATE}/interfaces-down/$1"
+}
+
+ip_link_show()
+{
+ dev="$1"
+ if [ "$dev" = "dev" ] && [ -n "$2" ]; then
+ dev="$2"
+ fi
+
+ if [ -e "${FAKE_IP_STATE}/interfaces-deleted/$dev" ]; then
+ echo "Device \"${dev}\" does not exist." >&2
+ exit 255
+ fi
+
+ if [ -r "${FAKE_IP_STATE}/interfaces-vlan/${dev}" ]; then
+ read -r _link <"${FAKE_IP_STATE}/interfaces-vlan/${dev}"
+ dev="${dev}@${_link}"
+ fi
+
+ _state="UP"
+ _flags=",UP,LOWER_UP"
+ if [ -e "${FAKE_IP_STATE}/interfaces-down/$dev" ]; then
+ _state="DOWN"
+ _flags=""
+ fi
+ case "$dev" in
+ lo)
+ _mac="00:00:00:00:00:00"
+ _brd="00:00:00:00:00:00"
+ _type="loopback"
+ _state="UNKNOWN"
+ _status="<LOOPBACK${_flags}>"
+ _opts="mtu 65536 qdisc noqueue state ${_state}"
+ ;;
+ *)
+ _mac=$(echo "$dev" | cksum | sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@')
+ _brd="ff:ff:ff:ff:ff:ff"
+ _type="ether"
+ _status="<BROADCAST,MULTICAST${_flags}>"
+ _opts="mtu 1500 qdisc pfifo_fast state ${_state} qlen 1000"
+ ;;
+ esac
+
+ if $brief; then
+ printf '%-16s %-14s %-17s %s\n' \
+ "$dev" "$_status" "$_mac" "$_status"
+ else
+ echo "${n:-42}: ${dev}: ${_status} ${_opts}"
+ echo " link/${_type} ${_mac} brd ${_brd}"
+ fi
+}
+
+# This is incomplete because it doesn't actually look up table ids in
+# /etc/iproute2/rt_tables. The rules/routes are actually associated
+# with the name instead of the number. However, we include a variable
+# to fake a bad table id.
+[ -n "$IP_ROUTE_BAD_TABLE_ID" ] || IP_ROUTE_BAD_TABLE_ID=false
+
+ip_check_table()
+{
+ _cmd="$1"
+
+ if [ "$_cmd" = "route" ] && [ -z "$_table" ]; then
+ _table="main"
+ fi
+
+ [ -n "$_table" ] || not_implemented "ip rule/route without \"table\""
+
+ # Only allow tables names from 13.per_ip_routing and "main". This
+ # is a cheap way of avoiding implementing the default/local
+ # tables.
+ case "$_table" in
+ ctdb.* | main)
+ if $IP_ROUTE_BAD_TABLE_ID; then
+ # Ouch. Simulate inconsistent errors from ip. :-(
+ case "$_cmd" in
+ route)
+ echo "Error: argument \"${_table}\" is wrong: table id value is invalid" >&2
+
+ ;;
+ *)
+ echo "Error: argument \"${_table}\" is wrong: invalid table ID" >&2
+ ;;
+ esac
+ exit 255
+ fi
+ ;;
+ *) not_implemented "table=${_table} ${orig_args}" ;;
+ esac
+}
+
+######################################################################
+
+ip_addr()
+{
+ case "$1" in
+ show | list | "")
+ shift
+ ip_addr_show "$@"
+ ;;
+ add*)
+ shift
+ ip_addr_add "$@"
+ ;;
+ del*)
+ shift
+ ip_addr_del "$@"
+ ;;
+ *) not_implemented "\"$1\" in \"$orig_args\"" ;;
+ esac
+}
+
+ip_addr_show()
+{
+ dev=""
+ primary=true
+ secondary=true
+ _to=""
+
+ if $brief; then
+ not_implemented "ip -br addr show in \"$orig_args\""
+ fi
+
+ while [ -n "$1" ]; do
+ case "$1" in
+ dev)
+ dev="$2"
+ shift 2
+ ;;
+ # Do stupid things and stupid things will happen!
+ primary)
+ primary=true
+ secondary=false
+ shift
+ ;;
+ secondary)
+ secondary=true
+ primary=false
+ shift
+ ;;
+ to)
+ _to="$2"
+ shift 2
+ ;;
+ *)
+ # Assume an interface name
+ dev="$1"
+ shift 1
+ ;;
+ esac
+ done
+ devices="$dev"
+ if [ -z "$devices" ]; then
+ # No device specified? Get all the primaries...
+ devices=$(find "${FAKE_IP_STATE}/addresses" -name "*-primary" |
+ sed -e 's@.*/@@' -e 's@-.*-primary$@@' |
+ sort -u)
+ fi
+ calc_brd()
+ {
+ case "${local#*/}" in
+ 24) brd="${local%.*}.255" ;;
+ 32) brd="" ;;
+ *) not_implemented "list ... fake bits other than 24/32: ${local#*/}" ;;
+ esac
+ }
+ show_iface()
+ {
+ ip_link_show "$dev"
+
+ nets=$(find "${FAKE_IP_STATE}/addresses" -name "${dev}-*-primary" |
+ sed -e 's@.*/@@' -e "s@${dev}-\(.*\)-primary\$@\1@")
+
+ for net in $nets; do
+ pf="${FAKE_IP_STATE}/addresses/${dev}-${net}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-${net}-secondary"
+ if $primary && [ -r "$pf" ]; then
+ read -r local scope <"$pf"
+ if [ -z "$_to" ] || [ "${_to%/*}" = "${local%/*}" ]; then
+ calc_brd
+ echo " inet ${local} ${brd:+brd ${brd} }scope ${scope} ${dev}"
+ fi
+ fi
+ if $secondary && [ -r "$sf" ]; then
+ while read -r local scope; do
+ if [ -z "$_to" ] || [ "${_to%/*}" = "${local%/*}" ]; then
+ calc_brd
+ echo " inet ${local} ${brd:+brd }${brd} scope ${scope} secondary ${dev}"
+ fi
+ done <"$sf"
+ fi
+ if [ -z "$_to" ]; then
+ echo " valid_lft forever preferred_lft forever"
+ fi
+ done
+ }
+ n=1
+ for dev in $devices; do
+ if [ -z "$_to" ] ||
+ grep -F "${_to%/*}/" "${FAKE_IP_STATE}/addresses/${dev}-"* >/dev/null; then
+ show_iface
+ fi
+ n=$((n + 1))
+ done
+}
+
+# Copied from 13.per_ip_routing for now... so this is lazy testing :-(
+ipv4_host_addr_to_net()
+{
+ _addr="$1"
+
+ _host="${_addr%/*}"
+ _maskbits="${_addr#*/}"
+
+ # Convert the host address to an unsigned long by splitting out
+ # the octets and doing the math.
+ _host_ul=0
+ # Want 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}"
+}
+
+ip_addr_add()
+{
+ local=""
+ dev=""
+ brd=""
+ scope="global"
+ while [ -n "$1" ]; do
+ case "$1" in
+ *.*.*.*/*)
+ local="$1"
+ shift
+ ;;
+ local)
+ local="$2"
+ shift 2
+ ;;
+ broadcast | brd)
+ # For now assume this is always '+'.
+ if [ "$2" != "+" ]; then
+ not_implemented "addr add ... brd $2 ..."
+ fi
+ shift 2
+ ;;
+ dev)
+ dev="$2"
+ shift 2
+ ;;
+ scope)
+ scope="$2"
+ shift 2
+ ;;
+ *)
+ not_implemented "$@"
+ ;;
+ esac
+ done
+ if [ -z "$dev" ]; then
+ not_implemented "addr add (without dev)"
+ fi
+ mkdir -p "${FAKE_IP_STATE}/addresses"
+ net_str=$(ipv4_host_addr_to_net "$local")
+ net_str=$(echo "$net_str" | sed -e 's@/@_@')
+ pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-secondary"
+ # We could lock here... but we should be the only ones playing
+ # around here with these stubs.
+ if [ ! -f "$pf" ]; then
+ echo "$local $scope" >"$pf"
+ elif grep -Fq "$local" "$pf"; then
+ echo "RTNETLINK answers: File exists" >&2
+ exit 254
+ elif [ -f "$sf" ] && grep -Fq "$local" "$sf"; then
+ echo "RTNETLINK answers: File exists" >&2
+ exit 254
+ else
+ echo "$local $scope" >>"$sf"
+ fi
+}
+
+ip_addr_del()
+{
+ local=""
+ dev=""
+ while [ -n "$1" ]; do
+ case "$1" in
+ *.*.*.*/*)
+ local="$1"
+ shift
+ ;;
+ local)
+ local="$2"
+ shift 2
+ ;;
+ dev)
+ dev="$2"
+ shift 2
+ ;;
+ *)
+ not_implemented "addr del ... $1 ..."
+ ;;
+ esac
+ done
+ if [ -z "$dev" ]; then
+ not_implemented "addr del (without dev)"
+ fi
+ mkdir -p "${FAKE_IP_STATE}/addresses"
+ net_str=$(ipv4_host_addr_to_net "$local")
+ net_str=$(echo "$net_str" | sed -e 's@/@_@')
+ pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-secondary"
+ # We could lock here... but we should be the only ones playing
+ # around here with these stubs.
+ if [ ! -f "$pf" ]; then
+ echo "RTNETLINK answers: Cannot assign requested address" >&2
+ exit 254
+ elif grep -Fq "$local" "$pf"; then
+ if $promote_secondaries && [ -s "$sf" ]; then
+ head -n 1 "$sf" >"$pf"
+ sed -i -e '1d' "$sf"
+ else
+ # Remove primaries AND SECONDARIES.
+ rm -f "$pf" "$sf"
+ fi
+ elif [ -f "$sf" ] && grep -Fq "$local" "$sf"; then
+ grep -Fv "$local" "$sf" >"${sf}.new"
+ mv "${sf}.new" "$sf"
+ else
+ echo "RTNETLINK answers: Cannot assign requested address" >&2
+ exit 254
+ fi
+}
+
+######################################################################
+
+ip_rule()
+{
+ case "$1" in
+ show | list | "")
+ shift
+ ip_rule_show "$@"
+ ;;
+ add)
+ shift
+ ip_rule_add "$@"
+ ;;
+ del*)
+ shift
+ ip_rule_del "$@"
+ ;;
+ *) not_implemented "$1 in \"$orig_args\"" ;;
+ esac
+
+}
+
+# All non-default rules are in $FAKE_IP_STATE_RULES/rules. As with
+# the real version, rules can be repeated. Deleting just deletes the
+# 1st match.
+
+ip_rule_show()
+{
+ if $brief; then
+ not_implemented "ip -br rule show in \"$orig_args\""
+ fi
+
+ ip_rule_show_1()
+ {
+ _pre="$1"
+ _table="$2"
+ _selectors="$3"
+ # potentially more options
+
+ printf "%d:\t%s lookup %s \n" "$_pre" "$_selectors" "$_table"
+ }
+
+ ip_rule_show_some()
+ {
+ _min="$1"
+ _max="$2"
+
+ [ -f "${FAKE_IP_STATE}/rules" ] || return
+
+ while read -r _pre _table _selectors; do
+ # Only print those in range
+ if [ "$_min" -le "$_pre" ] &&
+ [ "$_pre" -le "$_max" ]; then
+ ip_rule_show_1 "$_pre" "$_table" "$_selectors"
+ fi
+ done <"${FAKE_IP_STATE}/rules"
+ }
+
+ ip_rule_show_1 0 "local" "from all"
+
+ ip_rule_show_some 1 32765
+
+ ip_rule_show_1 32766 "main" "from all"
+ ip_rule_show_1 32767 "default" "from all"
+
+ ip_rule_show_some 32768 2147483648
+}
+
+ip_rule_common()
+{
+ _from=""
+ _pre=""
+ _table=""
+ while [ -n "$1" ]; do
+ case "$1" in
+ from)
+ _from="$2"
+ shift 2
+ ;;
+ pref)
+ _pre="$2"
+ shift 2
+ ;;
+ table)
+ _table="$2"
+ shift 2
+ ;;
+ *) not_implemented "$1 in \"$orig_args\"" ;;
+ esac
+ done
+
+ [ -n "$_pre" ] || not_implemented "ip rule without \"pref\""
+ ip_check_table "rule"
+ # Relax this if more selectors added later...
+ [ -n "$_from" ] || not_implemented "ip rule without \"from\""
+}
+
+ip_rule_add()
+{
+ ip_rule_common "$@"
+
+ _f="${FAKE_IP_STATE}/rules"
+ touch "$_f"
+ (
+ flock 0
+ # Filter order must be consistent with the comparison in ip_rule_del()
+ echo "$_pre $_table${_from:+ from }$_from" >>"$_f"
+ ) <"$_f"
+}
+
+ip_rule_del()
+{
+ ip_rule_common "$@"
+
+ _f="${FAKE_IP_STATE}/rules"
+ touch "$_f"
+ # ShellCheck doesn't understand this flock pattern
+ # shellcheck disable=SC2094
+ (
+ flock 0
+ _tmp="${_f}.new"
+ : >"$_tmp"
+ _found=false
+ while read -r _p _t _s; do
+ if ! $_found &&
+ [ "$_p" = "$_pre" ] && [ "$_t" = "$_table" ] &&
+ [ "$_s" = "${_from:+from }$_from" ]; then
+ # Found. Skip this one but not future ones.
+ _found=true
+ else
+ echo "$_p $_t $_s" >>"$_tmp"
+ fi
+ done
+ if cmp -s "$_tmp" "$_f"; then
+ # No changes, must not have found what we wanted to delete
+ echo "RTNETLINK answers: No such file or directory" >&2
+ rm -f "$_tmp"
+ exit 2
+ else
+ mv "$_tmp" "$_f"
+ fi
+ ) <"$_f" || exit $?
+}
+
+######################################################################
+
+ip_route()
+{
+ case "$1" in
+ show | list)
+ shift
+ ip_route_show "$@"
+ ;;
+ flush)
+ shift
+ ip_route_flush "$@"
+ ;;
+ add)
+ shift
+ ip_route_add "$@"
+ ;;
+ del*)
+ shift
+ ip_route_del "$@"
+ ;;
+ *) not_implemented "$1 in \"ip route\"" ;;
+ esac
+}
+
+ip_route_common()
+{
+ if [ "$1" = table ]; then
+ _table="$2"
+ shift 2
+ fi
+
+ ip_check_table "route"
+}
+
+# Routes are in a file per table in the directory
+# $FAKE_IP_STATE/routes. These routes just use the table ID
+# that is passed and don't do any lookup. This could be "improved" if
+# necessary.
+
+ip_route_show()
+{
+ ip_route_common "$@"
+
+ # Missing file is just an empty table
+ sort "$FAKE_IP_STATE/routes/${_table}" 2>/dev/null || true
+}
+
+ip_route_flush()
+{
+ ip_route_common "$@"
+
+ rm -f "$FAKE_IP_STATE/routes/${_table}"
+}
+
+ip_route_add()
+{
+ _prefix=""
+ _dev=""
+ _gw=""
+ _table=""
+ _metric=""
+
+ while [ -n "$1" ]; do
+ case "$1" in
+ *.*.*.*/* | *.*.*.*)
+ _prefix="$1"
+ shift 1
+ ;;
+ local)
+ _prefix="$2"
+ shift 2
+ ;;
+ dev)
+ _dev="$2"
+ shift 2
+ ;;
+ via)
+ _gw="$2"
+ shift 2
+ ;;
+ table)
+ _table="$2"
+ shift 2
+ ;;
+ metric)
+ _metric="$2"
+ shift 2
+ ;;
+ *) not_implemented "$1 in \"$orig_args\"" ;;
+ esac
+ done
+
+ ip_check_table "route"
+ [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\""
+ # This can't be easily deduced, so print some garbage.
+ [ -n "$_dev" ] || _dev="ethXXX"
+
+ # Alias or add missing bits
+ case "$_prefix" in
+ 0.0.0.0/0) _prefix="default" ;;
+ */*) : ;;
+ *) _prefix="${_prefix}/32" ;;
+ esac
+
+ _f="$FAKE_IP_STATE/routes/${_table}"
+ mkdir -p "$FAKE_IP_STATE/routes"
+ touch "$_f"
+
+ # Check for duplicate
+ _prefix_regexp=$(echo "^${_prefix}" | sed -e 's@\.@\\.@g')
+ if [ -n "$_metric" ]; then
+ _prefix_regexp="${_prefix_regexp} .*metric ${_metric} "
+ fi
+ if grep -q "$_prefix_regexp" "$_f"; then
+ echo "RTNETLINK answers: File exists" >&2
+ exit 1
+ fi
+
+ (
+ flock 0
+
+ _out="${_prefix} "
+ [ -z "$_gw" ] || _out="${_out}via ${_gw} "
+ [ -z "$_dev" ] || _out="${_out}dev ${_dev} "
+ [ -n "$_gw" ] || _out="${_out} scope link "
+ [ -z "$_metric" ] || _out="${_out} metric ${_metric} "
+ echo "$_out" >>"$_f"
+ ) <"$_f"
+}
+
+ip_route_del()
+{
+ _prefix=""
+ _dev=""
+ _gw=""
+ _table=""
+ _metric=""
+
+ while [ -n "$1" ]; do
+ case "$1" in
+ *.*.*.*/* | *.*.*.*)
+ _prefix="$1"
+ shift 1
+ ;;
+ local)
+ _prefix="$2"
+ shift 2
+ ;;
+ dev)
+ _dev="$2"
+ shift 2
+ ;;
+ via)
+ _gw="$2"
+ shift 2
+ ;;
+ table)
+ _table="$2"
+ shift 2
+ ;;
+ metric)
+ _metric="$2"
+ shift 2
+ ;;
+ *) not_implemented "$1 in \"$orig_args\"" ;;
+ esac
+ done
+
+ ip_check_table "route"
+ [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\""
+ # This can't be easily deduced, so print some garbage.
+ [ -n "$_dev" ] || _dev="ethXXX"
+
+ # Alias or add missing bits
+ case "$_prefix" in
+ 0.0.0.0/0) _prefix="default" ;;
+ */*) : ;;
+ *) _prefix="${_prefix}/32" ;;
+ esac
+
+ _f="$FAKE_IP_STATE/routes/${_table}"
+ mkdir -p "$FAKE_IP_STATE/routes"
+ touch "$_f"
+
+ # ShellCheck doesn't understand this flock pattern
+ # shellcheck disable=SC2094
+ (
+ flock 0
+
+ # Escape some dots
+ [ -z "$_gw" ] || _gw=$(echo "$_gw" | sed -e 's@\.@\\.@g')
+ _prefix=$(echo "$_prefix" | sed -e 's@\.@\\.@g' -e 's@/@\\/@')
+
+ _re="^${_prefix}\>.*"
+ [ -z "$_gw" ] || _re="${_re}\<via ${_gw}\>.*"
+ [ -z "$_dev" ] || _re="${_re}\<dev ${_dev}\>.*"
+ [ -z "$_metric" ] || _re="${_re}.*\<metric ${_metric}\>.*"
+ sed -i -e "/${_re}/d" "$_f"
+ ) <"$_f"
+}
+
+######################################################################
+
+orig_args="$*"
+
+brief=false
+case "$1" in
+-br*)
+ brief=true
+ shift
+ ;;
+esac
+
+case "$1" in
+link)
+ shift
+ ip_link "$@"
+ ;;
+addr*)
+ shift
+ ip_addr "$@"
+ ;;
+rule)
+ shift
+ ip_rule "$@"
+ ;;
+route)
+ shift
+ ip_route "$@"
+ ;;
+*) not_implemented "$1" ;;
+esac
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ip6tables b/ctdb/tests/UNIT/eventscripts/stubs/ip6tables
new file mode 100755
index 0000000..2c65f7b
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ip6tables
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Always succeed.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/iptables b/ctdb/tests/UNIT/eventscripts/stubs/iptables
new file mode 100755
index 0000000..2c65f7b
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/iptables
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Always succeed.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm b/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm
new file mode 100755
index 0000000..31bdf2c
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+die()
+{
+ echo "$1" >&2
+ exit "${2:-1}"
+}
+
+[ -n "$FAKE_LVS_STATE_DIR" ] || die "FAKE_LVS_STATE_DIR not set"
+
+service_address=""
+scheduling_method="wlc"
+persistent_timeout=""
+real_server=""
+forwarding_method="Route"
+
+set_service_address()
+{
+ [ -z "$service_address" ] ||
+ die "multiple 'service-address' options specified" 2
+ case "$2" in
+ *:*) service_address="${1} ${2}" ;;
+ *) service_address="${1} ${2}:0" ;;
+ esac
+}
+
+set_real_server()
+{
+ [ -z "$real_server" ] ||
+ die "multiple 'real-server' options specified" 2
+ case "$1" in
+ *\]:*) real_server="${1}" ;;
+ *\]) real_server="${1}:0" ;;
+ *:*) real_server="${1}" ;;
+ *) real_server="${1}:0" ;;
+ esac
+
+ case "$real_server" in
+ 127.0.0.1:* | \[::1\]:*) forwarding_method="Local" ;;
+ esac
+}
+
+case "$1" in
+-A)
+ shift
+ while [ -n "$1" ]; do
+ case "$1" in
+ -t)
+ set_service_address "TCP" "$2"
+ shift 2
+ ;;
+ -u)
+ set_service_address "UDP" "$2"
+ shift 2
+ ;;
+ -s)
+ scheduling_method="$2"
+ shift 2
+ ;;
+ -p)
+ persistent_timeout="persistent $2"
+ shift 2
+ ;;
+ *) die "Unsupported -A option $1" ;;
+ esac
+ done
+ [ -n "$service_address" ] ||
+ die "You need to supply the 'service-address' option for the 'add-service' command" 2
+ d="${FAKE_LVS_STATE_DIR}/${service_address}"
+ mkdir "$d" 2>/dev/null || die "Service already exists" 255
+ t="${scheduling_method}${persistent_timeout:+ }${persistent_timeout}"
+ echo "$t" >"${d}/.info"
+ ;;
+-D)
+ shift
+ while [ -n "$1" ]; do
+ case "$1" in
+ -t)
+ set_service_address "TCP" "$2"
+ shift 2
+ ;;
+ -u)
+ set_service_address "UDP" "$2"
+ shift 2
+ ;;
+ *) die "Unsupported -D option $1" ;;
+ esac
+ done
+ [ -n "$service_address" ] ||
+ die "You need to supply the 'service-address' option for the 'delete-service' command" 2
+ d="${FAKE_LVS_STATE_DIR}/${service_address}"
+ rm -f "${d}/"*
+ rm -f "${d}/.info"
+ rmdir "$d" 2>/dev/null || die "No such service" 255
+ ;;
+-a)
+ shift
+ while [ -n "$1" ]; do
+ case "$1" in
+ -t)
+ set_service_address "TCP" "$2"
+ shift 2
+ ;;
+ -u)
+ set_service_address "UDP" "$2"
+ shift 2
+ ;;
+ -r)
+ set_real_server "$2"
+ shift 2
+ ;;
+ -g)
+ forwarding_method="Route"
+ shift 1
+ ;;
+ *) die "Unsupported -A option $1" ;;
+ esac
+ done
+ [ -n "$service_address" ] ||
+ die "You need to supply the 'service-address' option for the 'delete-service' command" 2
+ d="${FAKE_LVS_STATE_DIR}/${service_address}"
+ [ -d "$d" ] || die "Service not defined" 255
+ [ -n "$real_server" ] ||
+ die "You need to supply the 'real-server' option for the 'add-server' command" 2
+ f="${d}/${real_server}"
+ echo "$forwarding_method" >"$f"
+ ;;
+-l)
+ cat <<EOF
+IP Virtual Server version 1.2.1 (size=4096)
+Prot LocalAddress:Port Scheduler Flags
+ -> RemoteAddress:Port Forward Weight ActiveConn InActConn
+EOF
+ cd "$FAKE_LVS_STATE_DIR" || exit 0
+ (
+ for d in *; do
+ [ -d "$d" ] || continue
+ printf '%s ' "$d"
+ cat "${d}/.info"
+ for f in "${d}/"*; do
+ [ -f "$f" ] || continue
+ read -r forwarding_method <"$f"
+ printf " -> %-28s %-7s %-6s %-10s %-10s\n" \
+ "${f##*/}" "$forwarding_method" 1 0 0
+ done
+ done
+ )
+ ;;
+*)
+ die "Unknown option $1"
+ ;;
+esac
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/kill b/ctdb/tests/UNIT/eventscripts/stubs/kill
new file mode 100755
index 0000000..b69e3e6
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/kill
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Always succeed. This means that kill -0 will always find a
+# process and anything else will successfully kill. This should
+# exercise a good avriety of code paths.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/killall b/ctdb/tests/UNIT/eventscripts/stubs/killall
new file mode 100755
index 0000000..1e182e1
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/killall
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Always succeed. This means that killall -0 will always find a
+# process and anything else will successfully kill. This should
+# exercise a good avriety of code paths.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/multipath b/ctdb/tests/UNIT/eventscripts/stubs/multipath
new file mode 100755
index 0000000..319b734
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/multipath
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+usage()
+{
+ die "usage: ${0} -ll device"
+}
+
+[ "$1" = "-ll" ] || usage
+shift
+[ $# -eq 1 ] || usage
+
+device="$1"
+
+if [ -n "$FAKE_MULTIPATH_HANG" ]; then
+ FAKE_SLEEP_REALLY="yes" sleep 999
+fi
+
+path1_state="active"
+path2_state="enabled"
+
+for i in $FAKE_MULTIPATH_FAILURES; do
+ if [ "$device" = "$i" ]; then
+ path1_state="inactive"
+ path2_state="inactive"
+ break
+ fi
+done
+
+cat <<EOF
+${device} (AUTO-01234567) dm-0 ,
+size=10G features='0' hwhandler='0' wp=rw
+|-+- policy='round-robin 0' prio=1 status=${path1_state}
+| \`- #:#:#:# vda 252:0 active ready running
+\`-+- policy='round-robin 0' prio=1 status=${path2_state}
+ \`- #:#:#:# vdb 252:16 active ready running
+EOF
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/net b/ctdb/tests/UNIT/eventscripts/stubs/net
new file mode 100755
index 0000000..3f96413
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/net
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Always succeed for now...
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout b/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout
new file mode 100755
index 0000000..a4d43d0
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+case "$1" in
+register)
+ echo "ALL"
+ exit
+ ;;
+esac
+
+if [ "$NFS_FAKE_CALLOUT_MAGIC" = "$1" ]; then
+ echo "$1"
+ exit 1
+fi
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/nfsconf b/ctdb/tests/UNIT/eventscripts/stubs/nfsconf
new file mode 100755
index 0000000..84dd9ea
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/nfsconf
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# This always fails for now, since there are no tests that expect to
+# use it.
+exit 1
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/pidof b/ctdb/tests/UNIT/eventscripts/stubs/pidof
new file mode 100755
index 0000000..6a25395
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/pidof
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+case "$1" in
+nfsd)
+ echo "$FAKE_NFSD_THREAD_PIDS"
+ ;;
+rpc.statd | rpc.rquotad | rpc.mountd)
+ echo "$FAKE_RPC_THREAD_PIDS"
+ ;;
+smbd)
+ echo "$FAKE_SMBD_THREAD_PIDS"
+ ;;
+*)
+ echo "pidof: \"$1\" not implemented"
+ exit 1
+ ;;
+esac
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/pkill b/ctdb/tests/UNIT/eventscripts/stubs/pkill
new file mode 100755
index 0000000..b3f1de5
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/pkill
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Always succeed. This means that pkill -0 will always find a
+# process and anything else will successfully kill. This should
+# exercise a good avriety of code paths.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ps b/ctdb/tests/UNIT/eventscripts/stubs/ps
new file mode 100755
index 0000000..0d33203
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ps
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+usage()
+{
+ echo "ps [ -p PID | -o FORMAT | aufxww ]"
+ exit 1
+}
+
+while getopts "o:p:h:?" opt; do
+ case "$opt" in
+ o) format="$OPTARG" ;;
+ p) pid="$OPTARG" ;;
+ \? | h) usage ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+if [ -n "$pid" ] && [ -n "$FAKE_PS_MAP" ]; then
+ # shellcheck disable=SC1001
+ case "$format" in
+ comm\=)
+ echo "$FAKE_PS_MAP" |
+ awk -v pid="$pid" '$1 == pid { print $2 }'
+ ;;
+ state\=)
+ echo "$FAKE_PS_MAP" |
+ awk -v pid="$pid" '$1 == pid { print $3 }'
+ ;;
+ esac
+
+ exit
+fi
+
+if [ "$1" != "auxfww" ]; then
+ echo "option $1 not supported"
+ usage
+fi
+
+cat <<EOF
+USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
+root 2 0.0 0.0 0 0 ? S Aug28 0:00 [kthreadd]
+root 3 0.0 0.0 0 0 ? S Aug28 0:43 \_ [ksoftirqd/0]
+...
+root 1 0.0 0.0 2976 624 ? Ss Aug28 0:07 init [2]
+root 495 0.0 0.0 3888 1640 ? Ss Aug28 0:00 udevd --daemon
+...
+[MORE FAKE ps OUTPUT]
+EOF
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rm b/ctdb/tests/UNIT/eventscripts/stubs/rm
new file mode 100755
index 0000000..6034d75
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rm
@@ -0,0 +1,6 @@
+#!/bin/sh
+# Make statd-callout happy
+case "$*" in
+*/var/lib/nfs/statd/sm*) : ;;
+*) exec /bin/rm "$@" ;;
+esac
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd
new file mode 100755
index 0000000..e71f6cd
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Restart always "works". However, the test infrastructure may
+# continue to mark the service as down, so that's what matters.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd
new file mode 100755
index 0000000..e71f6cd
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Restart always "works". However, the test infrastructure may
+# continue to mark the service as down, so that's what matters.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad b/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad
new file mode 100755
index 0000000..e71f6cd
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Restart always "works". However, the test infrastructure may
+# continue to mark the service as down, so that's what matters.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd
new file mode 100755
index 0000000..e71f6cd
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# Restart always "works". However, the test infrastructure may
+# continue to mark the service as down, so that's what matters.
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo b/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo
new file mode 100755
index 0000000..8732751
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+prog="rpcinfo"
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $prog -T tcp host program [version]
+
+A fake rpcinfo stub that succeeds for items in FAKE_RPCINFO_SERVICES,
+depending on command-line options.
+
+EOF
+ exit 1
+}
+
+parse_options()
+{
+ while getopts "T:h?" opt; do
+ case "$opt" in
+ T) netid="$OPTARG" ;;
+ \? | h) usage ;;
+ esac
+ done
+ shift $((OPTIND - 1))
+
+ [ "$netid" = "tcp" ] || usage
+
+ host="$1"
+ shift
+ [ "$host" = "localhost" ] || [ "$host" = "127.0.0.1" ] || usage
+
+ if [ $# -lt 1 ] || [ $# -gt 2 ]; then
+ usage
+ fi
+
+ p="$1"
+ v="$2"
+}
+
+parse_options "$@"
+
+for i in ${FAKE_RPCINFO_SERVICES}; do
+ # This is stupidly cumulative, but needs to happen after the
+ # initial split of the list above.
+ IFS="${IFS}:"
+ # Want glob expansion
+ # shellcheck disable=SC2086
+ set -- $i
+ # $1 = program, $2 = low version, $3 = high version
+
+ if [ "$1" = "$p" ]; then
+ if [ -n "$v" ]; then
+ if [ "$2" -le "$v" ] && [ "$v" -le "$3" ]; then
+ echo "program ${p} version ${v} ready and waiting"
+ exit 0
+ else
+ echo "rpcinfo: RPC: Program/version mismatch; low version = ${2}, high version = ${3}" >&2
+ echo "program ${p} version ${v} is not available"
+ exit 1
+ fi
+ else
+ for j in $(seq "$2" "$3"); do
+ echo "program ${p} version ${j} ready and waiting"
+ done
+ exit 0
+ fi
+ fi
+done
+
+echo "rpcinfo: RPC: Program not registered" >&2
+if [ -n "$v" ]; then
+ echo "program ${p} version ${v} is not available"
+else
+ echo "program ${p} is not available"
+fi
+
+exit 1
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/service b/ctdb/tests/UNIT/eventscripts/stubs/service
new file mode 100755
index 0000000..d706280
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/service
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+service_status_dir="${CTDB_TEST_TMP_DIR}/service_fake_status"
+mkdir -p "$service_status_dir"
+
+service="$1"
+flag="${service_status_dir}/${service}"
+
+start()
+{
+ if [ -f "$flag" ]; then
+ echo "service: can't start ${service} - already running"
+ exit 1
+ else
+ touch "$flag"
+ echo "Starting ${service}: OK"
+ fi
+}
+
+stop()
+{
+ if [ -f "$flag" ]; then
+ echo "Stopping ${service}: OK"
+ rm -f "$flag"
+ else
+ echo "service: can't stop ${service} - not running"
+ exit 1
+ fi
+}
+
+case "$2" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart | reload)
+ stop
+ start
+ ;;
+status)
+ if [ -f "$flag" ]; then
+ echo "$service running"
+ exit 0
+ else
+ echo "$service not running"
+ exit 3
+ fi
+ ;;
+force-started)
+ # For test setup...
+ touch "$flag"
+ ;;
+force-stopped)
+ # For test setup...
+ rm -f "$flag"
+ ;;
+*)
+ echo "service $service $2 not supported"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/sleep b/ctdb/tests/UNIT/eventscripts/stubs/sleep
new file mode 100755
index 0000000..0d0e82b
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/sleep
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if [ "$FAKE_SLEEP_REALLY" = "yes" ]; then
+ /bin/sleep "$@"
+elif [ -n "$FAKE_SLEEP_FORCE" ]; then
+ /bin/sleep "$FAKE_SLEEP_FORCE"
+else
+ :
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/smnotify b/ctdb/tests/UNIT/eventscripts/stubs/smnotify
new file mode 100755
index 0000000..5606b3d
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/smnotify
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+usage()
+{
+ _prog="${0##*/}" # basename
+ cat <<EOF
+Usage: ${_prog} --client=CLIENT --ip=IP --server=SERVER --stateval=STATEVAL
+EOF
+ exit 1
+}
+
+cip=""
+sip=""
+mon_name=""
+state=""
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --client)
+ cip="$2"
+ shift 2
+ ;;
+ --client=*)
+ cip="${1#*=}"
+ shift
+ ;;
+ --ip)
+ sip="$2"
+ shift 2
+ ;;
+ --ip=*)
+ sip="${1#*=}"
+ shift
+ ;;
+ --server)
+ mon_name="$2"
+ shift 2
+ ;;
+ --server=*)
+ mon_name="${1#*=}"
+ shift
+ ;;
+ --stateval)
+ state="$2"
+ shift 2
+ ;;
+ --stateval=*)
+ state="${1#*=}"
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*) usage ;;
+ *) break ;;
+ esac
+done
+[ $# -eq 0 ] || usage
+
+if [ -z "$cip" ] || [ -z "$sip" ] || [ -z "$mon_name" ] || [ -z "$state" ]; then
+ usage
+fi
+
+echo "SM_NOTIFY: ${sip} -> ${cip}, MON_NAME=${mon_name}, STATE=${state}"
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ss b/ctdb/tests/UNIT/eventscripts/stubs/ss
new file mode 100755
index 0000000..c1199fe
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ss
@@ -0,0 +1,206 @@
+#!/bin/sh
+
+prog="ss"
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $prog { -t|--tcp | -x|--unix } [options] [ FILTER ]
+
+A fake ss stub that prints items depending on the variables
+FAKE_NETSTAT_TCP_ESTABLISHED, FAKE_TCP_LISTEN,
+FAKE_NETSTAT_UNIX_LISTEN, depending on command-line options.
+
+Note that -n is ignored.
+
+EOF
+ exit 1
+}
+
+not_supported()
+{
+ echo "Options not supported in stub: $*" >&2
+ usage
+}
+
+############################################################
+
+#
+parse_filter()
+{
+ # Very limited implementation:
+ # We only expect to find || inside parentheses
+ # We don't expect to see && - it is implied by juxtaposition
+ # Operator for port comparison is ignored and assumed to be ==
+
+ # Build lists of source ports and source IP addresses where
+ # each entry is surrounded by '|' characters. These lists can
+ # be easily "searched" using the POSIX prefix and suffix
+ # removal operators.
+ in_parens=false
+ sports="|"
+ srcs="|"
+
+ while [ -n "$1" ]; do
+ case "$1" in
+ \()
+ in_parens=true
+ shift
+ ;;
+ \))
+ in_parens=false
+ shift
+ ;;
+ \|\|)
+ if ! $in_parens; then
+ not_supported "|| in parentheses"
+ fi
+ shift
+ ;;
+ sport)
+ p="${3#:}"
+ sports="${sports}${p}|"
+ shift 3
+ ;;
+ src)
+ ip="${2#\[}"
+ ip="${ip%\]}"
+ srcs="${srcs}${ip}|"
+ shift 2
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ done
+}
+
+# Check if socket has matches in both ok_ips and ok_ports
+filter_socket()
+{
+ ok_ips="$1"
+ ok_ports="$2"
+ socket="$3"
+
+ ip="${socket%:*}"
+ port="${socket##*:}"
+
+ if [ "$ok_ports" != "|" ] &&
+ [ "${ok_ports#*|"${port}"|}" = "$ok_ports" ]; then
+ return 1
+ fi
+ if [ "$ok_ips" != "|" ] && [ "${ok_ips#*|"${ip}"|}" = "$ok_ips" ]; then
+ return 1
+ fi
+
+ return 0
+}
+
+ss_tcp_established()
+{
+ if $header; then
+ echo "Recv-Q Send-Q Local Address:Port Peer Address:Port"
+ fi
+
+ # Yes, lose the quoting so we can do a hacky parsing job
+ # shellcheck disable=SC2048,SC2086
+ parse_filter $*
+
+ for i in $FAKE_NETSTAT_TCP_ESTABLISHED; do
+ src="${i%|*}"
+ dst="${i#*|}"
+ if filter_socket "$srcs" "$sports" "$src"; then
+ echo 0 0 "$src" "$dst"
+ fi
+ done
+
+ if [ -z "$FAKE_NETSTAT_TCP_ESTABLISHED_FILE" ]; then
+ return
+ fi
+ while read -r src dst; do
+ if filter_socket "$srcs" "$sports" "$src"; then
+ echo 0 0 "$src" "$dst"
+ fi
+ done <"$FAKE_NETSTAT_TCP_ESTABLISHED_FILE"
+}
+
+############################################################
+
+unix_listen()
+{
+ if $header; then
+ cat <<EOF
+Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port"
+EOF
+ fi
+
+ # Yes, lose the quoting so we can do a hacky parsing job
+ # shellcheck disable=SC2048,SC2086
+ parse_filter $*
+
+ _n=12345
+ for _s in $FAKE_NETSTAT_UNIX_LISTEN; do
+ # ss matches Unix domain sockets as either src or
+ # sport.
+ if filter_socket "$srcs" "$sports" "${_s}:" ||
+ filter_socket "$srcs" "$sports" ":${_s}"; then
+ printf "u_str LISTEN 0 128 %s %d * 0\n" "$_s" "$_n"
+ _n=$((_n + 1))
+ fi
+ done
+}
+
+############################################################
+
+# Defaults.
+tcp=false
+unix=false
+all=false
+listen=false
+header=true
+
+orig="$*"
+
+while getopts "txnalHh?" opt; do
+ case "$opt" in
+ t) tcp=true ;;
+ x) unix=true ;;
+ l) listen=true ;;
+ a) all=true ;;
+ H) header=false ;;
+ n) : ;;
+ \? | h) usage ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+$tcp || $unix || not_supported "$*"
+if [ -z "$all" ]; then
+ nosupported "$*"
+fi
+
+if $tcp; then
+ if [ "$1" != "state" ] || [ "$2" != "established" ] || $listen; then
+ usage
+ fi
+
+ shift 2
+
+ # Yes, lose the quoting so we can do a hacky parsing job
+ # shellcheck disable=SC2048,SC2086
+ ss_tcp_established $*
+
+ exit
+fi
+
+if $unix; then
+ if ! $listen; then
+ not_supported "$orig"
+ fi
+
+ # Yes, lose the quoting so we can do a hacky parsing job
+ # shellcheck disable=SC2048,SC2086
+ unix_listen $*
+
+ exit
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/stat b/ctdb/tests/UNIT/eventscripts/stubs/stat
new file mode 100755
index 0000000..840265f
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/stat
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+usage()
+{
+ echo "stat -c FMT FILE ..."
+ exit 1
+}
+
+format=""
+
+while getopts "c:h:?" opt; do
+ case "$opt" in
+ c) format="$OPTARG" ;;
+ \? | h) usage ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+fake_device_id()
+{
+ _path="$1"
+
+ _t=$(echo "$FAKE_FILE_ID_MAP" |
+ awk -v path="${_path}" '$1 == path { print $2 }')
+ _major_minor="${_t%:*}"
+ _major="0x${_major_minor%:*}"
+ _minor="0x${_major_minor#*:}"
+ _device_id=$((_major * 256 + _minor))
+ echo "$_device_id"
+}
+
+fake_inode()
+{
+ _path="$1"
+
+ _t=$(echo "$FAKE_FILE_ID_MAP" |
+ awk -v path="${_path}" '$1 == path { print $2 }')
+ echo "${_t##*:}"
+}
+
+if [ -n "$format" ]; then
+ for f; do
+ if [ ! -e "$f" ]; then
+ continue
+ fi
+ case "$f" in
+ /*) path="$f" ;;
+ *) path="${PWD}/${f}" ;;
+ esac
+
+ case "$format" in
+ "s#[0-9a-f]*:[0-9a-f]*:%i #%n #")
+ inode=$(fake_inode "$path")
+ echo "s#[0-9a-f]*:[0-9a-f]*:${inode} #${f} #"
+ ;;
+ "%d:%i")
+ device_id=$(fake_device_id "$path")
+ inode=$(fake_inode "$path")
+ echo "${device_id}:${inode}"
+ ;;
+ *)
+ echo "Unsupported format \"${format}\""
+ usage
+ ;;
+ esac
+ done
+
+ exit
+fi
+
+usage
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check b/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check
new file mode 100755
index 0000000..6cc7572
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+if [ -z "$FAKE_TDB_MUTEX_CHECK" ]; then
+ exit
+fi
+
+echo "$FAKE_TDB_MUTEX_CHECK" |
+ while read -r pid chain; do
+ echo "[${chain}] pid=${pid}"
+ done
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdbdump b/ctdb/tests/UNIT/eventscripts/stubs/tdbdump
new file mode 100755
index 0000000..92dcb8e
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/tdbdump
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if [ "$FAKE_TDB_IS_OK" = "yes" ]; then
+ echo "TDB good"
+ exit 0
+else
+ echo "TDB busted"
+ exit 1
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdbtool b/ctdb/tests/UNIT/eventscripts/stubs/tdbtool
new file mode 100755
index 0000000..df83160
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/tdbtool
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+do_help()
+{
+ if [ "$FAKE_TDBTOOL_SUPPORTS_CHECK" = "yes" ]; then
+ echo "check"
+ fi
+ exit 0
+}
+
+do_check()
+{
+ if [ "$FAKE_TDB_IS_OK" = "yes" ]; then
+ echo "Database integrity is OK"
+ else
+ echo "Database is busted"
+ fi
+ exit 0
+}
+
+do_cmd()
+{
+ case "$*" in
+ *check) do_check ;;
+ help) do_help ;;
+ "") read -r tdb_cmd && [ -n "$tdb_cmd" ] && do_cmd "$tdb_cmd" ;;
+ *)
+ echo "$0: Not implemented: $*"
+ exit 1
+ ;;
+ esac
+}
+
+do_cmd "$@"
+
+exit 0
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/testparm b/ctdb/tests/UNIT/eventscripts/stubs/testparm
new file mode 100755
index 0000000..3a97e91
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/testparm
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+not_implemented()
+{
+ echo "testparm: option \"$1\" not implemented in stub" >&2
+ exit 2
+}
+
+error()
+{
+ cat >&2 <<EOF
+Load smb config files from ${CTDB_SYS_ETCDIR}/samba/smb.conf
+rlimit_max: increasing rlimit_max (2048) to minimum Windows limit (16384)
+EOF
+
+ for i in $FAKE_SHARES; do
+ bi=$(basename "$i")
+ echo "Processing section \"[${bi}]\""
+ done >&2
+
+ cat >&2 <<EOF
+Loaded services file OK.
+WARNING: 'workgroup' and 'netbios name' must differ.
+
+EOF
+
+ exit 1
+}
+
+timeout()
+{
+ echo "$0: INTERNAL ERROR - timeout stub should avoid this" >&2
+}
+
+if [ -n "$FAKE_TESTPARM_FAIL" ]; then
+ error
+fi
+
+if [ -n "$FAKE_TIMEOUT" ]; then
+ timeout
+fi
+
+# Ensure that testparm always uses our canned configuration instead of
+# the global one, unless some other file is specified.
+
+file=""
+param=""
+for i; do
+ case "$i" in
+ --parameter-name=*) param="${i#--parameter-name=}" ;;
+ -*) : ;;
+ *) file="$i" ;;
+ esac
+done
+
+# Parse out parameter request
+if [ -n "$param" ]; then
+ sed -n \
+ -e "s|^[[:space:]]*${param}[[:space:]]*=[[:space:]]\(..*\)|\1|p" \
+ "${file:-"${CTDB_SYS_ETCDIR}/samba/smb.conf"}"
+ exit 0
+fi
+
+if [ -n "$file" ]; then
+ # This should include the shares, since this is used when the
+ # samba eventscript caches the output.
+ cat "$file"
+else
+ # We force our own smb.conf and add the shares.
+ cat "${CTDB_SYS_ETCDIR}/samba/smb.conf"
+
+ for i in $FAKE_SHARES; do
+ bi=$(basename "$i")
+ cat <<EOF
+
+[${bi}]
+ path = $i
+ comment = fake share $bi
+ guest ok = no
+ read only = no
+ browsable = yes
+EOF
+ done
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/timeout b/ctdb/tests/UNIT/eventscripts/stubs/timeout
new file mode 100755
index 0000000..26132ee
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/timeout
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if [ -n "$FAKE_TIMEOUT" ]; then
+ exit 124
+else
+ shift 1
+ exec "$@"
+fi
diff --git a/ctdb/tests/UNIT/eventscripts/stubs/wbinfo b/ctdb/tests/UNIT/eventscripts/stubs/wbinfo
new file mode 100755
index 0000000..b4bd9f2
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/wbinfo
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if [ "$FAKE_WBINFO_FAIL" = "yes" ]; then
+ exit 1
+fi
+
+exit 0