summaryrefslogtreecommitdiffstats
path: root/ctdb/tests/UNIT/eventscripts/stubs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /ctdb/tests/UNIT/eventscripts/stubs
parentInitial commit. (diff)
downloadsamba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz
samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ctdb/tests/UNIT/eventscripts/stubs')
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb496
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb-config2
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs52
-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/ip679
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ip6tables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/iptables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ipvsadm126
-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/pidof16
-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/rpcinfo73
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/service64
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/sleep9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/smnotify38
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ss193
-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/tdbtool33
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/testparm85
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/timeout8
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/wbinfo7
41 files changed, 2276 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..fc7bd4f
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb
@@ -0,0 +1,496 @@
+#!/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
+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 _src _dst ; do
+ if [ -z "$_ip" -o "$_ip" = "${_dst%:*}" ] ; then
+ if [ -z "$_port" -o "$_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 _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
+ node=$(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"
+
+ (
+ 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 _ip _bit _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
+
+ (
+ flock 0
+
+ _t="${FAKE_CTDB_IP_LAYOUT}.new"
+
+ while read _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|"
+}
+
+######################################################################
+
+ctdb_setvar ()
+{
+ _var="$1"
+
+ for _i in $FAKE_CTDB_TUNABLES_OK ; do
+ if [ "$_var" = "$_i" ] ; then
+ return 0
+ fi
+ done
+
+ for _i in $FAKE_CTDB_TUNABLES_OBSOLETE ; do
+ if [ "$_var" = "$_i" ] ; then
+ echo "Setting obsolete tunable variable '${_var}'"
+ return 0
+ fi
+ done
+
+ echo "Unable to set tunable variable '${_var}'"
+ return 1
+}
+
+######################################################################
+
+_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 _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=$(echo -n "$_k" | wc -c)
+ _vn=$(echo -n "$_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 _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..cb5b6e5
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# Only supports reading from stdin
+
+iface="$1" # ignored
+
+while read 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..85e005c
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs
@@ -0,0 +1,52 @@
+#!/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 _ip _opts ; 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..e64a956
--- /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 _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..2f470a8
--- /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..a2e7093
--- /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..bd173f4
--- /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..46c6522
--- /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..9ee41cc
--- /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..630d0e8
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ip
@@ -0,0 +1,679 @@
+#!/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" -o -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" -a -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 _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"
+ _opts="<LOOPBACK${_flags}> mtu 65536 qdisc noqueue state UNKNOWN"
+ ;;
+ *)
+ _mac=$(echo $dev | cksum | sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@')
+ _brd="ff:ff:ff:ff:ff:ff"
+ _type="ether"
+ _opts="<BROADCAST,MULTICAST${_flags}> mtu 1500 qdisc pfifo_fast state ${_state} qlen 1000"
+ esac
+ echo "${n:-42}: ${dev}: ${_opts}"
+ echo " link/${_type} ${_mac} brd ${_brd}"
+}
+
+# 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" -a -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=""
+ 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=$(ls "${FAKE_IP_STATE}/addresses/"*-primary 2>/dev/null | \
+ 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=$(ls "${FAKE_IP_STATE}/addresses/${dev}"-*-primary 2>/dev/null | \
+ 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 local scope <"$pf"
+ if [ -z "$_to" -o "${_to%/*}" = "${local%/*}" ] ; then
+ calc_brd
+ echo " inet ${local} ${brd:+brd ${brd} }scope ${scope} ${dev}"
+ fi
+ fi
+ if $secondary && [ -r "$sf" ] ; then
+ while read local scope ; do
+ if [ -z "$_to" -o "${_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 ()
+{
+ _host="$1"
+ _maskbits="$2"
+
+ # Convert the host address to an unsigned long by splitting out
+ # the octets and doing the math.
+ _host_ul=0
+ 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 $(IFS="/" ; echo $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 $(IFS="/" ; echo $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 ()
+{
+ 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 _pre _table _selectors ; do
+ # Only print those in range
+ [ $_min -le $_pre -a $_pre -le $_max ] || continue
+
+ ip_rule_show_1 $_pre "$_table" "$_selectors"
+ 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"
+ (
+ flock 0
+ _tmp="${_f}.new"
+ : >"$_tmp"
+ _found=false
+ while read _p _t _s ; do
+ if ! $_found && \
+ [ "$_p" = "$_pre" -a "$_t" = "$_table" -a \
+ "$_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"
+
+ (
+ 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="$*"
+
+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..0567f88
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm
@@ -0,0 +1,126 @@
+#!/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
+ [ -d "$FAKE_LVS_STATE_DIR" ] || exit 0
+ (
+ cd "$FAKE_LVS_STATE_DIR"
+ for d in *; do
+ [ -d "$d" ] || continue
+ echo -n "$d "
+ cat "${d}/.info"
+ for f in "${d}/"* ; do
+ [ -f "$f" ] || continue
+ read 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..64f95e7
--- /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..8b57923
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/pidof
@@ -0,0 +1,16 @@
+#!/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..744ba3b
--- /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..64b4d18
--- /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..dd6de3b
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo
@@ -0,0 +1,73 @@
+#!/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" -o "$host" = "127.0.0.1" ] || usage
+
+ [ 1 -le $# -a $# -le 2 ] || usage
+
+ p="$1"
+ v="$2"
+}
+
+parse_options "$@"
+
+for i in ${FAKE_RPCINFO_SERVICES} ; do
+ # This is stupidly cummulative, but needs to happen after the
+ # initial split of the list above.
+ IFS="${IFS}:"
+ set -- $i
+ # $1 = program, $2 = low version, $3 = high version
+
+ if [ "$1" = "$p" ] ; then
+ if [ -n "$v" ] ; then
+ if [ "$2" -le "$v" -a "$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..3d6ddf4
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/service
@@ -0,0 +1,64 @@
+#!/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..e454244
--- /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..7871034
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/smnotify
@@ -0,0 +1,38 @@
+#!/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" -o -z "$sip" -o -z "$mon_name" -o -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..54ff436
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/ss
@@ -0,0 +1,193 @@
+#!/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" != "|" -a "${ok_ports#*|${port}|}" = "$ok_ports" ] ; then
+ return 1
+ fi
+ if [ "$ok_ips" != "|" -a "${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
+
+ 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 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
+
+ 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 $tcp ; then
+ if [ "$1" != "state" -o "$2" != "established" ] || $listen ; then
+ usage
+ fi
+
+ shift 2
+
+ # Yes, lose the quoting so we can do a hacky parsing job
+ 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
+ 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..71508f6
--- /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..16c229c
--- /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..986c5c5
--- /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..816df3b
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/tdbtool
@@ -0,0 +1,33 @@
+#!/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 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..0473bbf
--- /dev/null
+++ b/ctdb/tests/UNIT/eventscripts/stubs/testparm
@@ -0,0 +1,85 @@
+#!/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=""
+parameter=""
+for i ; do
+ case "$i" in
+ --parameter-name=*) parameter="${i#--parameter-name=}" ;;
+ -*) : ;;
+ *) file="$i" ;;
+ esac
+done
+
+# Just hard-code parameter requests for now. Later on they could be
+# parsed out of the file.
+case "$parameter" in
+ security) echo "ADS" ; exit 0 ;;
+ smb*ports) echo "445, 139" ; exit 0 ;;
+ ?*) not_implemented "--parameter-name=$parameter" ;;
+ # Fall through if $parameter not set
+esac
+
+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
+ browseable = 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..1ddce53
--- /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..4fc6b98
--- /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