summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 07:52:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 07:52:36 +0000
commit7de03e4e519705301265c0415b3c0af85263a7ac (patch)
tree29d819c5227e3619d18a67d2a5dde963b3229dbe /tools
parentInitial commit. (diff)
downloadresource-agents-7de03e4e519705301265c0415b3c0af85263a7ac.tar.xz
resource-agents-7de03e4e519705301265c0415b3c0af85263a7ac.zip
Adding upstream version 1:4.13.0.upstream/1%4.13.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am85
-rw-r--r--tools/README.sfex336
-rw-r--r--tools/findif.c845
-rw-r--r--tools/nfsconvert.in324
-rw-r--r--tools/ocf-tester.848
-rwxr-xr-xtools/ocf-tester.in351
-rw-r--r--tools/ocft/ChangeLog57
-rw-r--r--tools/ocft/Filesystem105
-rw-r--r--tools/ocft/IPaddr2137
-rw-r--r--tools/ocft/IPaddr2v4323
-rw-r--r--tools/ocft/IPaddr2v6250
-rw-r--r--tools/ocft/IPsrcaddr63
-rw-r--r--tools/ocft/IPv6addr150
-rw-r--r--tools/ocft/LVM83
-rw-r--r--tools/ocft/MailTo57
-rw-r--r--tools/ocft/Makefile.am63
-rw-r--r--tools/ocft/README.in147
-rw-r--r--tools/ocft/README.zh_CN.in124
-rw-r--r--tools/ocft/Raid1134
-rw-r--r--tools/ocft/SendArp74
-rw-r--r--tools/ocft/VirtualDomain71
-rw-r--r--tools/ocft/Xen65
-rw-r--r--tools/ocft/Xinetd64
-rw-r--r--tools/ocft/apache68
-rw-r--r--tools/ocft/caselib.in299
-rw-r--r--tools/ocft/db2164
-rw-r--r--tools/ocft/drbd.linbit183
-rw-r--r--tools/ocft/exportfs80
-rw-r--r--tools/ocft/exportfs-multidir80
-rw-r--r--tools/ocft/helpers.sh43
-rw-r--r--tools/ocft/iscsi108
-rw-r--r--tools/ocft/jboss83
-rw-r--r--tools/ocft/mysql82
-rw-r--r--tools/ocft/mysql-proxy83
-rw-r--r--tools/ocft/named69
-rw-r--r--tools/ocft/nfsserver69
-rw-r--r--tools/ocft/ocft.in893
-rw-r--r--tools/ocft/oracle81
-rw-r--r--tools/ocft/pgsql71
-rw-r--r--tools/ocft/portblock69
-rw-r--r--tools/ocft/postfix102
-rwxr-xr-xtools/ocft/runocft38
-rw-r--r--tools/ocft/runocft.prereq30
-rw-r--r--tools/ocft/sg_persist225
-rw-r--r--tools/ocft/tomcat73
-rw-r--r--tools/send_arp.libnet.c764
-rw-r--r--tools/send_arp.linux.c1336
-rw-r--r--tools/sfex.h176
-rw-r--r--tools/sfex_daemon.c345
-rw-r--r--tools/sfex_init.819
-rw-r--r--tools/sfex_init.c177
-rw-r--r--tools/sfex_lib.c474
-rw-r--r--tools/sfex_lib.h42
-rw-r--r--tools/sfex_stat.c218
-rw-r--r--tools/storage_mon.c891
-rwxr-xr-xtools/test-findif.sh352
-rw-r--r--tools/tickle_tcp.c380
57 files changed, 12123 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..55e292c
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,85 @@
+#
+# heartbeat: Linux-HA heartbeat code
+#
+# Copyright (C) 2001 Michael Moerz
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+SUBDIRS = ocft
+
+AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
+
+halibdir = $(libexecdir)/heartbeat
+
+EXTRA_DIST = ocf-tester.8 sfex_init.8
+
+sbin_PROGRAMS =
+sbin_SCRIPTS = ocf-tester
+halib_PROGRAMS = findif \
+ storage_mon
+halib_SCRIPTS =
+
+man8_MANS = ocf-tester.8
+
+if BUILD_SFEX
+halib_PROGRAMS += sfex_daemon
+sbin_PROGRAMS += sfex_init sfex_stat
+man8_MANS += sfex_init.8
+endif
+
+if USE_LIBNET
+halib_PROGRAMS += send_arp
+send_arp_SOURCES = send_arp.libnet.c
+send_arp_CFLAGS = @LIBNETDEFINES@
+send_arp_LDADD = $(GLIBLIB) -lplumb @LIBNETLIBS@
+else
+
+if SENDARP_LINUX
+halib_PROGRAMS += send_arp
+send_arp_SOURCES = send_arp.linux.c
+endif
+
+if NFSCONVERT
+halib_SCRIPTS += nfsconvert
+endif
+
+endif
+
+sfex_daemon_SOURCES = sfex_daemon.c sfex.h sfex_lib.c sfex_lib.h
+sfex_daemon_CFLAGS = -D_GNU_SOURCE
+sfex_daemon_LDADD = $(GLIBLIB) -lplumb -lplumbgpl
+
+sfex_init_SOURCES = sfex_init.c sfex.h sfex_lib.c sfex_lib.h
+sfex_init_CFLAGS = -D_GNU_SOURCE
+sfex_init_LDADD = $(GLIBLIB) -lplumb -lplumbgpl
+
+sfex_stat_SOURCES = sfex_stat.c sfex.h sfex_lib.c sfex_lib.h
+sfex_stat_CFLAGS = -D_GNU_SOURCE
+sfex_stat_LDADD = $(GLIBLIB) -lplumb -lplumbgpl
+
+findif_SOURCES = findif.c
+
+storage_mon_SOURCES = storage_mon.c
+storage_mon_CFLAGS = -D_GNU_SOURCE ${LIBQB_CFLAGS}
+storage_mon_LDADD = ${LIBQB_LIBS}
+
+if BUILD_TICKLE
+halib_PROGRAMS += tickle_tcp
+tickle_tcp_SOURCES = tickle_tcp.c
+endif
+
+.PHONY: install-exec-hook
diff --git a/tools/README.sfex b/tools/README.sfex
new file mode 100644
index 0000000..ff850d1
--- /dev/null
+++ b/tools/README.sfex
@@ -0,0 +1,336 @@
+Shared Disk File EXclusiveness Control Program version 1.3
+OCF Resource Agent for Heartbeat v2
+FOR USE IN LINUX 2.6 KERNEL OPERATING SYSTEM ENVIRONMENTS ONLY.
+
+Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+
+Note: Before using this information and the product it supports,
+read the general information in section 4.0 "Trademarks and Notices"
+in this document.
+
+Last Update Date: 10/10/2007
+
+
+=======================================================================
+CONTENTS
+--------
+1.0 Overview
+2.0 Installation and Setup Instructions
+3.0 Configuration Information
+4.0 Trademarks and Notices
+5.0 Disclaimer
+
+=======================================================================
+
+1.0 Overview
+--------------
+Shared Disk File EXclusiveness Control Program, called "SF-EX" for short,
+can prevent a destruction of data on shared disk file system due to
+Split-Brain.
+
+=======================================================================
+
+1.1 Limitations
+---------------------
+This program is tested on the following environment.
+
+ Heartbeat 2.1.2-2
+ Red Hat Enterprise Linux ES release 4 (Nahant Update 5) EM64T
+
+=======================================================================
+
+2.0 Installation and Setup Instructions
+-----------------------------------------
+
+ 2.1.1 Prerequisites
+ SF-EX is released as a source-code package in the format
+ of a gunzip compressed tar file. To unpack the source
+ package, type the following command in the Linux console
+ window:
+
+ $ tar zxf sfex-1.3.tar.gz
+
+ The source files will uncompress to the "sf-ex-x.x"
+ directory.
+
+ 2.1.3 Build and Installation
+
+ Change unpacked directory first.
+
+ $ cd sfex-1.3
+
+ Type the following command in the Linux console window:
+ Press Enter after each command.
+
+ $ ./configure
+ $ make
+ $ su
+ (you need root's password)
+ # make install
+
+ "make install" will copy the modules to /usr/lib64/heartbeat
+
+ NOTE: "make install" should be done on all nodes
+ which Heartbeat would run.
+
+ NOTE: in case of 32bit system
+ If you want to run SF-EX on 32bit system, the modules
+ should be setup on /usr/lib/heartbeat.
+ Use the following configure option on 32bit system.
+
+ $ ./configure --with-lib-dir=/usr/lib/heartbeat
+
+ 2.1.3 Initialization of a device
+ Before running SF-EX, one device should be initialized
+ as below.
+
+ sfex_init [-b <blocksize>] [-n <numlocks>] <device>
+
+ Example:
+ # /usr/lib/heartbeat/sfex_init -b 512 -n 10 /dev/sdb1
+
+ Initialized device is going to be used as a control area
+ for SF-EX.
+ See 3.2.2, if further information is necessary.
+
+ 2.1.4 Access without O_DIRECT
+ If you are planning to access a device without using
+ O_DIRECT, the following option is available.
+
+ Example:
+ $ ./configure -enable-directio=no
+
+ Default value for --enable-directio is "yes".
+
+=======================================================================
+
+3.0 Configuration Information
+-----------------------------
+
+3.1 Configuration Settings
+--------------------------
+
+ 3.1.1 Edit your cib.xml
+ The following example shows a typical configuration
+ for SF-EX and Filesystem.
+
+ 3.1.2 Example for cib.xml
+
+ /dev/sda1 control area for SF-EX
+ /dev/sda2 Filesystem
+
+--- skip ---
+<resources>
+ <group id="grp">
+ <primitive id="prmEx" class="ocf" type="sfex" provider="heartbeat">
+ <operations>
+ <op id="ex_start" name="start" timeout="180s" on_fail="fence"/>
+ <op id="ex_monitor" name="monitor" timeout="60s" on_fail="fence" interval="10s" />
+ <op id="ex_stop" name="stop" timeout="60s" on_fail="fence"/>
+ </operations>
+ <instance_attributes id="atrEx">
+ <attributes>
+ <nvpair id="dsk" name="device" value="/dev/sda1"/>
+ <nvpair id="idx" name="index" value="1"/>
+ <nvpair id="clt" name="collision_timeout" value="1"/>
+ <nvpair id="lct" name="lock_timeout" value="70"/>
+ <nvpair id="mnt" name="monitor_interval" value="10"/>
+ <nvpair id="fck" name="fsck" value="/sbin/fsck -p /dev/sdb2"/>
+ <nvpair id="fcm" name="fsck_mode" value="check"/>
+ <nvpair id="hlt" name="halt" value="/sbin/halt -f -n -p"/>
+ </attributes>
+ </instance_attributes>
+ </primitive>
+ <primitive id="prmFs" class="ocf" type="Filesystem" provider="heartbeat">
+ <operations>
+ <op id="fs_start" name="start" timeout="60s" on_fail="fence"/>
+ <op id="fs_monitor" name="monitor" timeout="60s" on_fail="fence" interval="10s" />
+ <op id="fs_stop" name="stop" timeout="60s" on_fail="fence"/>
+ </operations>
+ <instance_attributes id="atrFs">
+ <attributes>
+ <nvpair id="dev" name="device" value="/dev/sdb2"/>
+ <nvpair id="dir" name="directory" value="/mnt/shared-disk"/>
+ <nvpair id="fst" name="fstype" value="ext3"/>
+ </attributes>
+ </instance_attributes>
+ </primitive>
+ </group>
+</resources>
+--- skip ---
+
+
+3.2 Outline of each module
+--------------------------
+ 3.2.1 sfex
+ Resource Agent script for Heartbeat.
+
+ 3.2.2 sfex_init
+ sfex_init [-b <blocksize>] [-n <numlocks>] <device>
+
+ -b <blocksize> --- The size of the block is specified
+ by the number of bytes. In general, to prevent a partial
+ writing to the disk, the size of block is set to 512
+ bytes etc.
+ Note a set value because this value is used also for
+ the alignment adjustment in the input-output buffer in
+ the program when direct I/O is used(When you specify
+ --enable-directio option for configure script).
+ (In Linux kernel 2.6, "direct I/O " does not work if this
+ value is not a multiple of 512.) Default is 512 bytes.
+
+ -n <numlocks> --- The number of storing lock data is
+ specified by integer of one or more. When you want to
+ control two or more resources by one meta-data, you set
+ the value of two or more to numlocks. A necessary disk
+ area for meta data are (blocksize*(1+numlocks))bytes.
+ Default is 1.
+
+ <device> --- This is file path which stored mata-data.
+ It is usually expressed in "/dev/...", because it is
+ partition on the shared disk.
+
+ exit code ---
+ 0 - Normal end.
+ 3 - Error occurs while processing it.
+ The content of the error is displayed into stderr.
+ 4 - The mistake is found in the command line parameter.
+
+ 3.2.3 sfex_stat
+ sfex_stat [-i <index>] <device>
+
+ -i <index> --- The index is number of the resource that
+ display the lock. This number is specified by the integer
+ of one or more. When two or more resources are exclusively
+ controlled by one meta-data, this option is used.
+ Default is 1.
+
+ <device> --- This is file path which stored mata-data.
+ It is usually expressed in "/dev/...", because it is
+ partition on the shared disk.
+
+ exit code ---
+ 0 - Normal end. Own node is holding lock.
+ 2 - Normal end. Own node does not hold a lock.
+ 3 - Error occurs while processing it.
+ The content of the error is displayed into stderr.
+ 4 - The mistake is found in the command line parameter.
+
+ 3.2.4 sfex_lock
+ sfex_lock
+ [-i <index>]
+ [-c <collision_timeout>]
+ [-t <lock_timeout>]
+ <device>
+
+ -i <index> --- The index is number of the resource that
+ acquire the lock. This number is specified by the integer
+ of one or more. When two or more resources are exclusively
+ controlled by one meta-data, this option is used.
+ Default is 1.
+
+ -c <collision_timeout> --- The waiting time to detect
+ the collision of the lock with other nodes is specified.
+ Time that is very longer than "once synchronous read from
+ device which stored meta-data + once
+ synchronous write" is specified usually. Default is 1 second.
+ This value need not be changed by using this option usually.
+ Because it is not thought to take one second or more to
+ synchronous read and write.
+
+ -t <lock_timeout> --- This specifies the validity term
+ of lock. The unit is a second. This timer prevents the
+ resource being locked for a long time when node crashes
+ with the lock acquired. Therefore, the lock holding node
+ must update lock data at intervals that are shorter than
+ this timer. The sfex_update command is used for updating
+ lock. Default is 60 seconds.
+
+ <device> --- This is file path which stored mata-data.
+ It is usually expressed in "/dev/...", because it is
+ partition on the shared disk.
+
+ exit code ---
+ 0 - Acquire a lock from unlock status.
+ 1 - Acquire a lock from lock timeout status.
+ 2 - Lock acquisition failed.
+ 3 - Error occurs while processing it. The content of the
+ error is displayed into stderr.
+ 4 - The mistake is found in the command line parameter.
+
+ 3.2.5 sfex_unlock
+ sfex_unlock [-i <index>] <device>
+
+ -i <index> --- The index is number of the resource that
+ releases the lock. This number is specified by the integer
+ of one or more. When two or more resources are exclusively
+ controlled by one meta-data, this option is used.
+ Default is 1.
+
+ <device> --- This is file path which stored mata-data.
+ It is usually expressed in "/dev/...", because it is
+ partition on the shared disk.
+
+ exit code ---
+ 0 - Lock release success.
+ 1 - Lock release done already.
+ The lock has already been acquired by other nodes.
+ 3 - Error occurs while processing it.
+ The content of the error is displayed into stderr.
+ 4 - The mistake is found in the command line parameter.
+
+ 3.2.6 sfex_update
+ sfex_update [-i <index>] <device>
+
+ -i <index> --- The index is number of the resource that
+ update the lock. This number is specified by the integer
+ of one or more. When two or more resources are exclusively
+ controlled by one meta-data, this option is used.
+ Default is 1.
+
+ <device> --- This is file path which stored mata-data.
+ It is usually expressed in "/dev/...", because it is
+ partition on the shared disk.
+
+ exit code ---
+ 0 - Lock update success.
+ 2 - Lock update failed.
+ The lock is acquired by other nodes.
+ 3 - Error occurs while processing it.
+ The content of the error is displayed into stderr.
+ 4 - The mistake is found in the command line parameter.
+
+=======================================================================
+
+4.0 Trademarks and Notices
+----------------------------
+
+ Heartbeat is a registered trademark of The High Availability
+ Linux Project.
+
+ Linux is a registered trademark of Linus Torvalds.
+
+ Other company, product, and service names may be
+ trademarks or service marks of others.
+
+=======================================================================
+
+5.0 Disclaimer
+----------------
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND
+ PARTICULARLY THE NON-INFRINGEMENT OF ANY THIRD PARTY'S
+ INTELLECTUAL PROPERTY RIGHTS ARE DISCLAIMED. IN NO EVENT
+ SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGE.
+
diff --git a/tools/findif.c b/tools/findif.c
new file mode 100644
index 0000000..a25395f
--- /dev/null
+++ b/tools/findif.c
@@ -0,0 +1,845 @@
+/*
+ * findif.c: Finds an interface which can route a given address
+ *
+ * It's really simple to write in C, but hard to write in the shell...
+ *
+ * This code is dependent on IPV4 addressing conventions...
+ * Sorry.
+ *
+ * Copyright (C) 2000 Alan Robertson <alanr@unix.sh>
+ * Copyright (C) 2001 Matt Soffen <matt@soffen.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ *
+ ***********************************************************
+ *
+ * All our arguments come through the environment as OCF
+ * environment variables as below:
+ *
+ * OCF_RESKEY_ip
+ * OCF_RESKEY_broadcast
+ * OCF_RESKEY_nic
+ * OCF_RESKEY_cidr_netmask
+ *
+ * If the CIDR netmask is omitted, we choose the netmask associated with
+ * the route we selected.
+ *
+ * If the broadcast address was omitted, we assume the highest address
+ * in the subnet.
+ *
+ * If the interface is omitted, we choose the interface associated with
+ * the route we selected.
+ *
+ *
+ * See http://www.doom.net/docs/netmask.html for a table explaining
+ * CIDR address format and their relationship to life, the universe
+ * and everything.
+ *
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#ifdef __linux__
+#undef __OPTIMIZE__
+/*
+ * This gets rid of some silly -Wtraditional warnings on Linux
+ * because the netinet header has some slightly funky constants
+ * in it.
+ */
+#endif /* __linux__ */
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <agent_config.h>
+#include <config.h>
+
+#define DEBUG 0
+#define EOS '\0'
+#define PROCROUTE "/proc/net/route"
+#define ROUTEPARM "-n get"
+
+#ifndef HAVE_STRNLEN
+/* Any system that don't provide strnlen() only has itself to blame */
+#define strnlen(str, max) strlen(str)
+#endif
+
+/*
+ * "route -n get iii.jjj.kkk.lll" can, on Solaris at least,
+ * return the word "default" as the value from "mask" and "dest",
+ * typically if the host is remote, reached over a default route.
+ * We should probably treat such a mask as "0.0.0.0".
+ *
+ * Define "MASK_DEFAULT_TO_ZERO" to enable this interpretation.
+ *
+ * This is better for Solaris and is probably suitable (or irrelevant)
+ * for others OSes also. But if it breaks another OS, then reduce the
+ * "hash-if 1" below to exclude that OS.
+ * (David Lee, Jan 2006)
+ */
+#if 1
+# define MASK_DEFAULT_TO_ZERO
+#endif
+
+static int OutputInCIDR=0;
+
+
+/*
+ * Different OSes offer different mechnisms to obtain this information.
+ * Not all this can be determined at configure-time; need a run-time element.
+ *
+ * typedef ... SearchRoute ...:
+ * For routines that interface on these mechanisms.
+ * Return code:
+ * <0: mechanism invalid, so try next mechanism
+ * 0: mechanism worked: good answer
+ * >0: mechanism worked: bad answer
+ * On non-zero, errmsg may have been filled with an error message
+ */
+typedef int SearchRoute (char *address, struct in_addr *in
+, struct in_addr *addr_out, char *best_if, size_t best_iflen
+, unsigned long *best_netmask, char *errmsg
+, int errmsglen);
+
+static SearchRoute SearchUsingProcRoute;
+static SearchRoute SearchUsingRouteCmd;
+
+static SearchRoute *search_mechs[] = {
+ &SearchUsingProcRoute,
+ &SearchUsingRouteCmd,
+ NULL
+};
+
+void GetAddress (char **address, char **netmaskbits
+, char **bcast_arg, char **if_specified);
+
+int ConvertNetmaskBitsToInt(char *netmaskbits);
+
+void ValidateNetmaskBits(int bits, unsigned long *netmask);
+
+int ValidateIFName (const char *ifname, struct ifreq *ifr);
+
+int netmask_bits (unsigned long netmask);
+
+char * get_first_loopback_netdev(char * ifname);
+int is_loopback_interface(char * ifname);
+char * get_ifname(char * buf, char * ifname);
+
+int ConvertQuadToInt(char *dest);
+
+static const char *cmdname = "findif";
+#define OCF_SUCCESS 0
+#define OCF_ERR_GENERIC 1
+#define OCF_ERR_ARGS 2
+#define OCF_ERR_UNIMPLEMENTED 3
+#define OCF_ERR_PERM 4
+#define OCF_ERR_INSTALLED 5
+#define OCF_ERR_CONFIGURED 6
+#define OCF_NOT_RUNNING 7
+void usage(int ec);
+
+#define PATH_PROC_NET_DEV "/proc/net/dev"
+#define DELIM '/'
+#define BAD_BROADCAST (0L)
+#define MAXSTR 128
+
+static int
+SearchUsingProcRoute (char *address, struct in_addr *in
+, struct in_addr *addr_out, char *best_if, size_t best_iflen
+, unsigned long *best_netmask
+, char *errmsg, int errmsglen)
+{
+ unsigned long flags, refcnt, use, gw, mask;
+ unsigned long dest;
+ long metric = LONG_MAX;
+ long best_metric = LONG_MAX;
+ int rc = OCF_SUCCESS;
+
+ char buf[2048];
+ char interface[MAXSTR];
+ FILE *routefd = NULL;
+
+ if ((routefd = fopen(PROCROUTE, "r")) == NULL) {
+ snprintf(errmsg, errmsglen
+ , "Cannot open %s for reading"
+ , PROCROUTE);
+ rc = OCF_ERR_GENERIC; goto out;
+ }
+
+ /* Skip first (header) line */
+ if (fgets(buf, sizeof(buf), routefd) == NULL) {
+ snprintf(errmsg, errmsglen
+ , "Cannot skip first line from %s"
+ , PROCROUTE);
+ rc = OCF_ERR_GENERIC; goto out;
+ }
+ *best_netmask = 0;
+ while (fgets(buf, sizeof(buf), routefd) != NULL) {
+ if (sscanf(buf, "%[^\t]\t%lx%lx%lx%lx%lx%lx%lx"
+ , interface, &dest, &gw, &flags, &refcnt, &use
+ , &metric, &mask)
+ != 8) {
+ snprintf(errmsg, errmsglen, "Bad line in %s: %s"
+ , PROCROUTE, buf);
+ rc = OCF_ERR_GENERIC; goto out;
+ }
+ if ( (in->s_addr&mask) == (in_addr_t)(dest&mask)
+ && metric <= best_metric && mask >= *best_netmask) {
+ best_metric = metric;
+ *best_netmask = mask;
+ strncpy(best_if, interface, best_iflen);
+ }
+ }
+
+ if (best_metric == LONG_MAX) {
+ snprintf(errmsg, errmsglen, "No route to %s\n", address);
+ rc = OCF_ERR_GENERIC;
+ }
+
+ out:
+ if (routefd) {
+ fclose(routefd);
+ }
+
+ return(rc);
+}
+
+static int
+SearchUsingRouteCmd (char *address, struct in_addr *in
+, struct in_addr *addr_out, char *best_if, size_t best_iflen
+, unsigned long *best_netmask
+, char *errmsg, int errmsglen)
+{
+ char mask[20];
+ char routecmd[MAXSTR];
+ int best_metric = INT_MAX;
+ char buf[2048];
+ char interface[MAXSTR];
+ char *cp, *sp;
+ int done = 0;
+ FILE *routefd = NULL;
+ uint32_t maskbits;
+
+
+ /* Open route and get the information */
+ snprintf (routecmd, sizeof(routecmd), "%s %s %s"
+ , ROUTE, ROUTEPARM, address);
+ routefd = popen (routecmd, "r");
+ if (routefd == NULL)
+ return (OCF_ERR_GENERIC);
+ mask[0] = EOS;
+ interface[0] = EOS;
+
+
+ while ((done < 3) && fgets(buf, sizeof(buf), routefd)) {
+ int buflen = strnlen(buf, sizeof(buf));
+ /*cp = buf;*/
+
+ sp = buf + buflen;
+ while (sp!=buf && isspace((int)*(sp-1))) {
+ --sp;
+ }
+ *sp = EOS;
+
+ if (strstr (buf, "mask:")) {
+ /*strsep(&cp, ":");cp++;*/
+ strtok(buf, ":");
+ cp = strtok(NULL, ":");
+ if (cp) {
+ cp++;
+ strncpy(mask, cp, sizeof(mask) - 1);
+ *(mask + sizeof(mask) - 1) = '\0';
+ done++;
+ }
+ }
+
+ if (strstr (buf, "interface:")) {
+ /*strsep(&cp, ":");cp++;*/
+ strtok(buf, ":");
+ cp = strtok(NULL, ":");
+ if (cp) {
+ cp++;
+ strncpy(interface, cp, sizeof(interface) - 1);
+ *(interface + sizeof(interface) - 1) = '\0';
+ done++;
+ }
+ }
+ }
+ pclose(routefd);
+
+ /*
+ * Check to see if mask isn't available. It may not be
+ * returned if multiple IP's are defined.
+ * use 255.255.255.255 for mask then
+ */
+ /* I'm pretty sure this is the wrong behavior...
+ * I think the right behavior is to declare an error and give up.
+ * The admin didn't define his routes correctly. Fix them.
+ * It's useless to take over an IP address with no way to
+ * return packets to the originator. Without the right subnet
+ * mask, you can't reply to any packets you receive.
+ */
+ if (strnlen(mask, sizeof(mask)) == 0) {
+ strncpy (mask, "255.255.255.255", sizeof(mask));
+ }
+
+ /*
+ * Solaris (at least) can return the word "default" for mask and dest.
+ * For the moment, let's interpret this as:
+ * mask: 0.0.0.0
+ * This was manifesting itself under "BasicSanityCheck", which tries
+ * to use a remote IP number; these typically use the "default" route.
+ * Better schemes are warmly invited...
+ */
+#ifdef MASK_DEFAULT_TO_ZERO
+ if (strncmp(mask, "default", sizeof("default")) == 0) {
+ strncpy (mask, "0.0.0.0", sizeof(mask));
+ }
+#endif
+
+ if (inet_pton(AF_INET, mask, &maskbits) <= 0) {
+ snprintf(errmsg, errmsglen,
+ "mask [%s] not valid.", mask);
+ return(OCF_ERR_CONFIGURED);
+ }
+
+ if (inet_pton(AF_INET, address, addr_out) <= 0) {
+ snprintf(errmsg, errmsglen
+ , "IP address [%s] not valid.", address);
+ return(OCF_ERR_CONFIGURED);
+ }
+
+ if ((in->s_addr & maskbits) == (addr_out->s_addr & maskbits)) {
+ if (interface[0] == EOS) {
+ snprintf(errmsg, errmsglen, "No interface found.");
+ return(OCF_ERR_GENERIC);
+ }
+ best_metric = 0;
+ *best_netmask = maskbits;
+ strncpy(best_if, interface, best_iflen);
+ }
+
+ if (best_metric == INT_MAX) {
+ snprintf(errmsg, errmsglen, "No route to %s\n", address);
+ return(OCF_ERR_GENERIC);
+ }
+
+ return (OCF_SUCCESS);
+}
+
+/*
+ * Getaddress gets all its real parameters from the OCF environment
+ * variables that its callers already use.
+ */
+void
+GetAddress (char **address, char **netmaskbits
+, char **bcast_arg, char **if_specified)
+{
+ /*
+ * Here are out input environment variables:
+ *
+ * OCF_RESKEY_ip ip address
+ * OCF_RESKEY_cidr_netmask netmask of interface
+ * OCF_RESKEY_broadcast broadcast address for interface
+ * OCF_RESKEY_nic interface to assign to
+ *
+ */
+ *address = getenv("OCF_RESKEY_ip");
+ *netmaskbits = getenv("OCF_RESKEY_cidr_netmask");
+ if (*netmaskbits == NULL || **netmaskbits == EOS) {
+ *netmaskbits = getenv("OCF_RESKEY_netmask");
+ }
+ *bcast_arg = getenv("OCF_RESKEY_broadcast");
+ *if_specified = getenv("OCF_RESKEY_nic");
+}
+
+int
+ConvertNetmaskBitsToInt(char *netmaskbits)
+{
+ size_t nmblen = strnlen(netmaskbits, 3);
+
+ /* Maximum netmask is 32 */
+
+ if (nmblen > 2 || nmblen == 0
+ || (strspn(netmaskbits, "0123456789") != nmblen))
+ return -1;
+ else
+ return atoi(netmaskbits);
+}
+
+void
+ValidateNetmaskBits(int bits, unsigned long *netmask)
+{
+ /* Maximum netmask is 32 */
+
+ if (bits < 1 || bits > 32) {
+ fprintf(stderr
+ , "Invalid netmask specification [%d]"
+ , bits);
+ usage(OCF_ERR_CONFIGURED);
+ /*not reached */
+ }
+
+ bits = 32 - bits;
+ *netmask = (1L<<(bits))-1L;
+ *netmask = ((~(*netmask))&0xffffffffUL);
+ *netmask = htonl(*netmask);
+}
+
+int
+ValidateIFName(const char *ifname, struct ifreq *ifr)
+{
+ int skfd = -1;
+ char *colonptr;
+
+ if ( (skfd = socket(PF_INET, SOCK_DGRAM, 0)) == -1 ) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return -2;
+ }
+
+ strncpy(ifr->ifr_name, ifname, IFNAMSIZ - 1);
+ *(ifr->ifr_name + sizeof(ifr->ifr_name) - 1) = '\0';
+
+ /* Contain a ":"? Probably an error, but treat as warning at present */
+ if ((colonptr = strchr(ifname, ':')) != NULL) {
+ fprintf(stderr, "%s: warning: name may be invalid\n",
+ ifr->ifr_name);
+ }
+
+ if (ioctl(skfd, SIOCGIFFLAGS, ifr) < 0) {
+ fprintf(stderr, "%s: unknown interface: %s\n"
+ , ifr->ifr_name, strerror(errno));
+ close(skfd);
+ /* return -1 only if ifname is known to be invalid */
+ return -1;
+ }
+ close(skfd);
+ return 0;
+}
+
+int
+netmask_bits(unsigned long netmask)
+{
+ int j;
+
+ netmask = netmask & 0xFFFFFFFFUL;
+
+ for (j=0; j <= 32; ++j) {
+ if ((netmask >> j)&0x1) {
+ break;
+ }
+ }
+
+ return 32 - j;
+}
+
+char *
+get_first_loopback_netdev(char * output)
+{
+ char buf[512];
+ FILE * fd = NULL;
+ char *rc = NULL;
+
+ if (!output) {
+ fprintf(stderr, "output buf is a null pointer.\n");
+ goto out;
+ }
+
+ fd = fopen(PATH_PROC_NET_DEV, "r");
+ if (!fd) {
+ fprintf(stderr, "Warning: cannot open %s (%s).\n",
+ PATH_PROC_NET_DEV, strerror(errno));
+ goto out;
+ }
+
+ /* Skip the first two lines */
+ if (!fgets(buf, sizeof(buf), fd) || !fgets(buf, sizeof(buf), fd)) {
+ fprintf(stderr, "Warning: cannot read header from %s.\n",
+ PATH_PROC_NET_DEV);
+ goto out;
+ }
+
+ while (fgets(buf, sizeof(buf), fd)) {
+ char name[IFNAMSIZ];
+ if (NULL == get_ifname(buf, name)) {
+ /* Maybe somethin is wrong, anyway continue */
+ continue;
+ }
+ if (is_loopback_interface(name)) {
+ strncpy(output, name, IFNAMSIZ);
+ rc = output;
+ goto out;
+ }
+ }
+
+out:
+ if (fd) {
+ fclose(fd);
+ }
+ return rc;
+}
+
+int
+is_loopback_interface(char * ifname)
+{
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ if (ValidateIFName(ifname, &ifr) < 0)
+ return 0;
+
+ if (ifr.ifr_flags & IFF_LOOPBACK) {
+ /* this is a loopback device. */
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+char *
+get_ifname(char * buf, char * ifname)
+{
+ char * start, * end, * buf_border;
+
+ buf_border = buf + strnlen(buf, 512);
+
+ start = buf;
+ while (isspace((int) *start) && (start != buf_border)) {
+ start++;
+ }
+ end = start;
+ while ((*end != ':') && (end != buf_border)) {
+ end++;
+ }
+
+ if ( start == buf_border || end == buf_border ) {
+ /* Over the border of buf */
+ return NULL;
+ }
+
+ *end = '\0';
+ strncpy(ifname, start, IFNAMSIZ);
+
+ return ifname;
+}
+
+int ConvertQuadToInt(char *dest)
+{
+ struct in_addr ad;
+
+ if (inet_pton(AF_INET, dest, &ad) <= 0)
+ return -1;
+
+ return netmask_bits(ntohl(ad.s_addr));
+}
+
+int
+main(int argc, char ** argv) {
+
+ char * address = NULL;
+ char * bcast_arg = NULL;
+ char * netmaskbits = NULL;
+ struct in_addr in;
+ struct in_addr addr_out;
+ unsigned long netmask = 0;
+ char best_if[MAXSTR];
+ char * if_specified = NULL;
+ struct ifreq ifr;
+ unsigned long best_netmask = UINT_MAX;
+ int argerrs = 0;
+ int nmbits;
+
+ cmdname=argv[0];
+
+
+ memset(&addr_out, 0, sizeof(addr_out));
+ memset(&in, 0, sizeof(in));
+ memset(&ifr, 0, sizeof(ifr));
+
+ switch (argc) {
+ case 1: /* No -C argument */
+ break;
+ case 2: /* Hopefully a -C argument */
+ if (strncmp(argv[1], "-C", sizeof("-C")) != 0) {
+ argerrs=1;
+ }
+ OutputInCIDR=1;
+ break;
+ default:
+ argerrs=1;
+ break;
+ }
+ if (argerrs) {
+ usage(OCF_ERR_ARGS);
+ /* not reached */
+ return(1);
+ }
+
+ GetAddress (&address, &netmaskbits, &bcast_arg
+ , &if_specified);
+ if (address == NULL || *address == EOS) {
+ fprintf(stderr, "ERROR: IP address parameter is mandatory.");
+ usage(OCF_ERR_CONFIGURED);
+ /* not reached */
+ }
+
+ /* Is the IP address we're supposed to find valid? */
+
+ if (inet_pton(AF_INET, address, (void *)&in) <= 0) {
+ fprintf(stderr, "IP address [%s] not valid.", address);
+ usage(OCF_ERR_CONFIGURED);
+ /* not reached */
+ }
+
+ if (netmaskbits != NULL && *netmaskbits != EOS) {
+ if (strchr(netmaskbits, '.') != NULL) {
+ nmbits = ConvertQuadToInt(netmaskbits);
+ fprintf(stderr, "Converted dotted-quad netmask to CIDR as: %d\n", nmbits);
+ }else{
+ nmbits = ConvertNetmaskBitsToInt(netmaskbits);
+ }
+
+ if (nmbits < 0) {
+ fprintf(stderr, "Invalid netmask specification"
+ " [%s]", netmaskbits);
+ usage(OCF_ERR_CONFIGURED);
+ /*not reached */
+ }
+
+ /* Validate the netmaskbits field */
+ ValidateNetmaskBits (nmbits, &netmask);
+ }
+
+
+ if (if_specified != NULL && *if_specified != EOS) {
+ if(ValidateIFName(if_specified, &ifr) < 0) {
+ usage(OCF_ERR_CONFIGURED);
+ /* not reached */
+ }
+ strncpy(best_if, if_specified, sizeof(best_if) - 1);
+ *(best_if + sizeof(best_if) - 1) = '\0';
+ }else{
+ SearchRoute **sr = search_mechs;
+ char errmsg[MAXSTR] = "No valid mechanisms";
+ int rc = OCF_ERR_GENERIC;
+
+ strcpy(best_if, "UNKNOWN");
+
+ while (*sr) {
+ errmsg[0] = '\0';
+ rc = (*sr) (address, &in, &addr_out, best_if
+ , sizeof(best_if)
+ , &best_netmask, errmsg, sizeof(errmsg));
+ if (!rc) { /* Mechanism worked */
+ break;
+ }
+ sr++;
+ }
+ if (rc != 0) { /* No route, or all mechanisms failed */
+ if (*errmsg) {
+ fprintf(stderr, "%s", errmsg);
+ }
+ return(rc);
+ }
+ }
+
+ if (netmaskbits) {
+ best_netmask = netmask;
+ }else if (best_netmask == 0L) {
+ /*
+ On some distributions, there is no loopback related route
+ item, this leads to the error here.
+ My fix may be not good enough, please FIXME
+ */
+ if (0 == strncmp(address, "127", 3)) {
+ if (NULL != get_first_loopback_netdev(best_if)) {
+ best_netmask = 0x000000ff;
+ } else {
+ fprintf(stderr, "No loopback interface found.\n");
+ return(OCF_ERR_GENERIC);
+ }
+ } else {
+ fprintf(stderr
+ , "ERROR: Cannot use default route w/o netmask [%s]\n"
+ , address);
+ return(OCF_ERR_GENERIC);
+ }
+ }
+
+ /* Did they tell us the broadcast address? */
+
+ if (bcast_arg && *bcast_arg != EOS) {
+ /* Yes, they gave us a broadcast address.
+ * It at least should be a valid IP address
+ */
+ struct in_addr bcast_addr;
+ if (inet_pton(AF_INET, bcast_arg, (void *)&bcast_addr) <= 0) {
+ fprintf(stderr, "Invalid broadcast address [%s].", bcast_arg);
+ usage(OCF_ERR_CONFIGURED);
+ /* not reached */
+ }
+
+ best_netmask = htonl(best_netmask);
+ if (!OutputInCIDR) {
+ printf("%s\tnetmask %d.%d.%d.%d\tbroadcast %s\n"
+ , best_if
+ , (int)((best_netmask>>24) & 0xff)
+ , (int)((best_netmask>>16) & 0xff)
+ , (int)((best_netmask>>8) & 0xff)
+ , (int)(best_netmask & 0xff)
+ , bcast_arg);
+ }else{
+ printf("%s\tnetmask %d\tbroadcast %s\n"
+ , best_if
+ , netmask_bits(best_netmask)
+ , bcast_arg);
+ }
+ }else{
+ /* No, we use a common broadcast address convention */
+ unsigned long def_bcast;
+
+ /* Common broadcast address */
+ def_bcast = (in.s_addr | (~best_netmask));
+#if DEBUG
+ fprintf(stderr, "best_netmask = %08lx, def_bcast = %08lx\n"
+ , best_netmask, def_bcast);
+#endif
+
+ /* Make things a bit more machine-independent */
+ best_netmask = htonl(best_netmask);
+ def_bcast = htonl(def_bcast);
+ if (!OutputInCIDR) {
+ printf("%s\tnetmask %d.%d.%d.%d\tbroadcast %d.%d.%d.%d\n"
+ , best_if
+ , (int)((best_netmask>>24) & 0xff)
+ , (int)((best_netmask>>16) & 0xff)
+ , (int)((best_netmask>>8) & 0xff)
+ , (int)(best_netmask & 0xff)
+ , (int)((def_bcast>>24) & 0xff)
+ , (int)((def_bcast>>16) & 0xff)
+ , (int)((def_bcast>>8) & 0xff)
+ , (int)(def_bcast & 0xff));
+ }else{
+ printf("%s\tnetmask %d\tbroadcast %d.%d.%d.%d\n"
+ , best_if
+ , netmask_bits(best_netmask)
+ , (int)((def_bcast>>24) & 0xff)
+ , (int)((def_bcast>>16) & 0xff)
+ , (int)((def_bcast>>8) & 0xff)
+ , (int)(def_bcast & 0xff));
+ }
+ }
+ return(0);
+}
+
+void
+usage(int ec)
+{
+ fprintf(stderr, "\n"
+ "%s version 2.99.1 Copyright Alan Robertson\n"
+ "\n"
+ "Usage: %s [-C]\n"
+ "Options:\n"
+ " -C: Output netmask as the number of bits rather "
+ "than as 4 octets.\n"
+ "Environment variables:\n"
+ "OCF_RESKEY_ip ip address (mandatory!)\n"
+ "OCF_RESKEY_cidr_netmask netmask of interface\n"
+ "OCF_RESKEY_broadcast broadcast address for interface\n"
+ "OCF_RESKEY_nic interface to assign to\n"
+ , cmdname, cmdname);
+ exit(ec);
+}
+
+/*
+Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
+eth0 33D60987 00000000 0005 0 0 0 FFFFFFFF 0 0 0
+eth0 00D60987 00000000 0001 0 0 0 00FFFFFF 0 0 0
+lo 0000007F 00000000 0001 0 0 0 000000FF 0 0 0
+eth0 00000000 FED60987 0003 0 0 0 00000000 0 0 0
+
+netstat -rn outpug from RedHat Linux 6.0
+Kernel IP routing table
+Destination Gateway Genmask Flags MSS Window irtt Iface
+192.168.85.2 0.0.0.0 255.255.255.255 UH 0 0 0 eth1
+10.0.0.2 0.0.0.0 255.255.255.255 UH 0 0 0 eth2
+208.132.134.61 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
+208.132.134.32 0.0.0.0 255.255.255.224 U 0 0 0 eth0
+192.168.85.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
+10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth2
+127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
+0.0.0.0 208.132.134.33 0.0.0.0 UG 0 0 0 eth0
+
+|--------------------------------------------------------------------------------
+netstat -rn output from FreeBSD 3.3
+Routing tables
+
+Internet:
+Destination Gateway Flags Refs Use Netif Expire
+default 209.61.94.161 UGSc 3 8 pn0
+192.168 link#1 UC 0 0 xl0
+192.168.0.2 0:60:8:a4:91:fd UHLW 0 38 lo0
+192.168.0.255 ff:ff:ff:ff:ff:ff UHLWb 1 7877 xl0
+209.61.94.160/29 link#2 UC 0 0 pn0
+209.61.94.161 0:a0:cc:26:c2:ea UHLW 6 17265 pn0 1105
+209.61.94.162 0:a0:cc:27:1c:fb UHLW 1 568 pn0 1098
+209.61.94.163 0:a0:cc:29:1f:86 UHLW 0 4749 pn0 1095
+209.61.94.166 0:a0:cc:27:2d:e1 UHLW 0 12 lo0
+209.61.94.167 ff:ff:ff:ff:ff:ff UHLWb 0 10578 pn0
+
+|--------------------------------------------------------------------------------
+netstat -rn output from FreeBSD 4.2
+Routing tables
+
+Internet:
+Destination Gateway Flags Refs Use Netif Expire
+default 64.65.195.1 UGSc 1 11 dc0
+64.65.195/24 link#1 UC 0 0 dc0 =>
+64.65.195.1 0:3:42:3b:0:dd UHLW 2 0 dc0 1131
+64.65.195.184 0:a0:cc:29:1f:86 UHLW 2 18098 dc0 1119
+64.65.195.194 0:a0:cc:27:2d:e1 UHLW 3 335161 dc0 943
+64.65.195.200 52:54:0:db:33:b3 UHLW 0 13 dc0 406
+64.65.195.255 ff:ff:ff:ff:ff:ff UHLWb 1 584 dc0
+127.0.0.1 127.0.0.1 UH 0 0 lo0
+192.168/16 link#2 UC 0 0 vx0 =>
+192.168.0.1 0:20:af:e2:f0:36 UHLW 0 2 lo0
+192.168.255.255 ff:ff:ff:ff:ff:ff UHLWb 0 1 vx0
+
+Internet6:
+Destination Gateway Flags Netif Expire
+::1 ::1 UH lo0
+fe80::%dc0/64 link#1 UC dc0
+fe80::%vx0/64 link#2 UC vx0
+fe80::%lo0/64 fe80::1%lo0 Uc lo0
+ff01::/32 ::1 U lo0
+ff02::%dc0/32 link#1 UC dc0
+ff02::%vx0/32 link#2 UC vx0
+ff02::%lo0/32 fe80::1%lo0 UC lo0
+*/
+
diff --git a/tools/nfsconvert.in b/tools/nfsconvert.in
new file mode 100644
index 0000000..c58a16a
--- /dev/null
+++ b/tools/nfsconvert.in
@@ -0,0 +1,324 @@
+#!@PYTHON@ -tt
+"""
+Read in the deprecated /etc/sysconfig/nfs file and
+set the corresponding values in nfs.conf
+"""
+
+from __future__ import print_function
+import os
+import sys
+import getopt
+import subprocess
+import configparser
+
+CONF_NFS = '/etc/nfs.conf'
+CONF_IDMAP = '/etc/idmapd.conf'
+SYSCONF_NFS = '/etc/sysconfig/nfs'
+SYSCONF_BACKUP = ".rpmsave"
+CONF_TOOL = '/usr/sbin/nfsconf'
+
+# options for nfsd found in RPCNFSDARGS
+OPTS_NFSD = 'dH:p:rR:N:V:stTuUG:L:'
+LONG_NFSD = ['debug', 'host=', 'port=', 'rdma=', 'nfs-version=', 'no-nfs-version=',
+ 'tcp', 'no-tcp', 'udp', 'no-udp', 'grace-time=', 'lease-time=']
+CONV_NFSD = {'-d': (CONF_NFS, 'nfsd', 'debug', 'all'),
+ '-H': (CONF_NFS, 'nfsd', 'host', ','),
+ '-p': (CONF_NFS, 'nfsd', 'port', '$1'),
+ '-r': (CONF_NFS, 'nfsd', 'rdma', 'nfsrdma'),
+ '-R': (CONF_NFS, 'nfsd', 'rdma', '$1'),
+ '-N': (CONF_NFS, 'nfsd', 'vers$1', 'n'),
+ '-V': (CONF_NFS, 'nfsd', 'vers$1', 'y'),
+ '-t': (CONF_NFS, 'nfsd', 'tcp', '1'),
+ '-T': (CONF_NFS, 'nfsd', 'tcp', '0'),
+ '-u': (CONF_NFS, 'nfsd', 'udp', '1'),
+ '-U': (CONF_NFS, 'nfsd', 'udp', '0'),
+ '-G': (CONF_NFS, 'nfsd', 'grace-time', '$1'),
+ '-L': (CONF_NFS, 'nfsd', 'lease-time', '$1'),
+ '$1': (CONF_NFS, 'nfsd', 'threads', '$1'),
+ '--debug': (CONF_NFS, 'nfsd', 'debug', 'all'),
+ '--host': (CONF_NFS, 'nfsd', 'host', ','),
+ '--port': (CONF_NFS, 'nfsd', 'port', '$1'),
+ '--rdma': (CONF_NFS, 'nfsd', 'rdma', '$1'),
+ '--no-nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'n'),
+ '--nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'y'),
+ '--tcp': (CONF_NFS, 'nfsd', 'tcp', '1'),
+ '--no-tcp': (CONF_NFS, 'nfsd', 'tcp', '0'),
+ '--udp': (CONF_NFS, 'nfsd', 'udp', '1'),
+ '--no-udp': (CONF_NFS, 'nfsd', 'udp', '0'),
+ '--grace-time': (CONF_NFS, 'nfsd', 'grace-time', '$1'),
+ '--lease-time': (CONF_NFS, 'nfsd', 'lease-time', '$1'),
+ }
+
+# options for mountd found in RPCMOUNTDOPTS
+OPTS_MOUNTD = 'go:d:H:p:N:nrs:t:V:'
+LONG_MOUNTD = ['descriptors=', 'debug=', 'nfs-version=', 'no-nfs-version=',
+ 'port=', 'no-tcp', 'ha-callout=', 'state-directory-path=',
+ 'num-threads=', 'reverse-lookup', 'manage-gids', 'no-udp']
+
+CONV_MOUNTD = {'-g': (CONF_NFS, 'mountd', 'manage-gids', '1'),
+ '-o': (CONF_NFS, 'mountd', 'descriptors', '$1'),
+ '-d': (CONF_NFS, 'mountd', 'debug', '$1'),
+ '-H': (CONF_NFS, 'mountd', 'ha-callout', '$1'),
+ '-p': (CONF_NFS, 'mountd', 'port', '$1'),
+ '-N': (CONF_NFS, 'nfsd', 'vers$1', 'n'),
+ '-V': (CONF_NFS, 'nfsd', 'vers$1', 'y'),
+ '-n': (CONF_NFS, 'nfsd', 'tcp', '0'),
+ '-s': (CONF_NFS, 'mountd', 'stat-directory-path', '$1'),
+ '-t': (CONF_NFS, 'mountd', 'threads', '$1'),
+ '-r': (CONF_NFS, 'mountd', 'reverse-lookup', '1'),
+ '-u': (CONF_NFS, 'nfsd', 'udp', '0'),
+ '--manage-gids': (CONF_NFS, 'mountd', 'manage-gids', '1'),
+ '--descriptors': (CONF_NFS, 'mountd', 'descriptors', '$1'),
+ '--debug': (CONF_NFS, 'mountd', 'debug', '$1'),
+ '--ha-callout': (CONF_NFS, 'mountd', 'ha-callout', '$1'),
+ '--port': (CONF_NFS, 'mountd', 'port', '$1'),
+ '--nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'y'),
+ '--no-nfs-version': (CONF_NFS, 'nfsd', 'vers$1', 'n'),
+ '--no-tcp': (CONF_NFS, 'nfsd', 'tcp', '0'),
+ '--state-directory-path': (CONF_NFS, 'mountd', 'state-directory-path', '$1'),
+ '--num-threads': (CONF_NFS, 'mountd', 'threads', '$1'),
+ '--reverse-lookup': (CONF_NFS, 'mountd', 'reverse-lookup', '1'),
+ '--no-udp': (CONF_NFS, 'nfsd', 'udp', '0'),
+ }
+
+# options for statd found in STATDARG
+OPTS_STATD = 'o:p:T:U:n:P:H:L'
+LONG_STATD = ['outgoing-port=', 'port=', 'name=', 'state-directory-path=',
+ 'ha-callout=', 'nlm-port=', 'nlm-udp-port=', 'no-notify']
+CONV_STATD = {'-o': (CONF_NFS, 'statd', 'outgoing-port', '$1'),
+ '-p': (CONF_NFS, 'statd', 'port', '$1'),
+ '-T': (CONF_NFS, 'lockd', 'port', '$1'),
+ '-U': (CONF_NFS, 'lockd', 'udp-port', '$1'),
+ '-n': (CONF_NFS, 'statd', 'name', '$1'),
+ '-P': (CONF_NFS, 'statd', 'state-directory-path', '$1'),
+ '-H': (CONF_NFS, 'statd', 'ha-callout', '$1'),
+ '-L': (CONF_NFS, 'statd', 'no-notify', '1'),
+ '--outgoing-port': (CONF_NFS, 'statd', 'outgoing-port', '$1'),
+ '--port': (CONF_NFS, 'statd', 'port', '$1'),
+ '--name': (CONF_NFS, 'statd', 'name', '$1'),
+ '--state-directory-path': (CONF_NFS, 'statd', 'state-directory-path', '$1'),
+ '--ha-callout': (CONF_NFS, 'statd', 'ha-callout', '$1'),
+ '--nlm-port': (CONF_NFS, 'lockd', 'port', '$1'),
+ '--nlm-udp-port': (CONF_NFS, 'lockd', 'udp-port', '$1'),
+ '--no-notify': (CONF_NFS, 'statd', 'no-notify', '1'),
+ }
+
+# options for sm-notify found in SMNOTIFYARGS
+OPTS_SMNOTIFY = 'dm:np:v:P:f'
+CONV_SMNOTIFY = {'-d': (CONF_NFS, 'sm-notify', 'debug', 'all'),
+ '-m': (CONF_NFS, 'sm-notify', 'retry-time', '$1'),
+ '-n': (CONF_NFS, 'sm-notify', 'update-state', '1'),
+ '-p': (CONF_NFS, 'sm-notify', 'outgoing-port', '$1'),
+ '-v': (CONF_NFS, 'sm-notify', 'outgoing-addr', '$1'),
+ '-f': (CONF_NFS, 'sm-notify', 'force', '1'),
+ '-P': (CONF_NFS, 'statd', 'state-directory-path', '$1'),
+ }
+
+# options for idmapd found in RPCIDMAPDARGS
+OPTS_IDMAPD = 'vp:CS'
+CONV_IDMAPD = {'-v': (CONF_IDMAP, 'general', 'verbosity', '+'),
+ '-p': (CONF_NFS, 'general', 'pipefs-directory', '$1'),
+ '-C': (CONF_IDMAP, 'general', 'client-only', '1'),
+ '-S': (CONF_IDMAP, 'general', 'server-only', '1'),
+ }
+
+# options for gssd found in RPCGSSDARGS
+OPTS_GSSD = 'Mnvrp:k:d:t:T:R:lD'
+CONV_GSSD = {'-M': (CONF_NFS, 'gssd', 'use-memcache', '1'),
+ '-n': (CONF_NFS, 'gssd', 'root_uses_machine_creds', '0'),
+ '-v': (CONF_NFS, 'gssd', 'verbosity', '+'),
+ '-r': (CONF_NFS, 'gssd', 'rpc-verbosity', '+'),
+ '-p': (CONF_NFS, 'general', 'pipefs-directory', '$1'),
+ '-k': (CONF_NFS, 'gssd', 'keytab-file', '$1'),
+ '-d': (CONF_NFS, 'gssd', 'cred-cache-directory', '$1'),
+ '-t': (CONF_NFS, 'gssd', 'context-timeout', '$1'),
+ '-T': (CONF_NFS, 'gssd', 'rpc-timeout', '$1'),
+ '-R': (CONF_NFS, 'gssd', 'preferred-realm', '$1'),
+ '-l': (CONF_NFS, 'gssd', 'limit-to-legacy-enctypes', '0'),
+ '-D': (CONF_NFS, 'gssd', 'avoid-dns', '0'),
+ }
+
+# options for blkmapd found in BLKMAPDARGS
+OPTS_BLKMAPD = ''
+CONV_BLKMAPD = {}
+
+# meta list of all the getopt lists
+GETOPT_MAPS = [('RPCNFSDARGS', OPTS_NFSD, LONG_NFSD, CONV_NFSD),
+ ('RPCMOUNTDOPTS', OPTS_MOUNTD, LONG_MOUNTD, CONV_MOUNTD),
+ ('STATDARG', OPTS_STATD, LONG_STATD, CONV_STATD),
+ ('STATDARGS', OPTS_STATD, LONG_STATD, CONV_STATD),
+ ('SMNOTIFYARGS', OPTS_SMNOTIFY, [], CONV_SMNOTIFY),
+ ('RPCIDMAPDARGS', OPTS_IDMAPD, [], CONV_IDMAPD),
+ ('RPCGSSDARGS', OPTS_GSSD, [], CONV_GSSD),
+ ('BLKMAPDARGS', OPTS_BLKMAPD, [], CONV_BLKMAPD),
+ ]
+
+# any fixups we need to apply first
+GETOPT_FIXUP = {'RPCNFSDARGS': ('--rdma', '--rdma=nfsrdma'),
+ }
+
+# map for all of the single option values
+VALUE_MAPS = {'LOCKD_TCPPORT': (CONF_NFS, 'lockd', 'port', '$1'),
+ 'LOCKD_UDPPORT': (CONF_NFS, 'lockd', 'udp-port', '$1'),
+ 'RPCNFSDCOUNT': (CONF_NFS, 'nfsd', 'threads', '$1'),
+ 'NFSD_V4_GRACE': (CONF_NFS, 'nfsd', 'grace-time', '$1'),
+ 'NFSD_V4_LEASE': (CONF_NFS, 'nfsd', 'lease-time', '$1'),
+ 'MOUNTD_PORT': (CONF_NFS, 'mountd', 'port', '$1'),
+ 'STATD_PORT': (CONF_NFS, 'statd', 'port', '$1'),
+ 'STATD_OUTGOING_PORT': (CONF_NFS, 'statd', 'outgoing-port', '$1'),
+ 'STATD_HA_CALLOUT': (CONF_NFS, 'statd', 'ha-callout', '$1'),
+ 'GSS_USE_PROXY': (CONF_NFS, 'gssd', 'use-gss-proxy', '$1')
+ }
+
+def eprint(*args, **kwargs):
+ """ Print error to stderr """
+ print(*args, file=sys.stderr, **kwargs)
+
+def makesub(param, value):
+ """ Variable substitution """
+ return param.replace('$1', value)
+
+def set_value(value, entry):
+ """ Set a configuration value by running nfsconf tool"""
+ cfile, section, tag, param = entry
+
+ tag = makesub(tag, value)
+ param = makesub(param, value)
+ if param == '+':
+ param = value
+ if param == ',':
+ param = value
+ args = [CONF_TOOL, "--file", cfile, "--set", section, tag, param]
+
+ try:
+ subprocess.check_output(args, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ print("Error running nfs-conf tool:\n %s" % (e.output.decode()))
+ print("Args: %s\n" % args)
+ raise Exception
+
+def convert_getopt(optname, options, optstring, longopts, conversions):
+ """ Parse option string into seperate config items
+
+ Take a getopt string and a table of conversions
+ parse it all and spit out the converted config
+
+ Keyword arguments:
+ options -- the argv string to convert
+ optstring -- getopt format option list
+ conversions -- table of translations
+ """
+ optcount = 0
+ try:
+ args = options.strip('\"').split()
+ if optname in GETOPT_FIXUP:
+ (k, v) = GETOPT_FIXUP[optname]
+ for i, opt in enumerate(args):
+ if opt == k:
+ args[i] = v
+ elif opt == '--':
+ break
+ optlist, optargs = getopt.gnu_getopt(args, optstring, longopts=longopts)
+ except getopt.GetoptError as err:
+ eprint(err)
+ raise Exception
+
+ setlist = {}
+ for (k, v) in optlist:
+ if k in conversions:
+ # it's already been set once
+ param = conversions[k][3]
+ tag = k + makesub(conversions[k][2], v)
+ if tag in setlist:
+ value = setlist[tag][0]
+ # is it a cummulative entry
+ if param == '+':
+ value = str(int(value) + 1)
+ if param == ',':
+ value += "," + v
+ else:
+ if param == '+':
+ value = "1"
+ elif param == ',':
+ value = v
+ else:
+ value = v
+ setlist[tag] = (value, conversions[k])
+ else:
+ if v:
+ eprint("Ignoring unrecognised option %s=%s in %s" % (k, v, optname))
+ else:
+ eprint("Ignoring unrecognised option %s in %s" % (k, optname))
+
+
+ for v, c in setlist.values():
+ try:
+ set_value(v, c)
+ optcount += 1
+ except Exception:
+ raise
+
+ i = 1
+ for o in optargs:
+ opname = '$' + str(i)
+ if opname in conversions:
+ try:
+ set_value(o, conversions[opname])
+ optcount += 1
+ except Exception:
+ raise
+ else:
+ eprint("Unrecognised trailing arguments")
+ raise Exception
+ i += 1
+
+ return optcount
+
+def map_values():
+ """ Main function """
+ mapcount = 0
+
+ # Lets load the old config
+ with open(SYSCONF_NFS) as cfile:
+ file_content = '[sysconf]\n' + cfile.read()
+ sysconfig = configparser.RawConfigParser()
+ sysconfig.read_string(file_content)
+
+ # Map all the getopt option lists
+ for (name, opts, lopts, conv) in GETOPT_MAPS:
+ if name in sysconfig['sysconf']:
+ try:
+ mapcount += convert_getopt(name, sysconfig['sysconf'][name], opts,
+ lopts, conv)
+ except Exception:
+ eprint("Error whilst converting %s to nfsconf options." % (name))
+ raise
+
+ # Map the single value options
+ for name, opts in VALUE_MAPS.items():
+ if name in sysconfig['sysconf']:
+ try:
+ value = sysconfig['sysconf'][name]
+ set_value(value.strip('\"'), opts)
+ mapcount += 1
+ except Exception:
+ raise
+
+ # All went well, move aside the old file
+ # but dont bother if there were no changes and
+ # an old config file already exists
+ backupfile = SYSCONF_NFS + SYSCONF_BACKUP
+ if mapcount > 0 or not os.path.exists(backupfile):
+ try:
+ os.replace(SYSCONF_NFS, backupfile)
+ except OSError as err:
+ eprint("Error moving old config %s: %s" % (SYSCONF_NFS, err))
+ raise
+
+# Main routine
+try:
+ map_values()
+except Exception as e:
+ eprint(e)
+ eprint("Conversion failed. Please correct the error and try again.")
+ exit(1)
diff --git a/tools/ocf-tester.8 b/tools/ocf-tester.8
new file mode 100644
index 0000000..3f39828
--- /dev/null
+++ b/tools/ocf-tester.8
@@ -0,0 +1,48 @@
+.TH OCF-TESTER "8" "July 2022" "Tool for testing if a cluster resource is OCF compliant" "System Administration Utilities"
+.SH NAME
+ocf-tester \- Part of the Linux-HA project
+.SH SYNOPSIS
+.B ocf-tester
+[\fI-hvqdX\fR] \fI-n resource_name \fR[\fI-o name=value\fR]\fI* /full/path/to/resource/agent\fR
+.SH DESCRIPTION
+Tool for testing if a cluster resource is OCF compliant
+.SH OPTIONS
+.TP
+\fB\-h\fR
+This text
+.TP
+\fB\-v\fR
+Be verbose while testing
+.TP
+\fB\-q\fR
+Be quiet while testing
+.TP
+\fB\-d\fR
+Turn on RA debugging
+.TP
+\fB\-n\fR name
+Name of the resource
+.TP
+\fB\-o\fR name=value
+Name and value of any parameters required by the agent
+.TP
+\fB\-h\fR
+This text
+.TP
+\fB\-v\fR
+Be verbose while testing
+.TP
+\fB\-q\fR
+Be quiet while testing
+.TP
+\fB\-d\fR
+Turn on RA debugging
+.TP
+\fB\-X\fR
+Turn on RA tracing (expect large output)
+.TP
+\fB\-n\fR name
+Name of the resource
+.TP
+\fB\-o\fR name=value
+Name and value of any parameters required by the agent
diff --git a/tools/ocf-tester.in b/tools/ocf-tester.in
new file mode 100755
index 0000000..cfdfd70
--- /dev/null
+++ b/tools/ocf-tester.in
@@ -0,0 +1,351 @@
+#!/bin/sh
+#
+# $Id: ocf-tester,v 1.2 2006/08/14 09:38:20 andrew Exp $
+#
+# Copyright (c) 2006 Novell Inc, Andrew Beekhof
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+#
+
+DATADIR=@datadir@
+METADATA_LINT="xmllint --noout --valid -"
+
+# set some common meta attributes, which are expected to be
+# present by resource agents
+export OCF_RESKEY_CRM_meta_timeout=20000 # 20 seconds timeout
+export OCF_RESKEY_CRM_meta_interval=10000 # reset this for probes
+
+num_errors=0
+
+info() {
+ [ "$quiet" -eq 1 ] && return
+ echo "$*"
+}
+debug() {
+ [ "$verbose" -eq 0 ] && return
+ echo "$*"
+}
+usage() {
+ # make sure to output errors on stderr
+ [ "x$1" = "x0" ] || exec >&2
+
+ echo "Tool for testing if a cluster resource is OCF compliant"
+ echo ""
+ echo "Usage: ocf-tester [-hvqdX] -n resource_name [-o name=value]* /full/path/to/resource/agent"
+ echo ""
+ echo "Options:"
+ echo " -h This text"
+ echo " -v Be verbose while testing"
+ echo " -q Be quiet while testing"
+ echo " -d Turn on RA debugging"
+ echo " -X Turn on RA tracing (expect large output)"
+ echo " -n name Name of the resource"
+ echo " -o name=value Name and value of any parameters required by the agent"
+ exit $1
+}
+
+assert() {
+ rc=$1; shift
+ target=$1; shift
+ msg=$1; shift
+ local targetrc matched
+
+ if [ $# = 0 ]; then
+ exit_code=0
+ else
+ exit_code=$1; shift
+ fi
+
+ for targetrc in `echo $target | tr ':' ' '`; do
+ [ $rc -eq $targetrc ] && matched=1
+ done
+ if [ "$matched" != 1 ]; then
+ num_errors=`expr $num_errors + 1`
+ echo "* rc=$rc: $msg"
+ if [ $exit_code != 0 ]; then
+ [ -n "$command_output" ] && cat<<EOF
+$command_output
+EOF
+ echo "Aborting tests"
+ exit $exit_code
+ fi
+ fi
+ command_output=""
+}
+
+done=0
+ra_args=""
+verbose=0
+quiet=0
+while test "$done" = "0"; do
+ case "$1" in
+ -n) OCF_RESOURCE_INSTANCE=$2; ra_args="$ra_args OCF_RESOURCE_INSTANCE=$2"; shift; shift;;
+ -o) name=${2%%=*}; value=${2#*=};
+ lrm_ra_args="$lrm_ra_args $2";
+ ra_args="$ra_args OCF_RESKEY_$name='$value'"; shift; shift;;
+ -v) verbose=1; shift;;
+ -d) export HA_debug=1; shift;;
+ -X) export OCF_TRACE_RA=1; verbose=1; shift;;
+ -q) quiet=1; shift;;
+ -?|--help) usage 0;;
+ --version) echo "@PACKAGE_VERSION@"; exit 0;;
+ -*) echo "unknown option: $1" >&2; usage 1;;
+ *) done=1;;
+ esac
+done
+
+if [ "x" = "x$OCF_ROOT" ]; then
+ if [ -d /usr/lib/ocf ]; then
+ export OCF_ROOT=/usr/lib/ocf
+ else
+ echo "You must supply the location of OCF_ROOT (common location is /usr/lib/ocf)" >&2
+ usage 1
+ fi
+fi
+
+if [ "x" = "x$OCF_RESOURCE_INSTANCE" ]; then
+ echo "You must give your resource a name, set OCF_RESOURCE_INSTANCE" >&2
+ usage 1
+fi
+
+agent=$1
+if [ ! -e $agent ]; then
+ echo "You must provide the full path to your resource agent" >&2
+ usage 1
+fi
+installed_rc=5
+stopped_rc=7
+has_demote=1
+has_promote=1
+
+test_permissions() {
+ action=meta-data
+ debug ${1:-"Testing permissions with uid nobody"}
+ su nobody -s /bin/sh -c "$agent $action" > /dev/null
+}
+
+test_metadata() {
+ action=meta-data
+ msg=${1:-"Testing: $action"}
+ debug $msg
+ $agent $action | (cd $DATADIR/resource-agents && $METADATA_LINT)
+ rc=$?
+ #echo rc: $rc
+ return $rc
+}
+
+test_command() {
+ action=$1; shift
+ export __OCF_ACTION=$action
+ msg=${1:-"Testing: $action"}
+ #echo Running: "export $ra_args; $agent $action 2>&1 > /dev/null"
+ if [ $verbose -eq 0 ]; then
+ command_output=`$agent $action 2>&1`
+ else
+ debug $msg
+ $agent $action
+ fi
+ rc=$?
+ #echo rc: $rc
+ return $rc
+}
+
+# Begin tests
+info "Beginning tests for $agent..."
+
+if [ ! -f $agent ]; then
+ assert 7 0 "Could not find file: $agent"
+fi
+
+if [ `id -u` = 0 ]; then
+ test_permissions
+ assert $? 0 "Your agent has too restrictive permissions: should be 755"
+else
+ echo "WARN: Can't check agent's permissions because we're not root; they should be 755"
+fi
+
+test_metadata
+assert $? 0 "Your agent produces meta-data which does not conform to ra-api-1.dtd"
+
+OCF_TESTER_FAIL_HAVE_BINARY=1
+export OCF_TESTER_FAIL_HAVE_BINARY
+test_command meta-data
+rc=$?
+if [ $rc -eq 3 ]; then
+ assert $rc 0 "Your agent does not support the meta-data action"
+else
+ assert $rc 0 "The meta-data action cannot fail and must return 0"
+fi
+unset OCF_TESTER_FAIL_HAVE_BINARY
+
+ra_args="export $ra_args"
+eval $ra_args
+test_command validate-all
+rc=$?
+if [ $rc -eq 3 ]; then
+ assert $rc 0 "Your agent does not support the validate-all action"
+elif [ $rc -ne 0 ]; then
+ assert $rc 0 "Validation failed. Did you supply enough options with -o ?" 1
+ usage $rc
+fi
+
+test_command monitor "Checking current state"
+rc=$?
+if [ $rc -eq 3 ]; then
+ assert $rc 7 "Your agent does not support the monitor action" 1
+
+elif [ $rc -eq 8 ]; then
+ test_command demote "Cleanup, demote"
+ assert $? 0 "Your agent was promoted and could not be demoted" 1
+
+ test_command stop "Cleanup, stop"
+ assert $? 0 "Your agent was promoted and could not be stopped" 1
+
+elif [ $rc -ne 7 ]; then
+ test_command stop
+ assert $? 0 "Your agent was active and could not be stopped" 1
+fi
+
+test_command monitor
+assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc"
+
+OCF_TESTER_FAIL_HAVE_BINARY=1
+export OCF_TESTER_FAIL_HAVE_BINARY
+OCF_RESKEY_CRM_meta_interval=0
+test_command monitor
+assert $? $stopped_rc:$installed_rc "The initial probe for a stopped resource should return $stopped_rc or $installed_rc even if all binaries are missing"
+unset OCF_TESTER_FAIL_HAVE_BINARY
+OCF_RESKEY_CRM_meta_interval=20000
+
+test_command start
+assert $? 0 "Start failed. Did you supply enough options with -o ?" 1
+
+test_command monitor
+assert $? 0 "Monitoring an active resource should return 0"
+
+OCF_RESKEY_CRM_meta_interval=0
+test_command monitor
+assert $? 0 "Probing an active resource should return 0"
+OCF_RESKEY_CRM_meta_interval=20000
+
+test_command notify
+rc=$?
+if [ $rc -eq 3 ]; then
+ info "* Your agent does not support the notify action (optional)"
+else
+ assert $rc 0 "The notify action cannot fail and must return 0"
+fi
+
+test_command demote "Checking for demote action"
+if [ $? -eq 3 ]; then
+ has_demote=0
+ info "* Your agent does not support the demote action (optional)"
+fi
+
+test_command promote "Checking for promote action"
+if [ $? -eq 3 ]; then
+ has_promote=0
+ info "* Your agent does not support the promote action (optional)"
+fi
+
+if [ $has_promote -eq 1 -a $has_demote -eq 1 ]; then
+ test_command demote "Testing: demotion of started resource"
+ assert $? 0 "Demoting a start resource should not fail"
+
+ test_command promote
+ assert $? 0 "Promote failed"
+
+ test_command demote
+ assert $? 0 "Demote failed" 1
+
+ test_command demote "Testing: demotion of demoted resource"
+ assert $? 0 "Demoting a demoted resource should not fail"
+
+ test_command promote "Promoting resource"
+ assert $? 0 "Promote failed" 1
+
+ test_command promote "Testing: promotion of promoted resource"
+ assert $? 0 "Promoting a promoted resource should not fail"
+
+ test_command demote "Demoting resource"
+ assert $? 0 "Demote failed" 1
+
+elif [ $has_promote -eq 0 -a $has_demote -eq 0 ]; then
+ info "* Your agent does not support promotable clones (optional)"
+
+else
+ echo "* Your agent partially supports promotable clones"
+ num_errors=`expr $num_errors + 1`
+fi
+
+test_command stop
+assert $? 0 "Stop failed" 1
+
+test_command monitor
+assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc"
+
+test_command start "Restarting resource..."
+assert $? 0 "Start failed" 1
+
+test_command monitor
+assert $? 0 "Monitoring an active resource should return 0"
+
+test_command start "Testing: starting a started resource"
+assert $? 0 "Starting a running resource is required to succeed"
+
+test_command monitor
+assert $? 0 "Monitoring an active resource should return 0"
+
+test_command stop "Stopping resource"
+assert $? 0 "Stop could not clean up after multiple starts" 1
+
+test_command monitor
+assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc"
+
+test_command stop "Testing: stopping a stopped resource"
+assert $? 0 "Stopping a stopped resource is required to succeed"
+
+test_command monitor
+assert $? $stopped_rc "Monitoring a stopped resource should return $stopped_rc"
+
+test_command migrate_to "Checking for migrate_to action"
+rc=$?
+if [ $rc -ne 3 ]; then
+ test_command migrate_from "Checking for migrate_from action"
+fi
+if [ $? -eq 3 ]; then
+ info "* Your agent does not support the migrate action (optional)"
+fi
+
+test_command reload "Checking for reload action"
+if [ $? -eq 3 ]; then
+ info "* Your agent does not support the reload action (optional)"
+fi
+
+if [ $num_errors -gt 0 ]; then
+ echo "Tests failed: $agent failed $num_errors tests" >&2
+ exit 1
+else
+ echo $agent passed all tests
+ exit 0
+fi
+
+# vim:et:ts=8:sw=4
diff --git a/tools/ocft/ChangeLog b/tools/ocft/ChangeLog
new file mode 100644
index 0000000..8b00210
--- /dev/null
+++ b/tools/ocft/ChangeLog
@@ -0,0 +1,57 @@
+0.43:
+ - Add an option 'Agent' in 'CONFIG'
+ - Fix a bug about remote shell.
+ - Add top level option 'VARIABLE'.
+ - Add top level option 'CLEANUP-AGENT'.
+ - Rename Var & Unvar to Env & Unenv.
+ - Fix a bug about agent installation.
+ - Modified configs of agent according the new syntax.
+0.42:
+ - Fix a bug about agent installation.
+ - The tests stop early if the basic functionality it not there.
+ - Fix a bug about 'config parser'.
+ - Keep the resource stat between the runs to avoid there are multiple stop/start per run.
+ - Add a function with warning output.
+ - Adjust the sequence of cases running.
+ - Change the default timeout.
+ - Replace the crm_master command in Agent scripts, it is no use in testing,
+ because the agent runs in local when test.
+ - Support drbd testing if the configuration is correct.
+ - Fix a 'Include' bug.
+ - Fix a bug about configuration of 'mysql'.
+0.41:
+ - Fixed a remote shell bug.
+ - Improved 'Include' option, now it supports remote shell, invoked just like 'Include@ip_address'.
+ - Show line number, if syntax error occurs in config file.
+ - Add a 'AgentRoot' sub-option.
+ - Fix a bug with config parsing.
+ - Rename ACTION "run" to "test".
+ - Add a simple output mode for "test" ACTION.
+ - Add a verbose ouput mode for "help" ACTION.
+ - The output will be recorded to log file.
+ - Add double stop and double start to tests.
+ - Exit with 1 if test script failed.
+ - The output of agents will be exposed if the test fails.
+ - Run zypper with -q
+ - Use 127.0.0.x(and lo for interface) for agents IP*
+0.4:
+ - Add a 'CASE-BLOCK' top option, it can be included by 'CASE'
+ - Add a 'SETUP-AGENT' top option, you can initialize agents before testing.
+ - Add a 'Include' sub-option.
+ - Add a 'Unvar' sub-option.
+ - Rename 'GLOBAL' to 'CONFIG'.
+ - Modify a part of syntax.
+ - Imporve the 'InstallPackage' option, now it supports SUSE, Redhat and Debian.
+ - Add README & README.zh_CN.
+0.3:
+ - All statements in CASE can be executed by remote shell.
+0.2:
+ - Implement options: BashAtExit, GLOBAL, HangTimeout, InstallPackage.
+ - Delete option: Name.
+ - Modify a part of syntax.
+ - Fix some bugs.
+ - Print testing message in human readable format.
+ - Improve 'make' and 'run' option.
+ - Add 'clean' option.
+0.1:
+ - Implement options: CASE, Bash, Run, Var, Name.
diff --git a/tools/ocft/Filesystem b/tools/ocft/Filesystem
new file mode 100644
index 0000000..2b22d1b
--- /dev/null
+++ b/tools/ocft/Filesystem
@@ -0,0 +1,105 @@
+# Filesystem
+# by dejan@suse.de on
+# Tue Feb 15 18:50:04 CET 2011
+#
+CONFIG
+ Agent Filesystem
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+VARIABLE
+ OCFT_rundir="`get_rundir`"
+ OCFT_fs="$OCFT_rundir/resource-agents/ocft-Filesystem-fs"
+ OCFT_dir="$OCFT_rundir/resource-agents/ocft-Filesystem-mnt"
+ OCFT_loop="`loopbackeddev make $OCFT_fs 16M`"
+
+SETUP-AGENT
+ mke2fs -j -Fq -m 0 $OCFT_loop
+
+CLEANUP-AGENT
+ loopbackeddev unmake $OCFT_fs
+ rmdir $OCFT_dir
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_device=$OCFT_loop
+ Env OCF_RESKEY_fstype=ext3
+ Env OCF_RESKEY_directory=$OCFT_dir
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_device'"
+ Include prepare
+ Env OCF_RESKEY_device=/dev/no_such_device
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: unset 'OCF_RESKEY_device'"
+ Include prepare
+ Unenv OCF_RESKEY_device
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "monitor depth 10 when running"
+ Include prepare
+ AgentRun start
+ Env OCF_CHECK_LEVEL=10
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor depth 20 with running"
+ Include prepare
+ AgentRun start
+ Env OCF_CHECK_LEVEL=20
+ AgentRun monitor OCF_SUCCESS
+
+CASE "start insert failure (remove device)"
+ Include prepare
+ Bash losetup -d $OCFT_loop
+ BashAtExit losetup $OCFT_loop $OCFT_fs
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "monitor depth 20 insert failure (r/o fs)"
+ Include prepare
+ AgentRun start
+ Bash mount -o remount,ro $OCFT_dir
+ BashAtExit mount -o remount,rw $OCFT_dir
+ Env OCF_CHECK_LEVEL=20
+ AgentRun monitor OCF_ERR_GENERIC
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/IPaddr2 b/tools/ocft/IPaddr2
new file mode 100644
index 0000000..04698a0
--- /dev/null
+++ b/tools/ocft/IPaddr2
@@ -0,0 +1,137 @@
+# IPaddr2
+
+CONFIG
+ Agent IPaddr2
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+SETUP-AGENT
+ ip addr add 192.168.144.1/24 dev eth0 brd 192.168.144.255
+
+CLEANUP-AGENT
+ ip addr del 192.168.144.1/24 dev eth0
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ip=192.168.144.2
+
+CASE-BLOCK check_iflabel_assigned
+ Bash ip -4 -o addr show eth0 | grep -w 192.168.144.2/24 | grep -w eth0:iflabel >/dev/null # checking iflabel was assigned correctly
+
+CASE-BLOCK check_iflabel_removed
+ Bash ! ip -4 -o addr show eth0 | grep -w 192.168.144.2/24 | grep -w eth0:iflabel >/dev/null # checking iflabel was removed correctly
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_ip'"
+ Include prepare
+ Unenv OCF_RESKEY_ip
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set invalid 'OCF_RESKEY_ip'"
+ Include prepare
+ Env OCF_RESKEY_ip=not_ip_address
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set 'OCF_RESKEY_cidr_netmask'"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=24
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: set invalid 'OCF_RESKEY_cidr_netmask'"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=not_netmask
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set 'OCF_RESKEY_broadcast'"
+ Include prepare
+ Env OCF_RESKEY_broadcast=192.168.144.255
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: set invalid 'OCF_RESKEY_broadcast'"
+ Include prepare
+ Env OCF_RESKEY_broadcast=not_broadcast
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set 'OCF_RESKEY_nic'"
+ Include prepare
+ Env OCF_RESKEY_nic=eth0
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: set invalid 'OCF_RESKEY_nic'"
+ Include prepare
+ Env OCF_RESKEY_nic=not_nic
+ AgentRun start OCF_ERR_CONFIGURED
+ AgentRun validate-all OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "Attachment to loopback interface"
+ Env OCF_RESKEY_ip=127.0.0.3
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+
+CASE "check additional env: set 'OCF_RESKEY_iflabel'"
+ Include prepare
+ Env OCF_RESKEY_nic=eth0
+ Env OCF_RESKEY_iflabel=iflabel
+ AgentRun start OCF_SUCCESS
+ Include check_iflabel_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_iflabel_removed
+
+# This is deprecated but still supported for the compatibility.
+CASE "check additional env: specify iflabel in 'OCF_RESKEY_nic'"
+ Include prepare
+ Env OCF_RESKEY_nic=eth0:iflabel
+ AgentRun start OCF_SUCCESS
+ Include check_iflabel_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_iflabel_removed
+
+# monitor should return OCF_ERR_GENERIC rather than OCF_ERR_CONFIGURED
+# when the specified OCF_RESKEY_nic is vanished by a failure.
+# This has been changed as of 3.9.6.
+CASE "monitor failure when 'OCF_RESKEY_nic' is vanished"
+ Include prepare
+ Env OCF_RESKEY_nic=ethVanished
+ Env OCF_RESKEY_CRM_meta_interval=10 # not in probe
+ AgentRun monitor OCF_ERR_GENERIC
diff --git a/tools/ocft/IPaddr2v4 b/tools/ocft/IPaddr2v4
new file mode 100644
index 0000000..4d37168
--- /dev/null
+++ b/tools/ocft/IPaddr2v4
@@ -0,0 +1,323 @@
+# IPaddr2v4
+
+# Note: This test case uses two NICs(eth0, eth1) and
+# a IPv4 address prefix (192.168.144.0/24).
+# Adjust them according to your environment at VARIABLE section if needed.
+
+CONFIG
+ Agent IPaddr2
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+VARIABLE
+ OCFT_target_ip=192.168.144.2
+ OCFT_target_nic=eth0
+ OCFT_target_prefix=24
+ OCFT_target_netaddr=192.168.144.1/$OCFT_target_prefix
+ OCFT_target_brd=192.168.144.255
+ OCFT_wrong_ip=192.168.120.1
+ OCFT_force_nic=eth1
+ OCFT_force_prefix=16
+ OCFT_force_prefix2=28
+ OCFT_force_brd=192.168.255.255
+
+SETUP-AGENT
+ ip addr add $OCFT_target_netaddr dev $OCFT_target_nic brd $OCFT_target_brd
+
+CLEANUP-AGENT
+ ip addr del $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ip=$OCFT_target_ip
+ Env OCFT_check_ip=$OCFT_target_ip
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ Env OCFT_check_nic=$OCFT_target_nic
+
+CASE-BLOCK check_ip_assigned
+ Bash ip -4 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv4 address was assigned correctly
+
+CASE-BLOCK check_ip_removed
+ Bash ! ip -4 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv4 address was removed correctly
+
+CASE-BLOCK base_ip_assigned
+ Bash ip addr add $OCFT_target_netaddr dev $OCFT_target_nic brd $OCFT_target_brd
+
+CASE-BLOCK base_ip_removed
+ Bash ip addr del $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+# CASE No.0
+#
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+
+# CASE No.1
+#
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.2
+#
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+# CASE No.3
+#
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+# CASE No.4
+#
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+# CASE No.5
+#
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+# CASE No.6
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 succeeds if the ip matched based on the netmask of the subnet
+# or fails if it did not match to any.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case.
+#
+CASE "params with nic, no cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.7
+#
+CASE "params with nic, cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.8
+#
+CASE "error params with wrong ip"
+ Include prepare
+ Env OCF_RESKEY_ip=$OCFT_wrong_ip
+ AgentRun start OCF_ERR_GENERIC
+
+# CASE No.9
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 fails when it could not determine the correct subnet mask.
+# When it could not get base ip, it becomes the error.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case.
+#
+CASE "params with force nic"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCFT_check_nic=$OCFT_force_nic
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+ Unenv OCF_RESKEY_nic
+
+# CASE No.10
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 fails when it could not determine the broadcast.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 succeeded but it's considered ambiguous.
+#
+CASE "params with force cidr_netmask (base netmask > assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+
+# CASE No.11
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 succeeds but the broadcast is not set.
+# This is because findif.sh can not calculate a broadcast from a netmask.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast.
+#
+CASE "force to use the specified nic and cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+ Unenv OCF_RESKEY_cidr_netmask
+
+
+# CASE No.12
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 fails when it could not determine the correct subnet mask.
+# When it could not get base ip, it becomes the error.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 was using a wrong subnet mask (constant of 32) in this case.
+#
+CASE "error params with wrong ip and nic (not exist base_ip)"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCFT_check_nic=$OCFT_target_nic
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+ Include base_ip_assigned
+
+# CASE No.13
+#
+CASE "params with cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.14
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 does not override the broadcast by cidr_netmask.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 overrode the broadcast calculated by cidr_netmask.
+#
+CASE "params with force cidr_netmask (base netmask < assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.15
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 fails when it could not determine the broadcast.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 succeeded but it's considered ambiguous.
+#
+CASE "error params with wrong ip and cidr_netmask (not exist base_ip)"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ AgentRun start OCF_ERR_GENERIC
+ Include base_ip_assigned
+
+# CASE No.16
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 succeeds but the broadcast is not set.
+# This is because findif.sh can not calculate a broadcast from a netmask.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast.
+#
+CASE "force to use the specified nic and cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ Env OCFT_check_nic=$OCFT_force_nic
+ AgentRun start OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.17
+# Note: this result is different from IPaddr2 in 3.9.3.
+# IPaddr2 succeeds but the broadcast is not set.
+# This is because findif.sh can not calculate a broadcast from a netmask.
+# Recommended to always specify both nic, cidr_netmask, and broadcast when you needed.
+# IPaddr2 in 3.9.3 succeeded with using a calculated broadcast.
+#
+CASE "force to use the specified nic and cidr_netmask (not exist base_ip)"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ Env OCFT_check_nic=$OCFT_force_nic
+ AgentRun start OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Unenv OCF_RESKEY_nic
+ Unenv OCF_RESKEY_cidr_netmask
+ Include base_ip_assigned
+
+# CASE No.18
+#
+CASE "params with broadcast, no nic, no cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_broadcast=$OCFT_force_brd
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.19
+#
+CASE "params with broadcast, no nic, no cidr_netmask"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_broadcast=$OCFT_force_brd
+ AgentRun start OCF_ERR_GENERIC
+ Include base_ip_assigned
+
+# CASE No.20
+#
+CASE "force to use the specified nic and cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCF_RESKEY_broadcast=$OCFT_force_brd
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+ Unenv OCF_RESKEY_cidr_netmask
+
+# CASE No.21
+#
+CASE "force to use the specified nic and cidr_netmask"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCF_RESKEY_broadcast=$OCFT_target_brd
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+ Include base_ip_assigned
+
diff --git a/tools/ocft/IPaddr2v6 b/tools/ocft/IPaddr2v6
new file mode 100644
index 0000000..d24d890
--- /dev/null
+++ b/tools/ocft/IPaddr2v6
@@ -0,0 +1,250 @@
+# IPaddr2v6
+
+# Note: This test case uses two NICs(eth0, eth1) and
+# a IPv6 address prefix (2001:db8::/32, RFC3849).
+# Adjust them according to your environment at VARIABLE section if needed.
+
+CONFIG
+ Agent IPaddr2
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+VARIABLE
+ OCFT_target_ip=2001:db8:1234::2
+ OCFT_target_nic=eth0
+ OCFT_target_prefix=64
+ OCFT_target_netaddr=2001:db8:1234::1/$OCFT_target_prefix
+ OCFT_target_linklocal=fe80::2
+ OCFT_wrong_ip=2001:db8:5678::2
+ OCFT_force_nic=eth1
+ OCFT_force_prefix=80
+ OCFT_force_prefix2=48
+
+SETUP-AGENT
+ ip addr add $OCFT_target_netaddr dev $OCFT_target_nic
+
+CLEANUP-AGENT
+ ip addr del $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ip=$OCFT_target_ip
+ Env OCFT_check_ip=$OCFT_target_ip
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ Env OCFT_check_nic=$OCFT_target_nic
+
+CASE-BLOCK check_ip_assigned
+ Bash ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was assigned correctly
+
+CASE-BLOCK check_ip_removed
+ Bash ! ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ip/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was removed correctly
+
+CASE-BLOCK base_ip_assigned
+ Bash ip addr add $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK base_ip_removed
+ Bash ip addr del $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+# CASE No.0
+#
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+
+# CASE No.1
+#
+CASE "normal stop"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.2
+#
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+# CASE No.3
+#
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+# CASE No.4
+#
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+# CASE No.5
+#
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+# CASE No.6
+#
+CASE "error params with wrong ip"
+ Include prepare
+ Env OCF_RESKEY_ip=$OCFT_wrong_ip
+ AgentRun start OCF_ERR_GENERIC
+
+# CASE No.7
+#
+CASE "error params with no nic for a link-local IPv6 address"
+ Include prepare
+ Env OCF_RESKEY_ip=$OCFT_target_linklocal
+ Env OCFT_check_ip=$OCFT_target_linklocal
+ # nic is mandatory for a link-local address
+ AgentRun start OCF_ERR_CONFIGURED
+
+# CASE No.8
+#
+CASE "params with nic, no cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.9
+#
+CASE "normal usage for a link-local IPv6 address, params with nic"
+ Include prepare
+ Env OCF_RESKEY_ip=$OCFT_target_linklocal
+ Env OCFT_check_ip=$OCFT_target_linklocal
+ # nic is mandatory for a link-local address
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCFT_check_nic=$OCFT_target_nic
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.10
+#
+CASE "error params with wrong ip and nic (not exist base_ip)"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCFT_check_nic=$OCFT_target_nic
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+ Include base_ip_assigned
+
+# CASE No.11
+#
+CASE "params with force nic"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCFT_check_nic=$OCFT_force_nic
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+ Unenv OCF_RESKEY_nic
+
+# CASE No.12
+#
+CASE "params with force cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.13
+#
+CASE "params with force cidr_netmask (base netmask < assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+
+# CASE No.14
+#
+CASE "params with force cidr_netmask (base netmask > assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+
+# CASE No.15
+#
+CASE "params with cidr_netmask"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ AgentRun start OCF_ERR_GENERIC
+ Include base_ip_assigned
+
+# CASE No.16
+#
+CASE "params with nic, cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ Env OCFT_check_nic=$OCFT_target_nic
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.17
+#
+CASE "force to use the specified nic and cidr_netmask (base netmask < assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.18
+# This use case is now valid. It was not allowed until v3.9.2.
+#
+CASE "force to use the specified nic and cidr_netmask (base netmask > assigned netmask)"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+# CASE No.19
+#
+CASE "force to use the specified nic and cidr_netmask (base netmask > assigned netmask)"
+ Include prepare
+ Include base_ip_removed
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix2
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix2
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+ Include base_ip_assigned
+
diff --git a/tools/ocft/IPsrcaddr b/tools/ocft/IPsrcaddr
new file mode 100644
index 0000000..d95142e
--- /dev/null
+++ b/tools/ocft/IPsrcaddr
@@ -0,0 +1,63 @@
+# IPsrcaddr
+
+CONFIG
+ Agent IPsrcaddr
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage iproute2
+ HangTimeout 20
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ipaddress= # put here your IP
+ Env OCF_RESKEY_cidr_netmask= # and the netmask
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_ipaddress'"
+ Include prepare
+ Unenv OCF_RESKEY_ipaddress
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set invalid 'OCF_RESKEY_ipaddress'"
+ Include prepare
+ Env OCF_RESKEY_ipaddress=not_ip_address
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/IPv6addr b/tools/ocft/IPv6addr
new file mode 100644
index 0000000..36a9642
--- /dev/null
+++ b/tools/ocft/IPv6addr
@@ -0,0 +1,150 @@
+# IPv6addr
+
+# Note: This test case uses two NICs(eth0, eth1) and
+# a IPv6 address prefix (2001:db8::/32, RFC3849).
+# Adjust them according to your environment at VARIABLE section if needed.
+
+CONFIG
+ Agent IPv6addr
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+VARIABLE
+ OCFT_target_ipv6addr=2001:db8:1234::2
+ OCFT_target_nic=eth0
+ OCFT_target_prefix=64
+ OCFT_target_netaddr=2001:db8:1234::1/$OCFT_target_prefix
+ OCFT_target_linklocal=fe80::2
+ OCFT_wrong_ipv6addr=2001:db8:5678::2
+ OCFT_force_nic=eth1
+ OCFT_force_prefix=80
+
+SETUP-AGENT
+ ip addr add $OCFT_target_netaddr dev $OCFT_target_nic
+
+CLEANUP-AGENT
+ ip addr del $OCFT_target_netaddr dev $OCFT_target_nic
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ipv6addr=$OCFT_target_ipv6addr
+ Env OCFT_check_ipv6addr=$OCFT_target_ipv6addr
+ Env OCFT_check_prefix=$OCFT_target_prefix
+ Env OCFT_check_nic=$OCFT_target_nic
+
+CASE-BLOCK check_ip_assigned
+ Bash ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ipv6addr/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was assigned correctly
+
+CASE-BLOCK check_ip_removed
+ Bash ! ip -6 -o addr show $OCFT_check_nic | grep -w $OCFT_check_ipv6addr/$OCFT_check_prefix >/dev/null # checking if the IPv6 address was removed correctly
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "params with nic, no cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+CASE "params with nic, cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_target_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+CASE "normal usage for a link-local IPv6 address"
+ Include prepare
+ Env OCF_RESKEY_ipv6addr=$OCFT_target_linklocal
+ Env OCFT_check_ipv6addr=$OCFT_target_linklocal
+ # nic is mandatory for a link-local address
+ Env OCF_RESKEY_nic=$OCFT_target_nic
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
+
+CASE "error start for a link-local IPv6 address when no nic"
+ Include prepare
+ Env OCF_RESKEY_ipv6addr=$OCFT_target_linklocal
+ # nic is mandatory for a link-local address
+ Unenv OCF_RESKEY_nic
+ AgentRun start OCF_ERR_GENERIC
+ Include check_ip_removed
+
+CASE "error params with wrong ipv6addr"
+ Include prepare
+ Env OCF_RESKEY_ipv6addr=$OCFT_wrong_ipv6addr
+ AgentRun start OCF_ERR_GENERIC
+
+# Note: this result is different from IPaddr2/findif
+# IPaddr2 succeeds if the ip matched based on the netmask of the subnet
+# or fails if it did not match to any.
+# Recommended to always specify both nic and cidr_netmask when you needed.
+CASE "error params with wrong cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ AgentRun start OCF_ERR_GENERIC
+
+# Note: this result is different from IPaddr2/findif
+# IPaddr2 succeeds but it uses /32 as a guessed cidr_netmask which
+# does not seem to be expected.
+# Recommended to always specify both nic and cidr_netmask when you needed.
+CASE "error params with wrong nic"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ AgentRun start OCF_ERR_GENERIC
+
+# Note: This use case is now valid. It was not allowed until v3.9.2.
+CASE "force to use the specified nic and cidr_netmask"
+ Include prepare
+ Env OCF_RESKEY_nic=$OCFT_force_nic
+ Env OCF_RESKEY_cidr_netmask=$OCFT_force_prefix
+ Env OCFT_check_nic=$OCFT_force_nic
+ Env OCFT_check_prefix=$OCFT_force_prefix
+ AgentRun start OCF_SUCCESS
+ Include check_ip_assigned
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ Include check_ip_removed
diff --git a/tools/ocft/LVM b/tools/ocft/LVM
new file mode 100644
index 0000000..00b69ee
--- /dev/null
+++ b/tools/ocft/LVM
@@ -0,0 +1,83 @@
+# LVM
+# by dejan@suse.de on
+# Wed Feb 16 13:15:01 CET 2011
+
+CONFIG
+ Agent LVM
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+VARIABLE
+ OCFT_rundir="`get_rundir`"
+ OCFT_pv=$OCFT_rundir/resource-agents/ocft-LVM-pv
+ OCFT_vg=ocft-vg
+ OCFT_lv=ocft-lv
+ OCFT_loop="`loopbackeddev make $OCFT_pv 16M`"
+
+SETUP-AGENT
+ pvcreate $OCFT_loop
+ vgcreate -s 4K $OCFT_vg $OCFT_loop
+ lvcreate -n $OCFT_lv -L 600K $OCFT_vg
+
+CLEANUP-AGENT
+ vgchange -an $OCFT_vg
+ lvremove -f /dev/$OCFT_vg/$OCFT_lv
+ vgremove -f $OCFT_vg
+ pvremove $OCFT_loop
+ loopbackeddev unmake $OCFT_pv
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_volgrpname=$OCFT_vg
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_volgrpname'"
+ Include prepare
+ Env OCF_RESKEY_volgrpname=/dev/no_such_device
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "check base env: unset 'OCF_RESKEY_volgrpname'"
+ Include prepare
+ Unenv OCF_RESKEY_volgrpname
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/MailTo b/tools/ocft/MailTo
new file mode 100644
index 0000000..8754035
--- /dev/null
+++ b/tools/ocft/MailTo
@@ -0,0 +1,57 @@
+# MailTo
+
+CONFIG
+ Agent MailTo
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage mailx
+ HangTimeout 20
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_email=root@localhost
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_email'"
+ Include prepare
+ Unenv OCF_RESKEY_email
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/Makefile.am b/tools/ocft/Makefile.am
new file mode 100644
index 0000000..69c59ee
--- /dev/null
+++ b/tools/ocft/Makefile.am
@@ -0,0 +1,63 @@
+# Author: John Shi
+# jshi@suse.de
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+EXTRA_DIST = $(ocftcfgs_DATA) $(ocft_DATA) $(ocft_SCRIPTS)
+
+sbin_SCRIPTS = ocft
+
+ocftcfgsdir = $(datadir)/$(PACKAGE_NAME)/ocft/configs
+ocftcfgs_DATA = apache \
+ IPaddr2 \
+ IPaddr2v4 \
+ IPaddr2v6 \
+ IPv6addr \
+ Filesystem \
+ LVM \
+ Raid1 \
+ IPsrcaddr \
+ MailTo \
+ jboss \
+ mysql \
+ mysql-proxy \
+ pgsql \
+ db2 \
+ oracle \
+ drbd.linbit \
+ exportfs \
+ exportfs-multidir \
+ nfsserver \
+ portblock \
+ iscsi \
+ named \
+ postfix \
+ sg_persist \
+ tomcat \
+ Xinetd \
+ Xen \
+ VirtualDomain \
+ SendArp
+
+ocftdir = $(datadir)/$(PACKAGE_NAME)/ocft
+ocft_DATA = README \
+ README.zh_CN \
+ caselib \
+ helpers.sh \
+ runocft.prereq
+ocft_SCRIPTS = runocft
+
diff --git a/tools/ocft/README.in b/tools/ocft/README.in
new file mode 100644
index 0000000..1c4ae12
--- /dev/null
+++ b/tools/ocft/README.in
@@ -0,0 +1,147 @@
+INTRODUCTION & DESIGN
+~~~~~~~~~~~~~~~~~~~~~
+
+ - Ocft is a testing tool for resource agents. Instead of the policy of HA,
+ it mainly concerns whether resource agents run correct locally. It can
+ design types of complicated environments to test the reliability of
+ resource agents. Precisely, it is to display whether resource agents can
+ return to correct or expected value. The advantage of the tool provides
+ us with competence to design conditions which can be recorded or reproduced.
+ Hence it is useful to debuggers.
+
+* Components
+ ** Test case generator (@sbindir@/ocft)
+ - Turning configuration files of test case to executable scripts.
+
+ ** Configuration file (@datadir@/@PACKAGE_NAME@/ocft/configs/)
+ - Every configuration file directs only one resource agent and share the same
+ name with resource agent but contains more test cases.
+
+ ** The testing script (/var/lib/@PACKAGE_NAME@/ocft/cases/)
+ - After the generator reads configuration files and generates many testing
+ scripts and the script is underway, the test begins.
+
+* How to customize the environment of testing
+ - Ocft designs the running conditions through two ways, one is changing the
+ environment variables of resource agents (it is the interface left by OCF itself),
+ the other is modifying the OS environment of resource agents, such as altering
+ the permission of some key file or IP address of the machine.
+
+* How to test
+ - Firstly, you need to sketch the all complex and uncommon environments against
+ a certain resource agent and keep in mind what consequences may be caused by
+ these uncommon environments.
+ Secondly, write the designed conditions and foreknown consequences into
+ configuration files, and then run the generator to translate the test case to
+ executable scripts.
+ Finally, you need running these scripts to observe the output and learn
+ the running status of each test case, which will compares the predicated result
+ with the actual one. If they differ, you will be able to find the bugs of the
+ resource agent.
+ - All of the output with test will be recorded into the log files, you can find them
+ in /var/lib/@PACKAGE_NAME@/ocft/cases/logs.
+
+
+HOW TO WRITE CONFIGURATION FILE
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - There are only 6 top level options that are all spelled by capital letters and "-".
+ Every top level option contains sub-options that they are initials.
+
+* 'CONFIG' (top level option)
+ - Grammar: CONFIG
+ - The design in this option is global and influences every test case.
+
+ ** 'Agent' (sub-option)
+ - Grammar: Agent AGENT_NAME
+ - The agent name you want to test.
+
+ ** 'AgentRoot' (sub-option)
+ - Grammar: AgentRoot /usr/lib/ocf/resource.d/xxx
+ - A few agents will go to "linbit" or "pacemaker" directory, if you define this option,
+ ocft will use it to replace the default directory "heartbeat".
+
+ ** 'InstallPackage' (sub-option)
+ - Grammar: InstallPackage package [package2 [...]]
+ - It will test whether the system have installed the service of the resource agent.
+ If not, it will download from Internet and have it installed automatically.
+
+ ** 'HangTimeout' (sub-option)
+ - Grammar: HangTimeout secs
+ - If you alter some key options, some resource agents will get puzzled and stop,
+ which will influence the running of the following test case. Hence timeout setting is
+ needed, if the resource agent stops timeout, the scripts will kill this resource agent.
+
+* 'VARIABLE' (top level option)
+ - Grammar:
+ VARIABLE
+ VAR1=value1
+ VAR2=value2
+ ...
+ - Define the global variable here, the variables can be visited everywhere, they can be referenced
+ using $VAR_NAME. Note, the variables in VARIABLE are different from 'Env VAR1=value1', 'Env' can
+ affect the activity of agent, but the variables in VARIABLE just be shared with top level option.
+
+* 'SETUP-AGENT' (top level option)
+ - Grammar:
+ SETUP-AGENT
+ bash scripts...
+ ...
+ - Some of Agents may need to be initialized before testing, you can do it here with bash script.
+
+* 'CLEANUP-AGENT' (top level option)
+ - Grammar:
+ CLEANUP-AGENT
+ bash scripts...
+ ...
+ - If SETUP-AGENT set, usually you might be use this option do some cleaning work after test.
+
+* 'CASE' & 'CASE-BLOCK' (top level option)
+ - Grammar: CASE "description" & CASE-BLOCK macro_name
+ - Usually, the conditions you designed are more than one and a few 'CASE "..."' will
+ appear in configuration file. It is worth noting that the following sub-options
+ have 2 spellings:
+ One is general, where shell affects the local environment; the other is special,
+ where each options added "@ipaddr". It can remotely execute shell codes. In other words,
+ it is to execute the shell codes from a remote host, which is meaningful when a resource
+ agent needs 2 hosts. This remote shell is not a remote execution only through "ssh", but
+ running a remote shell in the background while the test case is running. The remote shell
+ runs in the background till the end and saves the results during the process. That is to
+ say, you can alternatively carry out local and remote shell code segments.
+ The "CASE-BLOCK" option is a macro definer, the statements in "CASE-BLOCK" will be inserted
+ into "CASE" if you "Include" the "macro_name".
+
+ ** 'Env' (sub-option)
+ - Grammar: Env VARIABLE=value
+ - It is to set up an environment variable of the resource agent. They usually appear to
+ be OCF_RESKEY_xxx. One point is to be noted is there is no blank by both sides of "=".
+
+ ** 'Unenv' (sub-option)
+ - Grammar: Unenv VARIABLE [VARIABLE2 [...]]
+ - Remove the environment variable.
+
+ ** 'Include' (sub-option)
+ - Grammar: Include macro_name
+ - It will be replaced by statements in 'macro_name', of course, you should define the
+ content of 'macro_name' with 'CASE-BLOCK' first.
+
+ ** 'Bash' (sub-option)
+ - Grammar: Bash bash_codes
+ - This option is to set up the environment of OS, where you can insert BASH code to
+ customize the system randomly. Note, do not cause unrecoverable consequences to the
+ system.
+
+ ** 'BashAtExit' (sub-option)
+ - Grammar: BashAtExit bash_codes
+ - This option is to recover the OS environment in order to run another test case
+ correctly. Of cause you can use 'Bash' option to recover it. However, if mistakes occur
+ in the process, the script will quit directly instead of running your recovery codes.
+ If it happens, you ought to use BashAtExit which can restore the system environment
+ before you quit.
+
+ ** 'AgentRun' (sub-option)
+ - Grammar: AgentRun cmd [ret_value]
+ - This option is to run resource agent. "cmd" is the parameter of the resource agent,
+ such as "start, status, stop ...". The second parameter is optional. It will compare the
+ actual returned value with the expected value when the script has run recourse agent.
+ If differs, bugs will be found.
diff --git a/tools/ocft/README.zh_CN.in b/tools/ocft/README.zh_CN.in
new file mode 100644
index 0000000..5138cf1
--- /dev/null
+++ b/tools/ocft/README.zh_CN.in
@@ -0,0 +1,124 @@
+1 介绍和设计
+ocft是一个测试resource agents的工具。它并不关注HA的策略,而是关注resource agents
+是否正常运行在本机。他能对resource agents设计各种复杂环境,来考验resource agnets
+是否能正常应对,也就是看resouce agents是否能返回正确的或者说我们所期望的值。
+这个工具给我们带来的好处就是我们可以集中的批量的来设计环境,而且这种环境是可被我们
+记录、重现的,对Debug人员来说是很有用的。
+
+1.1 组成
+1.1.1 解释器 (@sbindir@/ocft)
+将test case配置文件转换成可执行的测试脚本。
+
+1.1.2 配置文件 (@datadir@/@PACKAGE_NAME@/ocft/configs/)
+每一个configuration file只针对一个resouce agent,配置文件名与resouce agent名相同,
+但是它可以容纳很多test case。
+
+1.1.3 测试脚本 (/var/lib/@PACKAGE_NAME@/ocft/cases/)
+由generator读取configuration file生成测试脚本,直接运行此脚本就可以开始测试了。
+
+1.2 如何定制环境
+ocft 通过两种手段来设计resouce agents的运行环境,一是更改resouce agents的环境变量,
+当然这是ocf本身就留给我们的接口。二是更改resource agents所处的系统环境,
+比如更改某个关键文件的权限,更改本机ip地址等等...
+
+1.3 如何进行测试
+首先你需要针对某个resource agent在脑中勾画各种复杂且异常的环境,并且你能清楚预知
+这些异常环境会给resource agent带来什么结果,然后将这些设计好的环境和你预知的结果
+都写入配置文件,然后运行generator,将你刚写的test case转换成可执行的脚本。最后运行
+这些脚本,观察它们的输出,你可以清楚看到每个test case运行状况,他会比较你的预知结
+果和resource agent的实际结果,如果不一样,说明你找到resource agent的bug了。
+所有的测试输出都会被记录到日志文件中,你可以在 /var/lib/@PACKAGE_NAME@/ocft/cases/logs
+中找到他们.
+
+2 配置
+只有6个top level option,它们是由大写字母和'-'构成的,每个top level option都有若干sub-option,
+它们是首字母大写。
+
+2.1 'CONFIG' 选项
+语法:CONFIG
+此option中的设计是全局的,对每个test case都有所影响。
+
+2.1.1 'Agent' 选项
+语法:Agent AGENT_NAME
+你要测试的Agent的名字。
+
+2.1.2 'AgentRoot' 选项
+语法:AgentRoot /usr/lib/ocf/resource.d/xxx
+一些agent将会被移到 "pacemaker" 或 "linbit" 目录,如果你定义了这个选项,ocft将会用它来
+替代默认的目录"heartbeat"。
+
+2.1.3 'InstallPackage' 选项
+语法:InstallPackage package [package2 [...]]
+他会检测系统是否安装了此resource agent的service,如果没有安装,会自动从网络进行安装。
+package_name是某个resouce agent在操作系统中必须安装的包。
+
+2.1.4 'HangTimeout' 选项
+语法:HangTimeout secs
+如果你更改了一些很关键的东西,有些resouce agent会不知所措,停在那里不动,那么就会影响
+到后面test case的运行,所以你需要设定超时,如果一旦resouce agent停在那超时了,脚本会
+杀死这个resouce agent。
+
+2.2 'VARIABLE' 选项
+语法:
+VARIABLE
+ VAR1=value1
+ VAR2=value2
+ ...
+在此定义全局变量,这些变量可以用于配置文件中的任何地方,引用时在变量名前加上$。请注意,这些
+变量不同于Env定义的变量,Env是定义环境变量,可以改变agent的行为,而这些变量只是用于配置文件中
+共享。
+
+2.3 'SETUP-AGENT' 选项
+语法:
+SETUP-AGENT
+ bash scripts...
+ ...
+一些Agent在测试前可能需要初始化,你可以用bash脚本在这初始化。
+
+2.4 'CLEANUP-AGENT' 选项
+语法:
+CLEANUP-AGENT
+ bash scripts...
+ ...
+如果之前定义了SETUP-AGENT, 你可能还需要此选项在测试完后来作一些清除。
+
+2.5 'CASE' & 'CASE-BLOCK' 选项
+语法:CASE "description" & CASE-BLOCK macro_name
+通常你设计的环境不止一个,那么配置文件中应该会出现许多 'CASE "..."',值得注意的是,以下子
+选项每个都有两种写法,一是普通写法,产生的shell代码对本地产生作用,二是特殊写法,就是在每
+个选项后加上 "@ipaddr",他可以远程执行shell代码,也就是控制远程机器执行你给的shell代码,
+这对需要两台机器以上的resouce agent很有用的,这个远程shell并非简单的用ssh去远程执行,而是在
+test case生存期内,后台运行一个远程shell,他始终在后台运行并且保存你的中间结果,也就是说你
+可以交替执行本地shell代码段和远程shell代码段。
+'CASE-BLOCK'选项是一个宏定义器,它定义的内容将会被插入到"CASE"内容中。
+
+2.5.1 'Env' 选项
+语法:Env VARIABLE=value
+这是设置resouce agent的环境变量,他们通常是OCF_RESKEY_xxx,注意=号两边不要有空格。
+
+2.5.2 'Unenv' 选项
+语法:Unenv VARIABLE
+此选项用于删除环境变量。
+
+2.5.3 'Include' 选项
+语法:Include macro_name
+此选项将会被宏"macro_name"的内容所替代,当然,你得预先用"CASE-BLOCK"来定义宏"macro_name"
+的内容。
+
+2.5.4 'Bash' 选项
+语法:Bash bash_codes
+此选项是用来设置os的环境,你可以嵌入bash代码来对系统作任意设置,不过要注意不要对系统造成
+不可恢复的后果就行了。
+
+2.5.5 'BashAtExit' 选项
+语法:BashAtExit bash_codes
+此选项是用来恢复os的环境,以便另一个test case能正常运行,当然你也可以直接用'Bash'选项来
+恢复,但是如果脚本在执行过程中产生某些错误,那么脚本会直接退出,而不会去执行你的恢复代码,
+那么你就要用到BashAtExit,他能在你退出前去恢复系统环境。
+
+2.5.6 'AgentRun' 选项
+语法:AgentRun cmd [ret_value]
+此option会去运行resouce agent,'cmd' 就是resouce agent的参数,如 start,status,stop ...
+第二个参数是可选的,它是你对系统环境作出特殊设定以后,你预计resouce agent会返回的值,
+如果给出此参数,那么脚本会在运行resouce agent后,会比较此时的返回值和你预期的返回值,
+如果不一致,那么说明找到bug了。
diff --git a/tools/ocft/Raid1 b/tools/ocft/Raid1
new file mode 100644
index 0000000..462b9b9
--- /dev/null
+++ b/tools/ocft/Raid1
@@ -0,0 +1,134 @@
+# Raid1
+# by dejan@suse.de on
+# Fri Aug 24 17:01:40 CEST 2012
+
+CONFIG
+ Agent Raid1
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage mdadm
+ HangTimeout 20
+
+VARIABLE
+ OCFT_rundir="`get_rundir`"
+ OCFT_disk0=$OCFT_rundir/resource-agents/ocft-Raid1-disk0
+ OCFT_disk1=$OCFT_rundir/resource-agents/ocft-Raid1-disk1
+ OCFT_disk2=$OCFT_rundir/resource-agents/ocft-Raid1-disk2
+ OCFT_disk3=$OCFT_rundir/resource-agents/ocft-Raid1-disk3
+ OCFT_raidconf=$OCFT_rundir/resource-agents/ocft-mdadm.conf
+ OCFT_raiddev=/dev/md8
+ OCFT_raiddev2=/dev/md9
+ OCFT_loop0="`loopbackeddev make $OCFT_disk0 16M`"
+ OCFT_loop1="`loopbackeddev make $OCFT_disk1 16M`"
+ OCFT_loop2="`loopbackeddev make $OCFT_disk2 16M`"
+ OCFT_loop3="`loopbackeddev make $OCFT_disk3 16M`"
+
+SETUP-AGENT
+ mdadm --create $OCFT_raiddev -l 0 --raid-devices=2 $OCFT_loop0 $OCFT_loop1
+ mdadm --create $OCFT_raiddev2 -l 0 --raid-devices=2 $OCFT_loop2 $OCFT_loop3
+ echo DEVICE $OCFT_loop0 $OCFT_loop1 > $OCFT_raidconf
+ echo DEVICE $OCFT_loop2 $OCFT_loop3 >> $OCFT_raidconf
+ echo ARRAY $OCFT_raiddev devices=$OCFT_loop0,$OCFT_loop1 >> $OCFT_raidconf
+ echo ARRAY $OCFT_raiddev2 devices=$OCFT_loop2,$OCFT_loop3 >> $OCFT_raidconf
+
+CLEANUP-AGENT
+ mdadm --zero-superblock $OCFT_loop0
+ mdadm --zero-superblock $OCFT_loop1
+ mdadm --zero-superblock $OCFT_loop2
+ mdadm --zero-superblock $OCFT_loop3
+ mdadm --remove $OCFT_raiddev 2>/dev/null
+ mdadm --remove $OCFT_raiddev2 2>/dev/null
+ loopbackeddev unmake $OCFT_disk0
+ loopbackeddev unmake $OCFT_disk1
+ loopbackeddev unmake $OCFT_disk2
+ loopbackeddev unmake $OCFT_disk3
+ rm -f $OCFT_raidconf
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_raidconf=$OCFT_raidconf
+ Env OCF_RESKEY_raiddev=$OCFT_raiddev
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE-BLOCK prepare_auto
+ Include required_args
+ Env OCF_RESKEY_raiddev="auto"
+ Include default_status
+
+CASE-BLOCK prepare_multiple
+ Include required_args
+ Env OCF_RESKEY_raiddev="$OCFT_raiddev $OCFT_raiddev2"
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_raiddev'"
+ Include prepare
+ Env OCF_RESKEY_raiddev=/dev/no_such_device
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "check base env: unset 'OCF_RESKEY_raiddev'"
+ Include prepare
+ Unenv OCF_RESKEY_raiddev
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "normal start (auto)"
+ Include prepare_auto
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+
+CASE "normal stop (auto)"
+ Include prepare_auto
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "normal start (multiple)"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+
+CASE "normal stop (multiple)"
+ Include prepare
+ Env OCF_RESKEY_raiddev="$OCFT_raiddev $OCFT_raiddev2"
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/SendArp b/tools/ocft/SendArp
new file mode 100644
index 0000000..7880388
--- /dev/null
+++ b/tools/ocft/SendArp
@@ -0,0 +1,74 @@
+# SendArp
+
+CONFIG
+ Agent SendArp
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage resource-agents
+ HangTimeout 15
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_ip=127.0.0.1
+ Env OCF_RESKEY_nic=lo
+ Env OCF_RESKEY_background=false
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_ip'"
+ Include prepare
+ Unenv OCF_RESKEY_ip
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set invalid 'OCF_RESKEY_ip'"
+ Include prepare
+ Env OCF_RESKEY_ip=not_ip_address
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "check base env: unset 'OCF_RESKEY_nic'"
+ Include prepare
+ Unenv OCF_RESKEY_nic
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: set invalid 'OCF_RESKEY_nic'"
+ Include prepare
+ Env OCF_RESKEY_nic=not_nic
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/VirtualDomain b/tools/ocft/VirtualDomain
new file mode 100644
index 0000000..3a44f20
--- /dev/null
+++ b/tools/ocft/VirtualDomain
@@ -0,0 +1,71 @@
+# VirtualDomain
+# by dejan@suse.de on
+# Tue Jul 8 12:48:03 CEST 2014
+
+CONFIG
+ Agent VirtualDomain
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+# set OCFT_config to the libvirt configuration file
+# the guest is going to be stopped and started
+VARIABLE
+ OCFT_config=/etc/libvirt/qemu/sle11-sp3.xml
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_config=$OCFT_config
+
+CASE-BLOCK unset_utilization
+ Env OCF_RESKEY_autoset_utilization_host_memory=false
+ Env OCF_RESKEY_autoset_utilization_hv_memory=false
+ Env OCF_RESKEY_autoset_utilization_cpu=false
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include unset_utilization
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_config'"
+ Include prepare
+ Env OCF_RESKEY_config=/no_such_file
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: unset 'OCF_RESKEY_config'"
+ Include prepare
+ Unenv OCF_RESKEY_config
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/Xen b/tools/ocft/Xen
new file mode 100644
index 0000000..731564b
--- /dev/null
+++ b/tools/ocft/Xen
@@ -0,0 +1,65 @@
+# Xen
+# by dejan@suse.de on
+# Tue Jul 8 12:20:23 CEST 2014
+
+CONFIG
+ Agent Xen
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 20
+
+# set OCFT_xmfile to the xen-xm format file
+# the guest is going to be stopped and started
+VARIABLE
+ OCFT_xmfile=/etc/xen/vm/xen-f
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_xmfile=$OCFT_xmfile
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_xmfile'"
+ Include prepare
+ Env OCF_RESKEY_xmfile=/no_such_file
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: unset 'OCF_RESKEY_xmfile'"
+ Include prepare
+ Unenv OCF_RESKEY_xmfile
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/Xinetd b/tools/ocft/Xinetd
new file mode 100644
index 0000000..53f4f65
--- /dev/null
+++ b/tools/ocft/Xinetd
@@ -0,0 +1,64 @@
+# Xinetd
+
+CONFIG
+ Agent Xinetd
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage xinetd
+
+SETUP-AGENT
+ sed -i /disable/s/yes/no/ /etc/xinetd.d/echo
+ if which /etc/init.d/xinetd >/dev/null 2>&1; then
+ /etc/init.d/xinetd start
+ elif systemctl list-unit-files | grep -qs xinetd; then
+ systemctl start xinetd
+ fi
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_service=discard
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_protocol'"
+ Include prepare
+ Unenv OCF_RESKEY_service
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/apache b/tools/ocft/apache
new file mode 100644
index 0000000..fe4f193
--- /dev/null
+++ b/tools/ocft/apache
@@ -0,0 +1,68 @@
+# apache
+# make sure that your apache configuration loads mod_status
+
+CONFIG
+ Agent apache
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage apache2
+ HangTimeout 20
+
+SETUP-AGENT
+ if systemctl list-unit-files 2>/dev/null | fgrep -q apache2.service; then
+ systemctl start apache2.service
+ systemctl stop apache2.service
+ else
+ /etc/init.d/apache2 start
+ /etc/init.d/apache2 stop
+ fi
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: set non-existing OCF_RESKEY_statusurl"
+ Include prepare
+ Env OCF_RESKEY_statusurl="yoyoyoyo"
+ AgentRun start OCF_ERR_GENERIC
+
+CASE "check base env: set non-existing OCF_RESKEY_configfile"
+ Include prepare
+ Env OCF_RESKEY_configfile="/yoyoyoyo/nosuchfile"
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/caselib.in b/tools/ocft/caselib.in
new file mode 100644
index 0000000..44165d6
--- /dev/null
+++ b/tools/ocft/caselib.in
@@ -0,0 +1,299 @@
+#
+# Copyright (c) 2010-2011 Novell Inc, John Shi
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+
+
+quit()
+{
+ local ret
+ ret="$1"
+
+ while [ $__OCFT__atexit_num -gt 0 ]; do
+ atexit$__OCFT__atexit_num
+ let __OCFT__atexit_num--
+ done
+
+ rm -rf $__OCFT__fakebin
+
+ exit $ret
+}
+
+agent_install()
+{
+ local pkg
+
+ if [ $# -eq 0 ]; then
+ return 0
+ fi
+
+ for pkg in "$@"; do
+ if [ -e /etc/SuSE-release ]; then
+ if ! zypper -q install -y "$pkg" >/dev/null 2>&1; then
+ echo
+ echo "${__OCFT__showhost}ERROR: Install '$pkg' failed."
+ quit 2
+ fi
+ elif [ -e /etc/debian_version ]; then
+ if ! apt-get -y install "$pkg" >/dev/null 2>&1; then
+ echo
+ echo "${__OCFT__showhost}ERROR: Install '$pkg' failed."
+ quit 2
+ fi
+ elif [ -e /etc/redhat-release ]; then
+ if ! yum -y install "$pkg" >/dev/null 2>&1; then
+ echo
+ echo "${__OCFT__showhost}ERROR: Install '$pkg' failed."
+ quit 2
+ fi
+ else
+ echo "${__OCFT__showhost}ERROR: Cannot detect your OS type."
+ quit 2
+ fi
+ done
+}
+
+set_ocf_env()
+{
+ export OCF_RA_VERSION_MAJOR=1
+ export OCF_RA_VERSION_MINOR=0
+ export OCF_RESOURCE_TYPE=$1
+ export OCF_RESOURCE_INSTANCE=${OCF_RESOURCE_INSTANCE:-"ocft"}
+}
+
+agent_run()
+{
+ local agent cmd timeout pid i ret aroot
+ agent="$1"
+ cmd="$2"
+ timeout="$3"
+
+ set_ocf_env $agent
+
+ export OCF_RESKEY_CRM_meta_timeout
+ : ${OCF_RESKEY_CRM_meta_timeout:=$((timeout*1000))}
+
+ aroot=${__OCFT__MYROOT:-$__OCFT__AGENT_ROOT}
+
+ setsid $aroot/$agent $cmd >${HA_RSCTMP}/.ocft_runlog 2>&1 &
+ pid=$!
+
+ i=0
+ while [ $i -lt $timeout ]; do
+ if [ ! -e /proc/$pid ]; then
+ break
+ fi
+ sleep 1
+ let i++
+ done
+
+ if [ $i -ge $timeout ]; then
+ kill -SIGTERM -$pid >/dev/null 2>&1
+ sleep 3
+ kill -SIGKILL -$pid >/dev/null 2>&1
+ echo -n "${__OCFT__showhost}ERROR: The agent was hanging, killed it, "
+ echo "maybe you damaged the agent or system's environment, see details below:"
+ cat ${HA_RSCTMP}/.ocft_runlog
+ echo
+ quit 1
+ fi
+
+ wait $pid
+}
+
+check_success()
+{
+ local ret msg
+ ret="$1"
+ msg="$2"
+
+ if [ $ret -ne 0 ]; then
+ echo "${__OCFT__showhost}ERROR: '${msg}' failed, the return code is ${ret}."
+ quit 1
+ fi
+}
+
+__maxfd()
+{
+ (echo 0; ls -1 /proc/$$/fd) | sort -rn | head -1
+}
+
+__getfd()
+{
+ local host rw fd file
+ host="$1"
+ rw="$2"
+
+ for fd in /proc/$$/fd/*; do
+ file=$(basename "$(readlink $fd)")
+ if [ "$file" = "${host}_$rw" ]; then
+ basename $fd
+ break
+ fi
+ done
+}
+
+backbash_start()
+{
+ local host fd rfd wfd
+ host="$1"
+
+ if [ ! -d "$__OCFT__CASES_DIR" ]; then
+ echo "${__OCFT__showhost}ERROR: Could not found Directory: ${__OCFT__CASES_DIR}."
+ quit 1
+ fi
+
+ if lsof $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w >/dev/null 2>&1; then
+ echo "${__OCFT__showhost}ERROR: Connection exist with $host."
+ quit 1
+ fi
+ if [ ! -p "$__OCFT__CASES_DIR/${host}_r" ] || [ ! -p "$__OCFT__CASES_DIR/${host}_w" ]; then
+ rm -f $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w
+ if ! mkfifo $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w >/dev/null 2>&1; then
+ echo "${__OCFT__showhost}ERROR: Could not create pipe file: $__OCFT__CASES_DIR/${host}_*."
+ quit 1
+ fi
+ fi
+
+ ssh root@$host '@BASH_SHELL@ 2>&1
+ sed "s/00/001/g" ${HA_RSCTMP}/.backbash-log
+ echo 000
+ echo 1' >$__OCFT__CASES_DIR/${host}_r <$__OCFT__CASES_DIR/${host}_w &
+
+ fd=$(__maxfd)
+ rfd=$(expr $fd + 1)
+ wfd=$(expr $fd + 2)
+ eval "exec ${rfd}<$__OCFT__CASES_DIR/${host}_r ${wfd}>$__OCFT__CASES_DIR/${host}_w"
+}
+
+backbash()
+{
+ local host rfd wfd ret
+ host="$1"
+
+ rfd=$(__getfd $host r)
+ wfd=$(__getfd $host w)
+
+ if [ -z "$rfd" -o -z "$wfd" ]; then
+ echo "${__OCFT__showhost}ERROR: Could not found connection with $host."
+ fi
+
+ cat >&$wfd <<EOF
+{
+true
+EOF
+ cat >&$wfd
+ cat >&$wfd <<EOF
+
+} >&${HA_RSCTMP}/.backbash-log
+sed 's/00/001/g' ${HA_RSCTMP}/.backbash-log
+echo 000
+echo 0
+EOF
+ if [ $? -ne 0 ]; then
+ echo "${__OCFT__showhost}ERROR: Broken connection with $host."
+ quit 1
+ fi
+
+ awk -vlive=2 '{
+ if (sub(/000$/, "")) {
+ if ($0 != "") {
+ gsub("001", "00");
+ printf("%s", $0);
+ }
+ getline live;
+ exit;
+ }
+ gsub("001", "00");
+ print;
+ } END {
+ exit(live);
+ }' <&$rfd
+ case $? in
+ 1)
+ quit 1
+ ;;
+ 2)
+ echo "${__OCFT__showhost}ERROR: Broken connection with $host."
+ quit 1
+ ;;
+ esac
+}
+
+backbash_stop()
+{
+ local host rfd wfd
+ host="$1"
+
+ wfd=$(__getfd $host w)
+ if [ -n "$wfd" ]; then
+ cat >&$wfd <<<'quit 0'
+ fi
+ rm -f $__OCFT__CASES_DIR/${host}_r $__OCFT__CASES_DIR/${host}_w
+}
+
+
+export OCF_ROOT=@OCF_ROOT_DIR@
+export OCF_LIB=@OCF_LIB_DIR@/heartbeat
+__OCFT__AGENT_ROOT=@OCF_RA_DIR@/heartbeat
+__OCFT__CASES_DIR=/var/lib/@PACKAGE_NAME@/ocft/cases
+OCFT_DIR=@datadir@/@PACKAGE_NAME@/ocft
+
+. $OCFT_DIR/helpers.sh
+
+__OCFT__atexit_num=0
+
+if [ $EUID -ne 0 ]; then
+ echo "${__OCFT__showhost}ERROR: '$0' needs to be run by root."
+ quit 3
+fi
+
+__OCFT__fakebin=./fakebin
+
+mkdir -p $__OCFT__fakebin >/dev/null 2>&1 &&
+ln -sf /bin/true $__OCFT__fakebin/crm_master >/dev/null 2>&1 &&
+ln -sf /bin/true $__OCFT__fakebin/crm_mon >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "${__OCFT__showhost}ERROR: initialize 'fakebin' failed."
+ quit 3
+fi
+export HA_SBIN_DIR=$__OCFT__fakebin
+
+. $OCF_LIB/ocf-returncodes || {
+ echo "${__OCFT__showhost}ERROR: $OCF_LIB/ocf-returncodes not found."
+ quit 3
+}
+
+. $OCF_LIB/ocf-directories || {
+ echo "${__OCFT__showhost}ERROR: $OCF_LIB/ocf-directories not found."
+ quit 3
+}
+
+while read __OCFT__line; do
+ if [ -n "$__OCFT__line" ]; then
+ __OCFT__retn=${__OCFT__line%%=*}
+ __OCFT__reti=$(eval echo \$$__OCFT__retn)
+ __OCFT__retval[__OCFT__reti]=$__OCFT__retn
+ fi
+done <<<"$(sed 's/#.*//' $OCF_LIB/ocf-returncodes)"
+
+
+# vim:ts=2:sw=2:et:
diff --git a/tools/ocft/db2 b/tools/ocft/db2
new file mode 100644
index 0000000..5c4d6ea
--- /dev/null
+++ b/tools/ocft/db2
@@ -0,0 +1,164 @@
+# db2
+#
+# This test assumes a db2 ESE instance with two partitions and a database.
+# Default is instance=db2inst1, database=ocft
+# adapt this in set_testenv below
+#
+# Simple steps to generate a test environment (if you don't have one):
+#
+# A virtual machine with 1200MB RAM is sufficient
+#
+# - download an eval version of DB2 server from IBM
+# - create an user "db2inst1" in group "db2inst1"
+#
+# As root
+# - install DB2 software in some location
+# - create instance
+# cd <this_location>/instance
+# ./db2icrt -s ese -u db2inst1 db2inst1
+# - adapt profile of db2inst1 as instructed by db2icrt
+#
+# As db2inst1
+# # allow to run with small memory footprint
+# db2set DB2_FCM_SETTINGS=FCM_MAXIMIZE_SET_SIZE:FALSE
+# db2start
+# db2start dbpartitionnum 1 add dbpartitionnum hostname $(uname -n) port 1 without tablespaces
+# db2stop
+# db2start
+# db2 create database ocft
+# Done
+# In order to install a real cluster refer to http://www.linux-ha.org/wiki/db2_(resource_agent)
+
+CONFIG
+ Agent db2
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 40
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK set_testenv
+ Env OCFT_instance=db2inst1
+ Env OCFT_db=ocft
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_instance=$OCFT_instance
+ Env OCF_RESKEY_CRM_meta_timeout=30000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include set_testenv
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_instance'"
+ Include prepare
+ Env OCF_RESKEY_instance=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "invalid instance config"
+ Include prepare
+ Bash eval mv ~$OCFT_instance/sqllib ~$OCFT_instance/sqllib-
+ BashAtExit eval mv ~$OCFT_instance/sqllib- ~$OCFT_instance/sqllib
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "started: monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not started: monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "killed instance: monitor"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ BashAtExit rm /tmp/ocft-helper1
+ Bash echo "su $OCFT_instance -c '. ~$OCFT_instance/sqllib/db2profile; db2nkill 0 >/dev/null 2>&1'" > /tmp/ocft-helper1
+ Bash sh -x /tmp/ocft-helper1
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "overload param instance by admin"
+ Include prepare
+ Env OCF_RESKEY_instance=no_such
+ Env OCF_RESKEY_admin=$OCFT_instance
+ AgentRun start OCF_SUCCESS
+
+CASE "check start really activates db"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+ BashAtExit rm /tmp/ocft-helper2
+ Bash echo "su $OCFT_instance -c '. ~$OCFT_instance/sqllib/db2profile; db2 get snapshot for database on $OCFT_db>/dev/null'" > /tmp/ocft-helper2
+ Bash sh -x /tmp/ocft-helper2
+
+CASE "multipartion test"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+
+ # start does not start partion 1
+ Env OCF_RESKEY_dbpartitionnum=1
+ AgentRun monitor OCF_NOT_RUNNING
+
+ # now start 1
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+
+ # now stop 1
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+
+ # does not affect 0
+ Env OCF_RESKEY_dbpartitionnum=0
+ AgentRun monitor OCF_SUCCESS
+
+# fault injection does not work on the 1.0.4 client due to a hardcoded path
+CASE "simulate hanging db2stop (not meaningful for 1.0.4 agent)"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Bash [ ! -f /usr/local/bin/db2stop ]
+ BashAtExit rm /usr/local/bin/db2stop
+ Bash echo -e "#!/bin/sh\necho fake db2stop\nsleep 10000" > /usr/local/bin/db2stop
+ Bash chmod +x /usr/local/bin/db2stop
+ AgentRun stop OCF_SUCCESS
+
+# fault injection does not work on the 1.0.4 client due to a hardcoded path
+CASE "simulate not stopping db2stop (not meaningful for 1.0.4 agent)"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Bash [ ! -f /usr/local/bin/db2stop ]
+ BashAtExit rm /usr/local/bin/db2stop
+ Bash echo -e "#!/bin/sh\necho fake db2stop\nexit 0" > /usr/local/bin/db2stop
+ Bash chmod +x /usr/local/bin/db2stop
+ AgentRun stop OCF_SUCCESS
diff --git a/tools/ocft/drbd.linbit b/tools/ocft/drbd.linbit
new file mode 100644
index 0000000..57fa088
--- /dev/null
+++ b/tools/ocft/drbd.linbit
@@ -0,0 +1,183 @@
+# linbit: drbd
+
+CONFIG
+ Agent drbd
+ AgentRoot /usr/lib/ocf/resource.d/linbit
+ InstallPackage drbd
+ HangTimeout 20
+
+VARIABLE
+ DRBDCONF=${HA_RSCTMP}/ocft_drbd_tmp.conf
+
+ # should be this machine's hostname/ip, please modify it by yourself.
+ NAME_1=HOSTNAME1
+ IP_1=IP_ADDRESS1
+
+ # the block device just for test, please modify it by yourself.
+ DISK_1=/dev/DEVICE1
+
+ PORT_1=5735
+ DEVICE_1=/dev/drbd0
+
+ ####################################################################
+
+ # please modify it by yourself.
+ NAME_2=HOSTNAME2
+ IP_2=IP_ADDRESS2
+
+ # the block device just for test, please modify it by yourself.
+ DISK_2=/dev/DEVICE2
+
+ PORT_2=5735
+ DEVICE_2=/dev/drbd0
+
+
+
+SETUP-AGENT
+ cat >$DRBDCONF <<EOF
+ global {
+ usage-count no;
+ }
+
+ resource ocft0 {
+ protocol C;
+ disk {
+ on-io-error detach;
+ }
+ on $NAME_1 {
+ device $DEVICE_1;
+ address $IP_1:$PORT_1;
+ meta-disk internal;
+ disk $DISK_1;
+ }
+ on $NAME_2 {
+ device $DEVICE_2;
+ address $IP_2:$PORT_2;
+ meta-disk internal;
+ disk $DISK_2;
+ }
+ }
+EOF
+ HOST=$(uname -n)
+ DRBDADM="drbdadm -c $DRBDCONF"
+
+ # prepare
+ modprobe drbd $(drbdadm sh-mod-parms)
+ $DRBDADM down ocft0
+
+ # create meta data block if necessary
+ $DRBDADM dump-md ocft0 >/dev/null 2>&1
+ if [ $? -eq 255 ]; then
+ $DRBDADM create-md ocft0
+ fi
+
+ # start drbd
+ $DRBDADM up ocft0
+
+ # UpToDate
+ if [ "$HOST" = "$NAME_1" ]; then
+ $DRBDADM wait-connect ocft0
+ echo "drbd Syncing .."
+ $DRBDADM primary --force ocft0
+ while true; do
+ CSTATE=$($DRBDADM cstate ocft0)
+ DSTATE=$($DRBDADM dstate ocft0)
+ if [ "$CSTATE" = "Connected" -a "$DSTATE" = "UpToDate/UpToDate" ]; then
+ break
+ else
+ sleep 3
+ fi
+ done
+ echo "done"
+ fi
+
+CLEANUP-AGENT
+ drbdadm -c $DRBDCONF down ocft0
+ rm -f $DRBDCONF
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_drbdconf=$DRBDCONF
+ Env OCF_RESKEY_drbd_resource=ocft0
+ Env OCF_RESKEY_CRM_meta_notify=true
+ Env OCF_RESKEY_CRM_meta_clone_max=2
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include required_args
+ AgentRun validate-all OCF_SUCCESS
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "wrong path of config file"
+ Include prepare
+ Env OCF_RESKEY_drbdconf=no_such_file
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "wrong resource name"
+ Include prepare
+ Env OCF_RESKEY_drbd_resource=no_such_src
+ # OCF_RESKEY_drbd_resource is a required parameter in agent meta-data,
+ # if wrong, I think the agent should return OCF_ERR_CONFIGURED.
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "Primary/Secondary monitor"
+ Include prepare
+ AgentRun start
+ AgentRun promote
+ AgentRun monitor OCF_RUNNING_MASTER
+ AgentRun demote
+ AgentRun monitor OCF_SUCCESS
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "try to 'promote' in single-primary mode"
+ Include prepare
+ Include@$IP_2 prepare
+
+ # start drbd
+ AgentRun start
+ AgentRun@$IP_2 start
+
+ # promote local drbd first
+ AgentRun promote OCF_SUCCESS
+
+ # demote local drbd prepare for remote drbd promote
+ AgentRun demote
+ # remote drbd promote
+ AgentRun@$IP_2 promote OCF_SUCCESS
+
+ # promote fails, because remote drbd promote first.
+ AgentRun promote OCF_ERR_GENERIC
diff --git a/tools/ocft/exportfs b/tools/ocft/exportfs
new file mode 100644
index 0000000..285a4b8
--- /dev/null
+++ b/tools/ocft/exportfs
@@ -0,0 +1,80 @@
+# exportfs
+#
+#
+
+CONFIG
+ Agent exportfs
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage nfs-kernel-server
+ HangTimeout 40
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK set_testenv
+ Env OCF_RESKEY_directory=/usr
+ Env OCF_RESKEY_fsid=105
+ Env OCF_RESKEY_clientspec="*"
+ Env OCF_RESKEY_CRM_meta_timeout=30000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include set_testenv
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: no 'OCF_RESKEY_fsid'"
+ Include prepare
+ Env OCF_RESKEY_fsid=
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: invalid 'OCF_RESKEY_directory'"
+ Include prepare
+ Env OCF_RESKEY_directory=/no_such
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: non-decimal 'OCF_RESKEY_fsid'"
+ Include prepare
+ Env OCF_RESKEY_fsid="4f838db14f838db14f838db14f838db1"
+ AgentRun start OCF_SUCCESS
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "stop with no env"
+ Include prepare
+ Env OCF_RESKEY_directory=/no_such
+ AgentRun stop OCF_SUCCESS
+
+CASE "started: monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not started: monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
diff --git a/tools/ocft/exportfs-multidir b/tools/ocft/exportfs-multidir
new file mode 100644
index 0000000..00e41f0
--- /dev/null
+++ b/tools/ocft/exportfs-multidir
@@ -0,0 +1,80 @@
+# exportfs
+#
+#
+
+CONFIG
+ Agent exportfs
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage nfs-kernel-server
+ HangTimeout 40
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK set_testenv
+ Env OCF_RESKEY_directory="/usr /var"
+ Env OCF_RESKEY_fsid=105
+ Env OCF_RESKEY_clientspec="*"
+ Env OCF_RESKEY_CRM_meta_timeout=30000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include set_testenv
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: no 'OCF_RESKEY_fsid'"
+ Include prepare
+ Env OCF_RESKEY_fsid=
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: invalid 'OCF_RESKEY_directory'"
+ Include prepare
+ Env OCF_RESKEY_directory=/no_such
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: invalid 'OCF_RESKEY_fsid'"
+ Include prepare
+ Env OCF_RESKEY_fsid=root
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "stop with no env"
+ Include prepare
+ Env OCF_RESKEY_directory="/usr /no_such"
+ AgentRun stop OCF_SUCCESS
+
+CASE "started: monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not started: monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
diff --git a/tools/ocft/helpers.sh b/tools/ocft/helpers.sh
new file mode 100644
index 0000000..9848934
--- /dev/null
+++ b/tools/ocft/helpers.sh
@@ -0,0 +1,43 @@
+get_rundir() {
+ local rundir
+ rundir="`mount | grep '/run ' | awk '{print $3}'`"
+ echo ${rundir:-"/var/run"}
+}
+
+loopbackeddev() {
+ local action file size ctlfile
+
+ action="$1"
+ file="$2"
+ size="$3"
+ ctlfile=$HA_RSCTMP/`echo $file | tr / _`
+
+ case "$action" in
+ start|setup|make)
+ if [ ! -f "$ctlfile" ]; then
+ if [ -z "$size" ]; then
+ echo "usage: $0 action file size" >&2
+ exit 1
+ fi
+ loopdev=`losetup -f`
+ if ! dd if=/dev/zero of=$file bs=1 count=0 seek=$size 2>/dev/null; then
+ echo "$0: dd failed" >&2
+ exit 1
+ fi
+ if ! losetup $loopdev $file; then
+ echo "$0: losetup failed" >&2
+ exit 1
+ fi
+ echo $loopdev | tee $ctlfile
+ else
+ cat $ctlfile
+ fi
+ ;;
+ stop|undo|unmake)
+ if [ -f "$ctlfile" ]; then
+ losetup -d `cat $ctlfile`
+ rm -f $file $ctlfile
+ fi
+ ;;
+ esac
+}
diff --git a/tools/ocft/iscsi b/tools/ocft/iscsi
new file mode 100644
index 0000000..3c5d524
--- /dev/null
+++ b/tools/ocft/iscsi
@@ -0,0 +1,108 @@
+# iscsi
+
+CONFIG
+ Agent iscsi
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage open-iscsi targetcli
+ HangTimeout 20
+
+VARIABLE
+ OCFT_rundir="`get_rundir`"
+ OCFT_disk="$OCFT_rundir/resource-agents/ocft-iscsi"
+ OCFT_target="iqn.2003-01.org.linux-iscsi:ocft"
+ OCFT_portal="127.0.0.1:3260"
+ OCFT_loop="`loopbackeddev make $OCFT_disk 16M`"
+ OCF_RESKEY_iqn=$OCFT_target
+ OCF_RESKEY_portals=$OCFT_portal
+ OCF_RESKEY_target_iqn=$OCFT_target
+ OCF_RESKEY_path=$OCFT_loop
+ OCF_RESKEY_lun=1
+
+SETUP-AGENT
+ if systemctl list-unit-files 2>/dev/null | fgrep -q iscsid.service; then
+ systemctl start iscsid.service
+ else
+ /etc/init.d/open-iscsi start
+ fi
+ if systemctl list-unit-files 2>/dev/null | fgrep -q target.service; then
+ systemctl start target.service
+ else
+ /etc/init.d/target start
+ fi
+ export OCF_RESKEY_iqn=$OCFT_target
+ export OCF_RESKEY_portals=$OCFT_portal
+ export OCF_RESKEY_target_iqn=$OCFT_target
+ export OCF_RESKEY_path=$OCFT_loop
+ export OCF_RESKEY_lun=1
+ /usr/lib/ocf/resource.d/heartbeat/iSCSITarget start
+ /usr/lib/ocf/resource.d/heartbeat/iSCSILogicalUnit start
+
+CLEANUP-AGENT
+ export OCF_RESKEY_iqn=$OCFT_target
+ export OCF_RESKEY_portals=$OCFT_portal
+ export OCF_RESKEY_target_iqn=$OCFT_target
+ export OCF_RESKEY_path=$OCFT_loop
+ export OCF_RESKEY_lun=1
+ /usr/lib/ocf/resource.d/heartbeat/iSCSILogicalUnit stop
+ /usr/lib/ocf/resource.d/heartbeat/iSCSITarget stop
+ if systemctl list-unit-files 2>/dev/null | fgrep -q target.service; then
+ systemctl stop target.service
+ fi
+ loopbackeddev unmake $OCFT_disk
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_portal=$OCFT_portal
+ Env OCF_RESKEY_target=$OCFT_target
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_portal'"
+ Include prepare
+ Unenv OCF_RESKEY_portal
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: unset 'OCF_RESKEY_target'"
+ Include prepare
+ Unenv OCF_RESKEY_target
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
diff --git a/tools/ocft/jboss b/tools/ocft/jboss
new file mode 100644
index 0000000..bc99d8f
--- /dev/null
+++ b/tools/ocft/jboss
@@ -0,0 +1,83 @@
+# jboss
+#
+# NOTE: Clean up $jboss_home/standalone/log before running this test
+# otherwise creating the pid/log files may fail
+# in the test case with a different user.
+
+CONFIG
+ Agent jboss
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 120
+
+# Note : Change setting by a version of JBoss.
+#
+VARIABLE
+ # JBoss5 Environment require
+# jboss_version=5
+# jboss_home=/opt/jboss5/current
+# java_home=/usr/lib/jvm/java-1.6.0-openjdk.x86_64
+# user=jboss5
+ # JBoss6 Environment require
+ jboss_version=6
+ jboss_home=/opt/jboss6/current
+ java_home=/usr/lib/jvm/java-1.7.0-openjdk.x86_64
+ user=jboss6
+
+CASE-BLOCK required_args_jboss
+ Env OCF_RESKEY_jboss_home=${jboss_home}
+ Env OCF_RESKEY_java_home=${java_home}
+ Env OCF_RESKEY_jboss_version=${jboss_version}
+ Env OCF_RESKEY_user=${user}
+
+CASE-BLOCK args_clear
+ Unenv OCF_RESKEY_jboss_home
+ Unenv OCF_RESKEY_java_home
+ Unenv OCF_RESKEY_jboss_version
+ Unenv OCF_RESKEY_user
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare_jboss
+ Include required_args_jboss
+ Include default_status
+
+# Test CASE
+#
+CASE "normal start jboss require_args (user:user)"
+ Include prepare_jboss
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
+CASE "normal start jboss require_args (user:root)"
+ Include prepare_jboss
+ Unenv OCF_RESKEY_user
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
+CASE "error start jboss no jboss_home"
+ Include prepare_jboss
+ Unenv OCF_RESKEY_jboss_home
+ AgentRun start OCF_ERR_INSTALLED
+ Include args_clear
+
+CASE "error start jboss no java_home"
+ Include prepare_jboss
+ Unenv OCF_RESKEY_java_home
+ AgentRun start OCF_ERR_INSTALLED
+ Include args_clear
+
+CASE "error start jboss no java command"
+ Include prepare_jboss
+ Env OCF_RESKEY_java_home=/var
+ AgentRun start OCF_ERR_INSTALLED
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
diff --git a/tools/ocft/mysql b/tools/ocft/mysql
new file mode 100644
index 0000000..305a900
--- /dev/null
+++ b/tools/ocft/mysql
@@ -0,0 +1,82 @@
+# mysql
+
+CONFIG
+ Agent mysql
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage mysql
+ HangTimeout 20
+
+SETUP-AGENT
+ if systemctl list-unit-files 2>/dev/null | fgrep -q mysql.service; then
+ systemctl start mysql.service
+ systemctl stop mysql.service
+ else
+ /etc/init.d/mysql start
+ /etc/init.d/mysql stop
+ fi
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_CRM_meta_timeout=15000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_binary'"
+ Include prepare
+ Env OCF_RESKEY_binary=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "check lib file"
+ Include prepare
+ Bash chmod u-w /var/lib/mysql
+ BashAtExit chmod u+w /var/lib/mysql
+ AgentRun start OCF_ERR_PERM
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "non-existent user"
+ Include prepare
+ Env OCF_RESKEY_user=no_user
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "invalid user"
+ Include prepare
+ Env OCF_RESKEY_user=nobody
+ AgentRun start OCF_ERR_PERM
diff --git a/tools/ocft/mysql-proxy b/tools/ocft/mysql-proxy
new file mode 100644
index 0000000..e16d52b
--- /dev/null
+++ b/tools/ocft/mysql-proxy
@@ -0,0 +1,83 @@
+# mysql-proxy
+# by r.bhatia@ipax.at
+#
+# test cases (to implement):
+#
+# * /usr/sbin/ocf-tester -n mp -o binary="/usr/sbin/mysql-proxy" -o defaults_file="" -o parameters="--proxy-skip-profiling" \
+# -o admin_address="127.0.0.1:4041" -o admin_username="root" -o admin_password="la" -o admin_lua_script="/usr/lib/mysql-proxy/lua/admin.lua" \
+# -o proxy_backend_addresses="192.168.100.200:42006" -o proxy_address="/var/run/mysqld/mysqld.sock" /usr/lib/ocf/resource.d/heartbeat/mysql-proxy
+#
+# * OCF_CHECK_LEVEL 20 check
+
+CONFIG
+ Agent mysql-proxy
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat/
+ InstallPackage mysql-proxy
+ HangTimeout 20
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_CRM_meta_timeout=15000
+ Env OCF_RESKEY_binary=/tmp/mysql-proxy
+ Env OCF_RESKEY_admin_username=root
+ Env OCF_RESKEY_admin_password=test123
+ Env OCF_RESKEY_admin_lua_script=/usr/lib/mysql-proxy/lua/admin.lua
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Bash [ ! -x /tmp/mysql-proxy ] && ln -s `which mysql-proxy` /tmp/mysql-proxy || true
+ Include crm_setting
+
+CASE-BLOCK teardown
+ AgentRun stop
+ BashAtExit rm -f /tmp/mysql-proxy
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Include teardown
+
+CASE "check base env: invalid 'OCF_RESKEY_binary'"
+ Include prepare
+ Env OCF_RESKEY_binary=no_such
+ AgentRun start OCF_ERR_INSTALLED
+ BashAtExit rm -f /tmp/mysql-proxy
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+ Include teardown
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+ Include teardown
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+ Include teardown
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+ Include teardown
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/named b/tools/ocft/named
new file mode 100644
index 0000000..90a4351
--- /dev/null
+++ b/tools/ocft/named
@@ -0,0 +1,69 @@
+#named
+
+# To work properly this test requires that standard bind and bin-utils
+# packages installed.
+
+CONFIG
+ Agent named
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage bind
+ InstallPackage bind-utils
+
+SETUP-AGENT
+ /etc/init.d/named start
+ /etc/init.d/named stop
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_CRM_meta_timeout=15000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_named'"
+ Include prepare
+ Env OCF_RESKEY_named=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "non-existent user"
+ Include prepare
+ Env OCF_RESKEY_named_user=no_user
+ AgentRun start OCF_ERR_INSTALLED
diff --git a/tools/ocft/nfsserver b/tools/ocft/nfsserver
new file mode 100644
index 0000000..cf2ac89
--- /dev/null
+++ b/tools/ocft/nfsserver
@@ -0,0 +1,69 @@
+# nfsserver
+
+CONFIG
+ Agent nfsserver
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage nfs-kernel-server
+ HangTimeout 20
+
+SETUP-AGENT
+ INITSCRIPT=""
+ if test -f /etc/init.d/nfsserver; then
+ INITSCRIPT="/etc/init.d/nfsserver"
+ fi
+ true
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_nfs_init_script=$INITSCRIPT
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_nfs_init_script'"
+ Include prepare
+ Env OCF_RESKEY_nfs_init_script=no_such_script
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: invalid 'OCF_RESKEY_nfs_notify_cmd'"
+ Include prepare
+ Env OCF_RESKEY_nfs_notify_cmd=no_such_program
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/ocft.in b/tools/ocft/ocft.in
new file mode 100644
index 0000000..0d7f645
--- /dev/null
+++ b/tools/ocft/ocft.in
@@ -0,0 +1,893 @@
+#!@BASH_SHELL@
+
+# Copyright (c) 2010-2013 Novell Inc, John Shi
+# All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of version 2 of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# Further, this software is distributed without any warranty that it is
+# free of the rightful claim of any third person regarding infringement
+# or the like. Any license provided herein, whether implied or
+# otherwise, applies only to this software file. Patent licenses, if
+# any, provided herein do not apply to combinations of this program with
+# other software, or any other product whatsoever.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+
+die()
+{
+ local str
+ str="$1"
+
+ echo "ERROR: $str" >&2
+ exit 1
+}
+
+warn()
+{
+ local str
+ str="$1"
+
+ echo "WARNING: $str" >&2
+}
+
+parse_die()
+{
+ local str
+ str="$1"
+
+ agent_parse_finish
+ die "${agent}: line ${line_num}: ${str}"
+}
+
+# add quotes to string for Here Documents
+add_quotes()
+{
+ local typ str a b
+ typ="$1"
+ str="$2"
+
+ case "$typ" in
+ 1) a=\'; b=\";;
+ 2) a=\"; b=\';;
+ esac
+
+ echo "$str" | sed "s/$a/$a$b$a$b$a/g; 1 s/^/$a/; $ s/$/$a/"
+}
+
+# split strings
+explode()
+{
+ local str
+ str="$1"
+
+ echo "$str" | awk -F'"' '{
+ if (NF > 0 && NF%2 == 0)
+ exit(1);
+ for (i=1; i<=NF; i++) {
+ if (i%2 == 0)
+ print $i;
+ else {
+ len = split($i, str, /[ \t]+/);
+ for (j=1; j<=len; j++) {
+ sb = sub(/#.*/, "", str[j]);
+ if (str[j] != "")
+ print str[j];
+ if (sb)
+ exit(0);
+ }
+ }
+ }
+ }'
+}
+
+# phase 1: parse the string to 'command' and 'argument collection'.
+line2trunk()
+{
+ trunk[0]="${line%%[[:blank:]]*}"
+ trunk[1]="${line#*[[:blank:]]}"
+}
+
+# phase 2: split the argument collection.
+trunk2branch()
+{
+ local IFS
+
+ # Some of statements need one parameter at least.
+ if [ "$line" = "${trunk[0]}" ]; then
+ parse_die "missing parameter."
+ fi
+
+ IFS=$'\n'
+ branch=($(explode "${trunk[1]}"))
+ if [ $? -ne 0 ]; then
+ parse_die "missing '\"'."
+ fi
+}
+
+preparse_cfg()
+{
+ local agent line trunk branch macro num host
+ agent="$1"
+
+ if [ ! -r "$opt_cfgsdir/$agent" ]; then
+ die "${agent}: configuration file not found."
+ fi
+
+ line_num=0
+ while read -r line; do
+ let line_num++
+ num=" $line_num"
+
+ case "$line" in
+ ""|\#*) continue;;
+ esac
+
+ line2trunk
+ case "${trunk[0]}" in
+ CASE-BLOCK)
+ trunk2branch
+ macro="$CASES_DIR/${agent}_macro.${branch[0]}"
+ continue
+ ;;
+ Include|Include@*)
+ host=$(echo "${trunk[0]}" | awk -F@ '{print $2}')
+ trunk2branch
+ if [ ! -r "$CASES_DIR/${agent}_macro.${branch[0]}" ]; then
+ parse_die "Macro '${branch[0]}' not found."
+ fi
+ if [ -n "$host" ]; then
+ line="$(sed -e 's/^\([^[:blank:]]*\)@[^[:blank:]]*/\1/' -e "s/^[^[:blank:]]*/&@$host/" "$CASES_DIR/${agent}_macro.${branch[0]}")"
+ else
+ line="$(<"$CASES_DIR/${agent}_macro.${branch[0]}")"
+ fi
+ num=
+ ;;
+ *[!A-Z-]*)
+ :
+ ;;
+ *)
+ macro=
+ ;;
+ esac
+
+ if [ -n "$macro" ]; then
+ echo "$line$num" >>"$macro"
+ else
+ echo "$line$num" >>"$CASES_DIR/${agent}_preparse"
+ fi
+ done <"$opt_cfgsdir/$agent"
+}
+
+case_parse_finish()
+{
+ local host
+
+ if [ -n "$sh" ]; then
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo
+fi
+# Cleanup and exit
+EOF
+ for host in $hosts; do
+ echo "backbash_stop $host" >>$sh
+ done
+ echo "quit 0" >>$sh
+ fi
+
+ atexit_num=0
+ hosts=
+ sh=
+}
+
+init_cfg_vars()
+{
+ cfg_agent=
+ cfg_agent_root=
+ cfg_install_package=()
+ cfg_hang_timeout=20
+}
+
+agent_parse_finish()
+{
+ local suf
+
+ for suf in preparse setup cleanup var hosts; do
+ rm -f $CASES_DIR/${agent}_$suf
+ done
+ rm -f $CASES_DIR/${agent}_macro.*
+ init_cfg_vars
+}
+
+need_make()
+{
+ local src_time obj_time
+
+ if [ ! -f "$CASES_DIR/0_${agent}.sh" ]; then
+ return 0
+ fi
+
+ src_time=$(stat -c '%Y' "$opt_cfgsdir/$agent")
+ obj_time=$(stat -c '%Y' "$CASES_DIR/0_${agent}.sh")
+
+ test $src_time -ge $obj_time
+}
+
+parse_cfg()
+{
+ local agents i line stat sh trunk branch atexit_num host hosts
+
+ if [ $# -eq 0 ]; then
+ agents=($opt_cfgsdir/*)
+ else
+ agents=("$@")
+ fi
+
+ for agent in "${agents[@]}"; do
+ agent="$(basename "$agent")"
+
+ if ! need_make; then
+ continue
+ fi
+
+ agent_obj_clean $agent
+ agent_parse_finish
+
+ i=0
+
+ echo "Making '$agent': "
+ preparse_cfg "$agent"
+ while read -r line; do
+ line_num="${line##* }"
+ line="${line% *}"
+ line2trunk
+
+ # state switch
+ case "${trunk[0]}" in
+ CONFIG)
+ case_parse_finish
+ stat=1
+ continue
+ ;;
+ VARIABLE)
+ case_parse_finish
+ stat=2
+ continue
+ ;;
+ SETUP-AGENT)
+ case_parse_finish
+ stat=3
+ continue
+ ;;
+ CLEANUP-AGENT)
+ case_parse_finish
+ stat=4
+ continue
+ ;;
+ CASE)
+ case_parse_finish
+ trunk2branch
+ echo " - case ${i}: ${branch[0]}"
+ sh="$CASES_DIR/${i}_${agent}.sh"
+ cat >$sh <<EOF
+#!@BASH_SHELL@
+
+# Agent: $cfg_agent
+# Summary: ${branch[0]}
+
+. $OCFT_DIR/caselib || {
+ echo "ERROR: '$OCFT_DIR/caselib' not found."
+ exit 2
+}
+
+$(test -r $CASES_DIR/${agent}_var && cat $CASES_DIR/${agent}_var)
+
+__OCFT__MYROOT="$cfg_agent_root"
+
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo -e $(add_quotes 1 "Starting '\\033[33m${agent}\\033[0m' case $i '\\033[33m${branch[0]}\\033[0m':")
+else
+ echo -n "${agent}: ${branch[0]} - "
+fi
+EOF
+ chmod a+x $sh
+
+ let i++
+ stat=5
+ continue
+ ;;
+ esac
+
+ case "$stat" in
+ 1)
+ case "${trunk[0]}" in
+ Agent)
+ trunk2branch
+ cfg_agent="${branch[0]}"
+ ;;
+ AgentRoot)
+ trunk2branch
+ cfg_agent_root="${branch[0]}"
+ ;;
+ InstallPackage)
+ trunk2branch
+ cfg_install_package=(${cfg_install_package[@]} ${branch[@]})
+ ;;
+ HangTimeout)
+ trunk2branch
+ if ! echo "${branch[0]}" | grep -qxE '[0-9]+'; then
+ parse_die "numeric argument required."
+ fi
+ cfg_hang_timeout="${branch[0]}"
+ ;;
+ *)
+ parse_die "unimplemented statement: ${trunk[0]}"
+ ;;
+ esac
+ ;;
+ 2)
+ if echo "$line" | grep -q '^__OCFT__'; then
+ parse_die "reserved key word '__OCFT__'."
+ fi
+ echo "declare $line" >>$CASES_DIR/${agent}_var
+ ;;
+ 3)
+ echo "$line" >>$CASES_DIR/${agent}_setup
+ ;;
+ 4)
+ echo "$line" >>$CASES_DIR/${agent}_cleanup
+ ;;
+ 5)
+ host=$(echo ${trunk[0]} | awk -F@ '{print $2}')
+ if [ -n "$host" ]; then
+ if ! echo "$hosts" | grep -q "$host"; then
+ echo "$host" >>$CASES_DIR/${agent}_hosts
+ hosts=$hosts$'\n'$host
+ cat >>$sh <<EOF
+# Initialize remote shell
+backbash_start $host
+backbash $host <<CMD
+__OCFT__VERBOSE=\$__OCFT__VERBOSE
+CMD
+backbash $host <$OCFT_DIR/caselib
+backbash $host <<'CMD'
+$(test -r $CASES_DIR/${agent}_var && cat $CASES_DIR/${agent}_var)
+
+__OCFT__MYROOT="$cfg_agent_root"
+__OCFT__showhost="${host}: "
+
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+CMD
+EOF
+ fi
+ fi
+
+ echo "
+# CASE statement: $line" >>$sh
+
+ if [ -n "$host" ]; then
+ echo "backbash $host <<'CMD'" >>$sh
+ fi
+
+ case "${trunk[0]}" in
+ Env|Env@*)
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Setting agent environment: export ${trunk[1]}")
+fi
+export ${trunk[1]}
+check_success \$? $(add_quotes 1 "export ${trunk[1]}")
+EOF
+ ;;
+ Unenv|Unenv@*)
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Removing agent environment: unset ${trunk[1]}")
+fi
+unset ${trunk[1]}
+check_success \$? $(add_quotes 1 "unset ${trunk[1]}")
+EOF
+ ;;
+ AgentRun|AgentRun@*)
+ trunk2branch
+ if [ -z "${branch[1]}" ]; then
+ if [ "${branch[0]}" = "start" ]; then
+ cat >>$sh <<EOF
+agent_run $(add_quotes 1 "$cfg_agent") monitor $cfg_hang_timeout
+__OCFT__rc=\$?
+
+if [ \$__OCFT__rc -eq \$OCF_SUCCESS -o \$__OCFT__rc -eq \$OCF_RUNNING_MASTER ]; then
+ : #The status I want, so I can do nothing.
+elif [ \$__OCFT__rc -eq \$OCF_NOT_RUNNING ]; then
+ if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}")
+ fi
+ agent_run $(add_quotes 1 "$cfg_agent") start $cfg_hang_timeout
+ check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}")
+else
+ check_success \$__OCFT__rc $(add_quotes 1 "./$cfg_agent monitor")
+fi
+EOF
+ elif [ "${branch[0]}" = "stop" ]; then
+ cat >>$sh <<EOF
+agent_run $(add_quotes 1 "$cfg_agent") monitor $cfg_hang_timeout
+__OCFT__rc=\$?
+
+if [ \$__OCFT__rc -eq \$OCF_NOT_RUNNING ]; then
+ : #The status I want, so I can do nothing.
+elif [ \$__OCFT__rc -eq \$OCF_SUCCESS -o \$__OCFT__rc -eq \$OCF_RUNNING_MASTER ]; then
+ if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}")
+ fi
+ agent_run $(add_quotes 1 "$cfg_agent") stop $cfg_hang_timeout
+ check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}")
+else
+ check_success \$__OCFT__rc $(add_quotes 1 "./$cfg_agent monitor")
+fi
+EOF
+ elif [ "${branch[0]}" = "monitor" ]; then
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}")
+fi
+agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout
+EOF
+ else
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}")
+fi
+agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout
+check_success \$? $(add_quotes 1 "./$cfg_agent ${branch[0]}")
+EOF
+ fi
+ else
+ cat >>$sh <<EOF
+test -n $(add_quotes 2 "\$${branch[1]}")
+check_success \$? $(add_quotes 1 "test -n \"\$${branch[1]}\"")
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Running agent: ./$cfg_agent ${branch[0]}")
+fi
+agent_run $(add_quotes 1 "$cfg_agent") $(add_quotes 1 "${branch[0]}") $cfg_hang_timeout
+__OCFT__ret=\$?
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo -n " \${__OCFT__showhost}Checking return value:"
+fi
+if [ -n "\${__OCFT__retval[__OCFT__ret]}" ]; then
+ __OCFT__retstr="\${__OCFT__retval[__OCFT__ret]}"
+else
+ __OCFT__retstr=\$__OCFT__ret
+fi
+if [ \$__OCFT__ret -eq \$${branch[1]} ]; then
+ if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo -e $(add_quotes 2 " \\033[32mOK\\033[0m. The return value '\\033[34m\$__OCFT__retstr\\033[0m' == '\\033[34m${branch[1]}\\033[0m'")
+ else
+ echo -e "\\033[32mOK\\033[0m."
+ fi
+else
+ if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo -en $(add_quotes 2 " \\033[31mFAILED\\033[0m. The return value '\\033[34m\$__OCFT__retstr\\033[0m' != '\\033[34m${branch[1]}\\033[0m'. ")
+ else
+ echo -en "\\033[31mFAILED\\033[0m. Agent returns unexpected value: '\$__OCFT__retstr'. "
+ fi
+ echo "See details below:"
+ cat /tmp/.ocft_runlog
+ echo
+ quit 1
+fi
+EOF
+ fi
+ ;;
+ Bash|Bash@*)
+ cat >>$sh <<EOF
+if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Setting system environment: ${trunk[1]}")
+fi
+${trunk[1]}
+check_success \$? $(add_quotes 1 "${trunk[1]}")
+EOF
+ ;;
+ BashAtExit|BashAtExit@*)
+ let atexit_num++
+ cat >>$sh <<EOF
+atexit${atexit_num}()
+{
+ if [ -n "\$__OCFT__VERBOSE" ]; then
+ echo $(add_quotes 2 " \${__OCFT__showhost}Setting system environment: ${trunk[1]}")
+ fi
+ ${trunk[1]}
+}
+let __OCFT__atexit_num++
+EOF
+ ;;
+ *)
+ parse_die "unimplemented statement: ${trunk[0]}"
+ ;;
+ esac
+ if [ -n "$host" ]; then
+ echo 'CMD' >>$sh
+ fi
+ ;;
+ *)
+ parse_die "unimplemented statement: ${trunk[0]}"
+ ;;
+ esac
+ done <$CASES_DIR/${agent}_preparse
+
+ if [ -r "$CASES_DIR/${agent}_setup" ]; then
+ cat >$CASES_DIR/setup_${agent}.sh <<EOF
+#!@BASH_SHELL@
+
+# Agent: $cfg_agent
+# Summary: SETUP before test
+
+echo "Initializing '$cfg_agent' ..."
+
+. $OCFT_DIR/caselib || {
+ echo "ERROR: '$OCFT_DIR/caselib' not found."
+ exit 2
+}
+
+$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var)
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+
+EOF
+ for host in $(test -r $CASES_DIR/${agent}_hosts && cat $CASES_DIR/${agent}_hosts); do
+ cat >>$CASES_DIR/setup_${agent}.sh <<EOF
+# Initialize remote shell
+backbash_start $host
+backbash $host <<CMD
+__OCFT__VERBOSE=\$__OCFT__VERBOSE
+CMD
+backbash $host <$OCFT_DIR/caselib
+backbash $host <<'CMD'
+$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var)
+
+__OCFT__MYROOT="$cfg_agent_root"
+__OCFT__showhost="${host}: "
+
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+
+$(cat $CASES_DIR/${agent}_setup)
+check_success \$? "SETUP-AGENT"
+CMD
+backbash_stop $host
+EOF
+ done
+ cat >>$CASES_DIR/setup_${agent}.sh <<EOF
+$(cat $CASES_DIR/${agent}_setup)
+check_success \$? "SETUP-AGENT"
+
+echo "Done."
+echo
+quit 0
+EOF
+ chmod a+x $CASES_DIR/setup_${agent}.sh
+ fi
+
+ if [ -r "$CASES_DIR/${agent}_cleanup" ]; then
+ cat >$CASES_DIR/cleanup_${agent}.sh <<EOF
+#!@BASH_SHELL@
+
+# Agent: $cfg_agent
+# Summary: CLEANUP after test
+
+echo "Cleaning '$cfg_agent' ..."
+
+. $OCFT_DIR/caselib || {
+ echo "ERROR: '$OCFT_DIR/caselib' not found."
+ exit 2
+}
+
+$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var)
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+
+$(cat $CASES_DIR/${agent}_cleanup)
+check_success \$? "CLEANUP-AGENT"
+
+EOF
+ for host in $(test -r $CASES_DIR/${agent}_hosts && cat $CASES_DIR/${agent}_hosts); do
+ cat >>$CASES_DIR/cleanup_${agent}.sh <<EOF
+# Initialize remote shell
+backbash_start $host
+backbash $host <<CMD
+__OCFT__VERBOSE=\$__OCFT__VERBOSE
+CMD
+backbash $host <$OCFT_DIR/caselib
+backbash $host <<'CMD'
+$(test -r "$CASES_DIR/${agent}_var" && cat $CASES_DIR/${agent}_var)
+
+__OCFT__MYROOT="$cfg_agent_root"
+__OCFT__showhost="${host}: "
+
+$(test -n "$cfg_install_package" && echo "agent_install ${cfg_install_package[@]}")
+
+$(cat $CASES_DIR/${agent}_cleanup)
+check_success \$? "CLEANUP-AGENT"
+CMD
+backbash_stop $host
+EOF
+ done
+ cat >>$CASES_DIR/cleanup_${agent}.sh <<EOF
+echo "Done."
+echo
+quit 0
+EOF
+ chmod a+x $CASES_DIR/cleanup_${agent}.sh
+ fi
+ case_parse_finish
+ agent_parse_finish
+
+ done
+}
+
+start_test()
+{
+ local sh shs testsh agents line ret
+ local rc=0
+ local varlib
+ local rc_f
+
+ if ! cd $CASES_DIR >/dev/null 2>&1; then
+ die "cases directory not found."
+ fi
+
+ if [ ! -d logs ]; then
+ mkdir logs
+ fi
+
+ export __OCFT__VERBOSE=$opt_verbose
+
+ if [ $# -eq 0 ]; then
+ agents=($(ls -1 *.sh 2>/dev/null | sed 's/.*_\([^_]*\)\.sh$/\1/' | sort | uniq))
+ else
+ agents=("$@")
+ fi
+
+ for shs in "${agents[@]}"; do
+ if [ -z "$opt_incremental" ]; then
+ testsh="setup_${shs}.sh
+ $(ls -1 [0-9]*_${shs}.sh 2>/dev/null | sort -n)
+ cleanup_${shs}.sh"
+ else
+ testsh="setup_${shs}.sh
+ $(ls -1 [0-9]*_${shs}.retest 2>/dev/null | sed 's/retest$/sh/' | sort -n)
+ cleanup_${shs}.sh"
+ fi
+
+ if [ -n "$opt_trace_ra" ]; then
+ varlib=${HA_VARLIB:="/var/lib/heartbeat"}
+ export OCF_RESKEY_trace_ra=1
+ echo "RA trace on, output in $varlib/trace_ra"
+ fi
+
+ rc_f=`mktemp`
+ (for sh in $testsh; do
+ if [ -r "$sh" ]; then
+ if [ -n "$opt_trace_ra" ]; then
+ export OCF_RESOURCE_INSTANCE="`echo $sh | sed 's/_.*//'`"
+ fi
+ ./$sh
+ ret=$?
+
+ case "$sh" in
+ setup*)
+ rc=$((rc|ret))
+ if [ $ret -ne 0 ]; then
+ warn "SETUP failed, break all tests of '$shs'."
+ break
+ fi
+ ;;
+ cleanup*)
+ if [ $ret -ne 0 ]; then
+ warn "CLEANUP failed."
+ fi
+ ;;
+ [0-9]*)
+ case $ret in
+ 3) die "core function failed, break all tests." ;;
+ 2) warn "core function failed, break all tests of '$shs'."; break ;;
+ 1) touch ${sh%.*}.retest ;;
+ 0) rm -f ${sh%.*}.retest ;;
+ esac
+ rc=$((rc|ret))
+ ;;
+ esac
+ fi
+ done 2>&1; echo $rc > $rc_f) | while read -r line; do
+ echo "$line"
+ echo "$(date '+%F %T'): $line" | cat -A |
+ sed -r 's/\^\[\[[0-9]+m|\^I|.$//g' >>logs/$shs.log
+ done
+ done
+ rc=`cat $rc_f`
+ rm -f $rc_f
+ return $rc
+}
+
+agent_clean()
+{
+ local typ ra
+ typ=$1
+
+ shift
+
+ if [ $# -eq 0 ]; then
+ rm -f $CASES_DIR/*.$typ
+ else
+ for ra in "$@"; do
+ rm -f $CASES_DIR/*_${ra}.$typ
+ done
+ fi
+}
+
+agent_retest_clean()
+{
+ agent_clean retest "$@"
+}
+
+agent_obj_clean()
+{
+ agent_clean sh "$@"
+}
+
+usage()
+{
+ cat <<EOF
+$0 ACTION [OPTION] [agent1 [agent2] [...]]
+ACTIONs include:
+ make [-d dir] Generate the testing shell scripts.
+ -d The directory that contains
+ configuration of cases.
+ test [-v|-i|-X] Execute the testing shell scripts.
+ -v Verbose output mode.
+ -i Incremental mode, skip case
+ which succeeded. If cleaning
+ the status of incremental mode
+ is needed, try to '$0 clean RA_NAME'.
+ -X Trace the RA
+ clean Delete the testing shell scripts.
+ help [-v] Show this help and exit.
+ -v Show HOWTO and exit.
+Version 0.44
+See '$OCFT_DIR/README' for detail.
+EOF
+}
+
+howto()
+{
+ cat <<EOF
+HOW TO USE THIS TOOL
+
+ - Ocft is a testing tool for resource agents. Instead of the policy of HA,
+ it mainly concerns whether resource agents run correct locally. It can
+ design types of complicated environments to test the reliability of
+ resource agents. Precisely, it is to display whether resource agents can
+ return to correct or expected value. The advantage of the tool provides
+ us with competence to design conditions which can be recorded or reproduced.
+ Hence it is useful to debuggers.
+
+* Components
+ ** Test case generator (/usr/sbin/ocft)
+ - Turning configuration files of test case to executable scripts.
+
+ ** Configuration file ($CONFIGS_DIR/)
+ - Every configuration file directs only one resource agent and share the same
+ name with resource agent but contains more test cases.
+
+ ** The testing script ($CASES_DIR/)
+ - After the generator reads configuration files and generates many testing
+ scripts and the script is underway, the test begins.
+
+* How to customize the environment of testing
+ - Ocft designs the running conditions through two ways, one is changing the
+ environment variables of resource agents (it is the interface left by OCF itself),
+ the other is modifying the OS environment of resource agents, such as altering
+ the permission of some key file or IP address of the machine.
+
+* How to test
+ - Firstly, you need to sketch the all complex and uncommon environments against
+ a certain resource agent and keep in mind what consequences may be caused by
+ these uncommon environments.
+ Secondly, write the designed conditions and foreknown consequences into
+ configuration files, and then run the generator to translate the test case to
+ executable scripts.
+ Finally, you need running these scripts to observe the output and learn
+ the running status of each test case, which will compares the predicated result
+ with the actual one. If they differ, you will be able to find the bugs of the
+ resource agent.
+ - All of the output with test will be recorded into the log files, you can find them
+ in $CASES_DIR/logs.
+EOF
+}
+
+
+export LANG=C
+
+# system variable
+OCFT_DIR=@datadir@/@PACKAGE_NAME@/ocft
+CONFIGS_DIR=@datadir@/@PACKAGE_NAME@/ocft/configs
+CASES_DIR=/var/lib/@PACKAGE_NAME@/ocft/cases
+
+# global variable
+agent=
+line_num=
+
+# default configuration
+init_cfg_vars
+
+# default option
+opt_verbose=
+opt_incremental=
+opt_cfgsdir=$CONFIGS_DIR
+
+command="$1"
+shift
+
+case "$command" in
+ make)
+ if [ "$1" = "-d" ]; then
+ if [ ! -d "$2" ]; then
+ usage
+ exit 1
+ fi
+ opt_cfgsdir="$2"
+ shift 2
+ fi
+ if [ ! -d "$CASES_DIR" ]; then
+ mkdir -p "$CASES_DIR" || die "Can not create directory: ${CASES_DIR}."
+ fi
+ parse_cfg "$@"
+ ;;
+ test)
+ for v in 1 2 3; do
+ case "$1" in
+ -v)
+ opt_verbose=1
+ shift
+ ;;
+ -X)
+ opt_trace_ra=1
+ shift
+ ;;
+ -i)
+ opt_incremental=1
+ shift
+ ;;
+ -*)
+ die "bad option $1"
+ ;;
+ esac
+ done
+ start_test "$@"
+ ;;
+ clean)
+ agent_obj_clean "$@"
+ agent_retest_clean "$@"
+ ;;
+ help)
+ if [ "$1" = "-v" ]; then
+ howto
+ else
+ usage
+ fi
+ exit 0
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+esac
+
+# vim:ts=2:sw=2:et:
diff --git a/tools/ocft/oracle b/tools/ocft/oracle
new file mode 100644
index 0000000..6f145c7
--- /dev/null
+++ b/tools/ocft/oracle
@@ -0,0 +1,81 @@
+# oracle
+# (based on db2)
+#
+# Created on an SLE11SP2 running oracle 11g
+# database sid is orcl
+# adapt this in set_testenv below
+# TODO: need oracle expert to break it, then test it
+#
+
+CONFIG
+ Agent oracle
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 40
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK set_testenv
+ Env OCFT_sid=orcl
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_sid=$OCFT_sid
+ Env OCF_RESKEY_CRM_meta_timeout=30000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include set_testenv
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: no 'OCF_RESKEY_sid'"
+ Include prepare
+ Env OCF_RESKEY_sid=
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: invalid 'OCF_RESKEY_home'"
+ Include prepare
+ Env OCF_RESKEY_home=/no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "started: monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not started: monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "try different ipcrm method"
+ Include prepare
+ Env OCF_RESKEY_ipcrm=none
+ AgentRun start OCF_SUCCESS
diff --git a/tools/ocft/pgsql b/tools/ocft/pgsql
new file mode 100644
index 0000000..9944b09
--- /dev/null
+++ b/tools/ocft/pgsql
@@ -0,0 +1,71 @@
+# pgsql
+
+CONFIG
+ Agent pgsql
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage postgresql-server
+ HangTimeout 20
+
+SETUP-AGENT
+ /etc/init.d/postgresql start
+ /etc/init.d/postgresql stop
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_CRM_meta_timeout=15000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_pgctl'"
+ Include prepare
+ Env OCF_RESKEY_pgctl=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "running monitor"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "not running monitor"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
+
+CASE "non-existent user"
+ Include prepare
+ Env OCF_RESKEY_pgdba=no_user
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "invalid user"
+ Include prepare
+ Env OCF_RESKEY_pgdba=nobody
+ AgentRun start OCF_ERR_PERM
diff --git a/tools/ocft/portblock b/tools/ocft/portblock
new file mode 100644
index 0000000..3475c63
--- /dev/null
+++ b/tools/ocft/portblock
@@ -0,0 +1,69 @@
+# portblock
+
+CONFIG
+ Agent portblock
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage iptables
+ HangTimeout 15
+
+CASE-BLOCK required_args
+ Env OCF_RESKEY_protocol=tcp
+ Env OCF_RESKEY_portno=80
+ Env OCF_RESKEY_action=block
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_protocol'"
+ Include prepare
+ Unenv OCF_RESKEY_protocol
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: unset 'OCF_RESKEY_portno'"
+ Include prepare
+ Unenv OCF_RESKEY_portno
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "check base env: unset 'OCF_RESKEY_action'"
+ Include prepare
+ Unenv OCF_RESKEY_action
+ AgentRun start OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor with running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor with not running"
+ Include prepare
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/postfix b/tools/ocft/postfix
new file mode 100644
index 0000000..f17e968
--- /dev/null
+++ b/tools/ocft/postfix
@@ -0,0 +1,102 @@
+# postfix
+# by r.bhatia@ipax.at
+#
+# test cases (to implement):
+#
+# * /usr/sbin/ocf-tester -n post1 /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE
+# * /usr/sbin/ocf-tester -n post2 -o binary="/usr/sbin/postfix" \
+# -o config_dir="" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE
+# * /usr/sbin/ocf-tester -n post3 -o binary="/usr/sbin/postfix" \
+# -o config_dir="/etc/postfix" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $? -> DONE
+# * /usr/sbin/ocf-tester -n post4 -o binary="/usr/sbin/postfix" \
+# -o config_dir="/root/postfix/" /usr/lib/ocf/resource.d/heartbeat/postfix; echo $?
+
+CONFIG
+ Agent postfix
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage postfix
+ HangTimeout 20
+
+SETUP-AGENT
+ # nothing
+
+CASE-BLOCK crm_setting
+ Env OCF_RESKEY_CRM_meta_timeout=15000
+ Env OCF_RESKEY_CRM_meta_interval=10000
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include crm_setting
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: invalid 'OCF_RESKEY_binary'"
+ Include prepare
+ Env OCF_RESKEY_binary=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: invalid 'OCF_RESKEY_config_dir'"
+ Include prepare
+ Env OCF_RESKEY_config_dir=no_such
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "check base env: 'OCF_RESKEY_binary'"
+ Include prepare
+ Env OCF_RESKEY_binary=/usr/sbin/postfix
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "check base env: 'OCF_RESKEY_config_dir' without trailing slash"
+ Include prepare
+ Env OCF_RESKEY_config_dir="/etc/postfix"
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "check base env: 'OCF_RESKEY_config_dir' with trailing slash"
+ Include prepare
+ Env OCF_RESKEY_config_dir="/etc/postfix/"
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor a running resource"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "(initial) probe a stopped resource"
+ Include prepare
+ Env OCF_RESKEY_CRM_meta_interval=0
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "(re-)probe a running resource"
+ Include prepare
+ Env OCF_RESKEY_CRM_meta_interval=0
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "unimplemented command"
+ Include prepare
+ AgentRun no_cmd OCF_ERR_UNIMPLEMENTED
diff --git a/tools/ocft/runocft b/tools/ocft/runocft
new file mode 100755
index 0000000..d269a6b
--- /dev/null
+++ b/tools/ocft/runocft
@@ -0,0 +1,38 @@
+#!/bin/sh
+OCFTDIR=/usr/share/resource-agents/ocft
+CONFDIR=$OCFTDIR/configs
+
+prereq_run() {
+ eval "$@"
+}
+prereq_prog() {
+ which $@
+}
+
+test_prereq() {
+ local tp arg
+ tp=`echo $prereq|sed 's/:.*//'`
+ arg=`echo $prereq|sed 's/[a-z]*://'`
+ prereq_$tp $arg >/dev/null 2>&1
+}
+
+rm -f ocft.FAILED
+rc=0
+while read f prereq; do
+ if [ -n "$prereq" ] && ! test_prereq; then
+ echo "$f: prerequisite not fulfilled, skipping"
+ continue
+ fi
+ ocft make $f
+ if ! ocft test $f; then
+ echo $f >> ocft.FAILED
+ rc=1
+ fi
+done < $OCFTDIR/runocft.prereq
+
+if [ -f ocft.FAILED ]; then
+ echo "The following ocft tests failed:"
+ cat ocft.FAILED
+fi
+
+exit $rc
diff --git a/tools/ocft/runocft.prereq b/tools/ocft/runocft.prereq
new file mode 100644
index 0000000..3bc222c
--- /dev/null
+++ b/tools/ocft/runocft.prereq
@@ -0,0 +1,30 @@
+apache
+db2 prog:~db2inst1/sqllib/bin/db2
+drbd.linbit run:false
+exportfs
+exportfs-multidir
+Filesystem
+IPaddr2 run:ip addr show dev eth0
+IPaddr2v4 run:ip addr show dev eth0 && ip addr show dev eth1
+IPaddr2v6 run:ip addr show dev eth0 && ip addr show dev eth1
+IPsrcaddr run:false
+IPv6addr run:ip addr show dev eth0 && ip addr show dev eth1
+iscsi
+jboss run:false
+LVM
+MailTo
+mysql
+mysql-proxy run:false
+named
+nfsserver
+oracle prog:sqlplus
+pgsql
+portblock
+postfix
+Raid1
+SendArp
+sg_persist run:false
+tomcat run:false
+VirtualDomain run:false
+Xen run:false
+Xinetd
diff --git a/tools/ocft/sg_persist b/tools/ocft/sg_persist
new file mode 100644
index 0000000..f11d36d
--- /dev/null
+++ b/tools/ocft/sg_persist
@@ -0,0 +1,225 @@
+# sg_persist
+# Before use sg_persist ocft test case you have to prepare to make pacemaker
+# can be started successfully because sg_persist RA needs to use the result of
+# `crm_node -i` as sg_persist register key.
+
+CONFIG
+ Agent sg_persist
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ InstallPackage sg3_utils
+ HangTimeout 20
+
+VARIABLE
+
+ # Please set the disk you want to test
+ #OCFT_DEVS="/dev/disk/by-id/scsi-14945540000000000844965720e6e555176b19461345d68d5"
+ OCFT_DEVS=
+
+ # please set the IP addrees of the other server.
+ IP_2=
+
+SETUP-AGENT
+ # start pacemaker to get node_id through crm_node -i
+ systemctl start pacemaker
+
+CLEANUP-AGENT
+
+CASE-BLOCK required_args
+ Env HA_SBIN_DIR=""
+ Env OCF_RESKEY_devs="$OCFT_DEVS"
+ Env OCF_RESKEY_reservation_type=1
+ Env OCF_RESKEY_CRM_meta_notify=true
+ Env OCF_RESKEY_CRM_meta_master_max=1
+
+CASE-BLOCK required_args_error
+ Env HA_SBIN_DIR=""
+ Env OCF_RESKEY_devs="$OCFT_DEVS"
+ Env OCF_RESKEY_reservation_type=1
+ Env OCF_RESKEY_CRM_meta_notify=true
+ Env OCF_RESKEY_CRM_meta_master_max=2
+
+CASE-BLOCK required_args_type_5
+ Env HA_SBIN_DIR=""
+ Env OCF_RESKEY_devs="$OCFT_DEVS"
+ Env OCF_RESKEY_reservation_type=5
+ Env OCF_RESKEY_CRM_meta_notify=true
+ Env OCF_RESKEY_CRM_meta_master_max=1
+
+CASE-BLOCK required_args_type_7
+ Env HA_SBIN_DIR=""
+ Env OCF_RESKEY_devs="$OCFT_DEVS"
+ Env OCF_RESKEY_reservation_type=7
+ Env OCF_RESKEY_CRM_meta_notify=true
+ Env OCF_RESKEY_CRM_meta_master_max=2
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare
+ Include required_args
+ Include default_status
+
+CASE-BLOCK prepare_type_5
+ Include required_args_type_5
+ Include default_status
+
+CASE-BLOCK prepare_type_7
+ Include required_args_type_7
+ Include default_status
+
+CASE "check base env"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "check base env: unset 'OCF_RESKEY_sg_persist_devs'"
+ Include prepare
+ Unenv OCF_RESKEY_devs
+ AgentRun start OCF_ERR_INSTALLED
+
+CASE "validate all"
+ Include required_args
+ AgentRun validate-all OCF_SUCCESS
+
+CASE "validate all--type:5"
+ Include required_args_type_5
+ AgentRun validate-all OCF_SUCCESS
+
+CASE "validate all--type:7"
+ Include required_args_type_7
+ AgentRun validate-all OCF_SUCCESS
+
+CASE "validate all--error configured"
+ Include required_args_error
+ AgentRun validate-all OCF_ERR_CONFIGURED
+
+CASE "normal start"
+ Include prepare
+ AgentRun start OCF_SUCCESS
+
+CASE "normal stop"
+ Include prepare
+ AgentRun start
+ AgentRun stop OCF_SUCCESS
+
+CASE "double start"
+ Include prepare
+ AgentRun start
+ AgentRun start OCF_SUCCESS
+
+CASE "double stop"
+ Include prepare
+ AgentRun stop
+ AgentRun stop OCF_SUCCESS
+
+CASE "monitor when running"
+ Include prepare
+ AgentRun start
+ AgentRun monitor OCF_SUCCESS
+
+CASE "monitor when not running"
+ Include prepare
+ AgentRun stop
+ AgentRun monitor OCF_NOT_RUNNING
+
+CASE "Primary/Secondary monitor"
+ Include prepare
+ AgentRun start
+ AgentRun promote
+ AgentRun monitor OCF_RUNNING_MASTER
+ AgentRun demote
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop
+
+CASE "promote/demote test in single-primary mode"
+ Include prepare
+ Include@$IP_2 prepare
+
+ # start
+ AgentRun start
+ AgentRun@$IP_2 start
+
+ # promote local first
+ AgentRun promote OCF_SUCCESS
+
+ # demote local prepare for remote promote
+ AgentRun demote
+
+ # remote promote
+ AgentRun@$IP_2 promote OCF_SUCCESS
+ AgentRun@$IP_2 monitor OCF_RUNNING_MASTER
+ AgentRun monitor OCF_SUCCESS
+
+ # promote local then promote(by preempt) remote, which will cause local OCF_NOT_RUNNING
+ AgentRun promote OCF_SUCCESS
+ AgentRun monitor OCF_RUNNING_MASTER
+ AgentRun@$IP_2 monitor OCF_NOT_RUNNING
+
+ # remove all reservation registration
+ AgentRun demote
+ AgentRun stop
+ AgentRun@$IP_2 stop
+
+
+CASE "normal start--type 5"
+ Include prepare_type_5
+ Include@$IP_2 prepare_type_5
+ AgentRun start OCF_SUCCESS
+ AgentRun@$IP_2 start OCF_SUCCESS
+
+CASE "promote/demote test in single-primary mode"
+ Include prepare_type_5
+ Include@$IP_2 prepare_type_5
+
+ # start
+ AgentRun start
+ AgentRun@$IP_2 start
+
+ # promote local first
+ AgentRun promote OCF_SUCCESS
+ AgentRun monitor OCF_RUNNING_MASTER
+
+ # demote local prepare for remote promote
+ AgentRun demote
+ # remote promote
+ AgentRun@$IP_2 promote OCF_SUCCESS
+ AgentRun@$IP_2 monitor OCF_RUNNING_MASTER
+ AgentRun monitor OCF_SUCCESS
+
+ # promote local then promote(by preempt) remote, which will cause local OCF_NOT_RUNNING
+ AgentRun promote OCF_SUCCESS
+ AgentRun monitor OCF_RUNNING_MASTER
+ AgentRun@$IP_2 monitor OCF_NOT_RUNNING
+
+ # remove all reservation registration
+ AgentRun demote
+ AgentRun stop
+ AgentRun@$IP_2 stop
+
+CASE "normal start--type 7: 2 masters"
+ Include prepare_type_7
+ Include@$IP_2 prepare_type_7
+ AgentRun start OCF_SUCCESS
+ AgentRun@$IP_2 start OCF_SUCCESS
+
+
+CASE "Primary/Primary monitor--type 7"
+ Include prepare_type_7
+ Include@$IP_2 prepare_type_7
+
+ AgentRun start OCF_SUCCESS
+ AgentRun@$IP_2 start OCF_SUCCESS
+
+ AgentRun promote OCF_SUCCESS
+ AgentRun monitor OCF_RUNNING_MASTER
+
+ AgentRun@$IP_2 promote OCF_SUCCESS
+ AgentRun monitor OCF_RUNNING_MASTER
+ AgentRun@$IP_2 monitor OCF_RUNNING_MASTER
+
+ AgentRun demote OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ AgentRun@$IP_2 demote OCF_SUCCESS
+ AgentRun@$IP_2 monitor OCF_NOT_RUNNING
+
+ AgentRun stop
+ AgentRun@$IP_2 stop
diff --git a/tools/ocft/tomcat b/tools/ocft/tomcat
new file mode 100644
index 0000000..56adf86
--- /dev/null
+++ b/tools/ocft/tomcat
@@ -0,0 +1,73 @@
+# tomcat
+#
+# NOTE: Clean up $catalina_home/logs before running this test
+# otherwise creating the pid/log files may fail
+# in the test case with a different user.
+
+CONFIG
+ Agent tomcat
+ AgentRoot /usr/lib/ocf/resource.d/heartbeat
+ HangTimeout 120
+
+VARIABLE
+ # Adjust accrding to your configuration
+ catalina_home=/opt/tomcat7
+ tomcat_user=tomcat7
+ java_home=/usr/lib/jvm/java-1.6.0-openjdk.x86_64
+
+CASE-BLOCK required_args_tomcat
+ Env OCF_RESKEY_catalina_home=${catalina_home}
+ Env OCF_RESKEY_tomcat_user=${tomcat_user}
+ Env OCF_RESKEY_java_home=${java_home}
+
+CASE-BLOCK args_clear
+ Unenv OCF_RESKEY_catalina_home
+ Unenv OCF_RESKEY_tomcat_user
+ Unenv OCF_RESKEY_java_home
+
+CASE-BLOCK default_status
+ AgentRun stop
+
+CASE-BLOCK prepare_tomcat
+ Include required_args_tomcat
+ Include default_status
+
+# Test CASE
+#
+CASE "normal start tomcat require_args (user:user)"
+ Include prepare_tomcat
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
+CASE "normal start tomcat require_args (user:root)"
+ Include prepare_tomcat
+ Unenv OCF_RESKEY_tomcat_user
+ AgentRun start OCF_SUCCESS
+ AgentRun monitor OCF_SUCCESS
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
+CASE "error start tomcat no catalina_home"
+ Include prepare_tomcat
+ Unenv OCF_RESKEY_catalina_home
+ AgentRun start OCF_ERR_INSTALLED
+ Include args_clear
+
+CASE "error start tomcat no java_home"
+ Include prepare_tomcat
+ Unenv OCF_RESKEY_java_home
+ AgentRun start OCF_ERR_INSTALLED
+ Include args_clear
+
+CASE "error start tomcat no java command"
+ Include prepare_tomcat
+ Env OCF_RESKEY_java_home=/var
+ AgentRun start OCF_ERR_INSTALLED
+ AgentRun stop OCF_SUCCESS
+ AgentRun monitor OCF_NOT_RUNNING
+ Include args_clear
+
diff --git a/tools/send_arp.libnet.c b/tools/send_arp.libnet.c
new file mode 100644
index 0000000..d7bca99
--- /dev/null
+++ b/tools/send_arp.libnet.c
@@ -0,0 +1,764 @@
+/*
+ * send_arp
+ *
+ * This program sends out one ARP packet with source/target IP and Ethernet
+ * hardware addresses suuplied by the user. It uses the libnet libary from
+ * Packet Factory (http://www.packetfactory.net/libnet/ ). It has been tested
+ * on Linux, FreeBSD, and on Solaris.
+ *
+ * This inspired by the sample application supplied by Packet Factory.
+
+ * Matt Soffen
+
+ * Copyright (C) 2001 Matt Soffen <matt@soffen.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Needs to be defined before any other includes, otherwise some system
+ * headers do not behave as expected! Major black magic... */
+#undef _GNU_SOURCE /* in case it was defined on the command line */
+#define _GNU_SOURCE
+
+#include <config.h>
+#include <sys/param.h>
+
+#define USE_GNU
+#if defined(ANSI_ONLY) && !defined(inline)
+# define inline /* nothing */
+#endif
+
+#include <limits.h>
+#include <libnet.h>
+#include <libgen.h>
+#include <clplumbing/timers.h>
+#include <clplumbing/cl_signal.h>
+#include <clplumbing/cl_log.h>
+
+#ifdef HAVE_LIBNET_1_0_API
+# define LTYPE struct libnet_link_int
+ static u_char *mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype);
+ static int send_arp(struct libnet_link_int *l, u_char *device, u_char *buf);
+#endif
+#ifdef HAVE_LIBNET_1_1_API
+# define LTYPE libnet_t
+ static libnet_t *mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype);
+ int send_arp(libnet_t* lntag);
+#endif
+
+#define PIDDIR HA_VARRUNDIR "/" PACKAGE
+#define PIDFILE_BASE PIDDIR "/send_arp-"
+
+static char print_usage[]={
+"send_arp: sends out custom ARP packet.\n"
+" usage: send_arp [-i repeatinterval-ms] [-r repeatcount] [-p pidfile] \\\n"
+" device src_ip_addr src_hw_addr broadcast_ip_addr netmask\n"
+"\n"
+" where:\n"
+" repeatinterval-ms: timing, in milliseconds of sending arp packets\n"
+" For each ARP announcement requested, a pair of ARP packets is sent,\n"
+" an ARP request, and an ARP reply. This is because some systems\n"
+" ignore one or the other, and this combination gives the greatest\n"
+" chance of success.\n"
+"\n"
+" Each time an ARP is sent, if another ARP will be sent then\n"
+" the code sleeps for half of repeatinterval-ms.\n"
+"\n"
+" repeatcount: how many pairs of ARP packets to send.\n"
+" See above for why pairs are sent\n"
+"\n"
+" pidfile: pid file to use\n"
+"\n"
+" device: network interface to use\n"
+"\n"
+" src_ip_addr: source ip address\n"
+"\n"
+" src_hw_addr: source hardware address.\n"
+" If \"auto\" then the address of device\n"
+"\n"
+" broadcast_ip_addr: ignored\n"
+"\n"
+" netmask: ignored\n"
+};
+
+static const char * SENDARPNAME = "send_arp";
+
+static void convert_macaddr (u_char *macaddr, u_char enet_src[6]);
+static int get_hw_addr(char *device, u_char mac[6]);
+int write_pid_file(const char *pidfilename);
+int create_pid_directory(const char *piddirectory);
+
+#define AUTO_MAC_ADDR "auto"
+
+
+#ifndef LIBNET_ERRBUF_SIZE
+# define LIBNET_ERRBUF_SIZE 256
+#endif
+
+
+/*
+ * For use logd, should keep identical with the same const variables defined
+ * in heartbeat.h.
+ */
+#define ENV_PREFIX "HA_"
+#define KEY_LOGDAEMON "use_logd"
+
+static void
+byebye(int nsig)
+{
+ (void)nsig;
+ /* Avoid an "error exit" log message if we're killed */
+ exit(0);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int c = -1;
+ char errbuf[LIBNET_ERRBUF_SIZE];
+ char* device;
+ char* ipaddr;
+ char* macaddr;
+ char* broadcast;
+ char* netmask;
+ u_int32_t ip;
+ u_char src_mac[6];
+ int repeatcount = 1;
+ int j;
+ long msinterval = 1000;
+ int flag;
+ char pidfilenamebuf[64];
+ char *pidfilename = NULL;
+ struct sigaction act;
+
+#ifdef HAVE_LIBNET_1_0_API
+ LTYPE* l;
+ u_char *request, *reply;
+#elif defined(HAVE_LIBNET_1_1_API)
+ LTYPE *request, *reply;
+#endif
+
+ memset(&act, 0, sizeof(struct sigaction));
+ act.sa_flags &= ~SA_RESTART; /* redundant - to stress syscalls should fail */
+ act.sa_handler = byebye;
+ if ((sigemptyset(&act.sa_mask) < 0) || (sigaction(SIGTERM, &act, NULL) < 0)) {
+ cl_log(LOG_ERR, "Could not set handler for signal: %s", strerror(errno));
+ return 1;
+ }
+
+ cl_log_set_entity(SENDARPNAME);
+ cl_log_enable_stderr(TRUE);
+ cl_log_set_facility(LOG_USER);
+ cl_inherit_logging_environment(0);
+
+ while ((flag = getopt(argc, argv, "i:r:p:")) != EOF) {
+ switch(flag) {
+
+ case 'i': msinterval= atol(optarg);
+ break;
+
+ case 'r': repeatcount= atoi(optarg);
+ break;
+
+ case 'p': pidfilename= optarg;
+ break;
+
+ default: fprintf(stderr, "%s\n\n", print_usage);
+ return 1;
+ break;
+ }
+ }
+ if (argc-optind != 5) {
+ fprintf(stderr, "%s\n\n", print_usage);
+ return 1;
+ }
+
+ /*
+ * argv[optind+1] DEVICE dc0,eth0:0,hme0:0,
+ * argv[optind+2] IP 192.168.195.186
+ * argv[optind+3] MAC ADDR 00a0cc34a878
+ * argv[optind+4] BROADCAST 192.168.195.186
+ * argv[optind+5] NETMASK ffffffffffff
+ */
+
+ device = argv[optind];
+ ipaddr = argv[optind+1];
+ macaddr = argv[optind+2];
+ broadcast = argv[optind+3];
+ netmask = argv[optind+4];
+
+ if (!pidfilename) {
+ if (snprintf(pidfilenamebuf, sizeof(pidfilenamebuf), "%s%s",
+ PIDFILE_BASE, ipaddr) >=
+ (int)sizeof(pidfilenamebuf)) {
+ cl_log(LOG_INFO, "Pid file truncated");
+ return EXIT_FAILURE;
+ }
+ pidfilename = pidfilenamebuf;
+ }
+
+ if(write_pid_file(pidfilename) < 0) {
+ return EXIT_FAILURE;
+ }
+
+ if (!strcasecmp(macaddr, AUTO_MAC_ADDR)) {
+ if (get_hw_addr(device, src_mac) < 0) {
+ cl_log(LOG_ERR, "Cannot find mac address for %s",
+ device);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ }
+ else {
+ convert_macaddr((unsigned char *)macaddr, src_mac);
+ }
+
+/*
+ * We need to send both a broadcast ARP request as well as the ARP response we
+ * were already sending. All the interesting research work for this fix was
+ * done by Masaki Hasegawa <masaki-h@pp.iij4u.or.jp> and his colleagues.
+ */
+
+#if defined(HAVE_LIBNET_1_0_API)
+#ifdef ON_DARWIN
+ if ((ip = libnet_name_resolve((unsigned char*)ipaddr, 1)) == -1UL) {
+#else
+ if ((ip = libnet_name_resolve(ipaddr, 1)) == -1UL) {
+#endif
+ cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+
+ l = libnet_open_link_interface(device, errbuf);
+ if (!l) {
+ cl_log(LOG_ERR, "libnet_open_link_interface on %s: %s"
+ , device, errbuf);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ request = mk_packet(ip, (unsigned char*)device, src_mac
+ , (unsigned char*)broadcast, (unsigned char*)netmask
+ , ARPOP_REQUEST);
+ reply = mk_packet(ip, (unsigned char*)device, src_mac
+ , (unsigned char *)broadcast
+ , (unsigned char *)netmask, ARPOP_REPLY);
+ if (!request || !reply) {
+ cl_log(LOG_ERR, "could not create packets");
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ for (j=0; j < repeatcount; ++j) {
+ c = send_arp(l, (unsigned char*)device, request);
+ if (c < 0) {
+ break;
+ }
+ mssleep(msinterval / 2);
+ c = send_arp(l, (unsigned char*)device, reply);
+ if (c < 0) {
+ break;
+ }
+ if (j != repeatcount-1) {
+ mssleep(msinterval / 2);
+ }
+ }
+#elif defined(HAVE_LIBNET_1_1_API)
+ if ((request=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) {
+ cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ if ((reply=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) {
+ cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ if ((signed)(ip = libnet_name2addr4(request, ipaddr, 1)) == -1) {
+ cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr);
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ request = mk_packet(request, ip, (unsigned char*)device, src_mac
+ , (unsigned char*)broadcast, (unsigned char*)netmask
+ , ARPOP_REQUEST);
+ reply = mk_packet(reply, ip, (unsigned char*)device, src_mac
+ , (unsigned char *)broadcast
+ , (unsigned char *)netmask, ARPOP_REPLY);
+ if (!request || !reply) {
+ cl_log(LOG_ERR, "could not create packets");
+ unlink(pidfilename);
+ return EXIT_FAILURE;
+ }
+ for (j=0; j < repeatcount; ++j) {
+ c = send_arp(request);
+ if (c < 0) {
+ break;
+ }
+ mssleep(msinterval / 2);
+ c = send_arp(reply);
+ if (c < 0) {
+ break;
+ }
+ if (j != repeatcount-1) {
+ mssleep(msinterval / 2);
+ }
+ }
+#else
+# error "Must have LIBNET API version defined."
+#endif
+
+ unlink(pidfilename);
+ return c < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+
+void
+convert_macaddr (u_char *macaddr, u_char enet_src[6])
+{
+ int i, pos;
+ u_char bits[3];
+
+ pos = 0;
+ for (i = 0; i < 6; i++) {
+ /* Inserted to allow old-style MAC addresses */
+ if (*macaddr == ':') {
+ pos++;
+ }
+ bits[0] = macaddr[pos++];
+ bits[1] = macaddr[pos++];
+ bits[2] = '\0';
+
+ enet_src[i] = strtol((const char *)bits, (char **)NULL, 16);
+ }
+
+}
+
+#ifdef HAVE_LIBNET_1_0_API
+int
+get_hw_addr(char *device, u_char mac[6])
+{
+ struct ether_addr *mac_address;
+ struct libnet_link_int *network;
+ char err_buf[LIBNET_ERRBUF_SIZE];
+
+ network = libnet_open_link_interface(device, err_buf);
+ if (!network) {
+ fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf);
+ return -1;
+ }
+
+ mac_address = libnet_get_hwaddr(network, device, err_buf);
+ if (!mac_address) {
+ fprintf(stderr, "libnet_get_hwaddr: %s\n", err_buf);
+ return -1;
+ }
+
+ memcpy(mac, mac_address->ether_addr_octet, 6);
+
+ return 0;
+}
+#endif
+
+#ifdef HAVE_LIBNET_1_1_API
+int
+get_hw_addr(char *device, u_char mac[6])
+{
+ struct libnet_ether_addr *mac_address;
+ libnet_t *ln;
+ char err_buf[LIBNET_ERRBUF_SIZE];
+
+ ln = libnet_init(LIBNET_LINK, device, err_buf);
+ if (!ln) {
+ fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf);
+ return -1;
+ }
+
+ mac_address = libnet_get_hwaddr(ln);
+ if (!mac_address) {
+ fprintf(stderr, "libnet_get_hwaddr: %s\n", err_buf);
+ return -1;
+ }
+
+ memcpy(mac, mac_address->ether_addr_octet, 6);
+
+ return 0;
+}
+#endif
+
+
+/*
+ * Notes on send_arp() behaviour. Horms, 15th June 2004
+ *
+ * 1. Target Hardware Address
+ * (In the ARP portion of the packet)
+ *
+ * a) ARP Reply
+ *
+ * Set to the MAC address we want associated with the VIP,
+ * as per RFC2002 (4.6).
+ *
+ * Previously set to ff:ff:ff:ff:ff:ff
+ *
+ * b) ARP Request
+ *
+ * Set to 00:00:00:00:00:00. According to RFC2002 (4.6)
+ * this value is not used in an ARP request, so the value should
+ * not matter. However, I observed that typically (always?) this value
+ * is set to 00:00:00:00:00:00. It seems harmless enough to follow
+ * this trend.
+ *
+ * Previously set to ff:ff:ff:ff:ff:ff
+ *
+ * 2. Source Hardware Address
+ * (Ethernet Header, not in the ARP portion of the packet)
+ *
+ * Set to the MAC address of the interface that the packet is being
+ * sent to. Actually, due to the way that send_arp is called this would
+ * usually (always?) be the case anyway. Although this value should not
+ * really matter, it seems sensible to set the source address to where
+ * the packet is really coming from. The other obvious choice would be
+ * the MAC address that is being associated for the VIP. Which was the
+ * previous values. Again, these are typically the same thing.
+ *
+ * Previously set to MAC address being associated with the VIP
+ */
+
+#ifdef HAVE_LIBNET_1_0_API
+u_char *
+mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype)
+{
+ u_char *buf;
+ u_char *target_mac;
+ u_char device_mac[6];
+ u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+
+ if (libnet_init_packet(LIBNET_ARP_H + LIBNET_ETH_H, &buf) == -1) {
+ cl_log(LOG_ERR, "libnet_init_packet memory:");
+ return NULL;
+ }
+
+ /* Convert ASCII Mac Address to 6 Hex Digits. */
+
+ /* Ethernet header */
+ if (get_hw_addr((char*)device, device_mac) < 0) {
+ cl_log(LOG_ERR, "Cannot find mac address for %s",
+ device);
+ return NULL;
+ }
+
+ if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0
+ , buf) == -1) {
+ cl_log(LOG_ERR, "libnet_build_ethernet failed:");
+ libnet_destroy_packet(&buf);
+ return NULL;
+ }
+
+ if (arptype == ARPOP_REQUEST) {
+ target_mac = zero_mac;
+ }
+ else if (arptype == ARPOP_REPLY) {
+ target_mac = macaddr;
+ }
+ else {
+ cl_log(LOG_ERR, "unknown arptype");
+ return NULL;
+ }
+
+ /*
+ * ARP header
+ */
+ if (libnet_build_arp(ARPHRD_ETHER, /* Hardware address type */
+ ETHERTYPE_IP, /* Protocol address type */
+ 6, /* Hardware address length */
+ 4, /* Protocol address length */
+ arptype, /* ARP operation */
+ macaddr, /* Source hardware addr */
+ (u_char *)&ip, /* Target hardware addr */
+ target_mac, /* Destination hw addr */
+ (u_char *)&ip, /* Target protocol address */
+ NULL, /* Payload */
+ 0, /* Payload length */
+ buf + LIBNET_ETH_H) == -1) {
+ cl_log(LOG_ERR, "libnet_build_arp failed:");
+ libnet_destroy_packet(&buf);
+ return NULL;
+ }
+ return buf;
+}
+#endif /* HAVE_LIBNET_1_0_API */
+
+
+
+
+#ifdef HAVE_LIBNET_1_1_API
+libnet_t*
+mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype)
+{
+ u_char *target_mac;
+ u_char device_mac[6];
+ u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ if (arptype == ARPOP_REQUEST) {
+ target_mac = zero_mac;
+ }
+ else if (arptype == ARPOP_REPLY) {
+ target_mac = macaddr;
+ }
+ else {
+ cl_log(LOG_ERR, "unkonwn arptype:");
+ return NULL;
+ }
+
+ /*
+ * ARP header
+ */
+ if (libnet_build_arp(ARPHRD_ETHER, /* hardware address type */
+ ETHERTYPE_IP, /* protocol address type */
+ 6, /* Hardware address length */
+ 4, /* protocol address length */
+ arptype, /* ARP operation type */
+ macaddr, /* sender Hardware address */
+ (u_int8_t *)&ip, /* sender protocol address */
+ target_mac, /* target hardware address */
+ (u_int8_t *)&ip, /* target protocol address */
+ NULL, /* Payload */
+ 0, /* Length of payload */
+ lntag, /* libnet context pointer */
+ 0 /* packet id */
+ ) == -1 ) {
+ cl_log(LOG_ERR, "libnet_build_arp failed:");
+ return NULL;
+ }
+
+ /* Ethernet header */
+ if (get_hw_addr((char *)device, device_mac) < 0) {
+ cl_log(LOG_ERR, "Cannot find mac address for %s",
+ device);
+ return NULL;
+ }
+
+ if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0
+ , lntag, 0) == -1 ) {
+ cl_log(LOG_ERR, "libnet_build_ethernet failed:");
+ return NULL;
+ }
+ return lntag;
+}
+#endif /* HAVE_LIBNET_1_1_API */
+
+#ifdef HAVE_LIBNET_1_0_API
+int
+send_arp(struct libnet_link_int *l, u_char *device, u_char *buf)
+{
+ int n;
+
+ n = libnet_write_link_layer(l, (char*)device, buf, LIBNET_ARP_H + LIBNET_ETH_H);
+ if (n == -1) {
+ cl_log(LOG_ERR, "libnet_write_link_layer failed");
+ }
+ return (n);
+}
+#endif /* HAVE_LIBNET_1_0_API */
+
+#ifdef HAVE_LIBNET_1_1_API
+int
+send_arp(libnet_t* lntag)
+{
+ int n;
+
+ n = libnet_write(lntag);
+ if (n == -1) {
+ cl_log(LOG_ERR, "libnet_write failed");
+ }
+ return (n);
+}
+#endif /* HAVE_LIBNET_1_1_API */
+
+
+int
+create_pid_directory(const char *pidfilename)
+{
+ int status;
+ struct stat stat_buf;
+ char *pidfilename_cpy;
+ char *dir;
+
+ pidfilename_cpy = strdup(pidfilename);
+ if (!pidfilename_cpy) {
+ cl_log(LOG_INFO, "Memory allocation failure: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ dir = dirname(pidfilename_cpy);
+
+ status = stat(dir, &stat_buf);
+
+ if (status < 0 && errno != ENOENT && errno != ENOTDIR) {
+ cl_log(LOG_INFO, "Could not stat pid-file directory "
+ "[%s]: %s", dir, strerror(errno));
+ free(pidfilename_cpy);
+ return -1;
+ }
+
+ if (status >= 0) {
+ if (S_ISDIR(stat_buf.st_mode)) {
+ return 0;
+ }
+ cl_log(LOG_INFO, "Pid-File directory exists but is "
+ "not a directory [%s]", dir);
+ free(pidfilename_cpy);
+ return -1;
+ }
+
+ if (mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP) < 0) {
+ /* Did someone else make it while we were trying ? */
+ if (errno == EEXIST && stat(dir, &stat_buf) >= 0
+ && S_ISDIR(stat_buf.st_mode)) {
+ return 0;
+ }
+ cl_log(LOG_INFO, "Could not create pid-file directory "
+ "[%s]: %s", dir, strerror(errno));
+ free(pidfilename_cpy);
+ return -1;
+ }
+
+ free(pidfilename_cpy);
+ return 0;
+}
+
+
+int
+write_pid_file(const char *pidfilename)
+{
+
+ int pidfilefd;
+ char pidbuf[11];
+ unsigned long pid;
+ ssize_t bytes;
+
+ if (*pidfilename != '/') {
+ cl_log(LOG_INFO, "Invalid pid-file name, must begin with a "
+ "'/' [%s]\n", pidfilename);
+ return -1;
+ }
+
+ if (create_pid_directory(pidfilename) < 0) {
+ return -1;
+ }
+
+ while (1) {
+ pidfilefd = open(pidfilename, O_CREAT|O_EXCL|O_RDWR,
+ S_IRUSR|S_IWUSR);
+ if (pidfilefd < 0) {
+ if (errno != EEXIST) { /* Old PID file */
+ cl_log(LOG_INFO, "Could not open pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+ }
+ else {
+ break;
+ }
+
+ pidfilefd = open(pidfilename, O_RDONLY, S_IRUSR|S_IWUSR);
+ if (pidfilefd < 0) {
+ cl_log(LOG_INFO, "Could not open pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+
+ while (1) {
+ bytes = read(pidfilefd, pidbuf, sizeof(pidbuf)-1);
+ if (bytes < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ cl_log(LOG_INFO, "Could not read pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+ pidbuf[bytes] = '\0';
+ break;
+ }
+
+ if(unlink(pidfilename) < 0) {
+ cl_log(LOG_INFO, "Could not delete pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+
+ if (!bytes) {
+ cl_log(LOG_INFO, "Invalid pid in pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+
+ close(pidfilefd);
+
+ pid = strtoul(pidbuf, NULL, 10);
+ if (pid == ULONG_MAX && errno == ERANGE) {
+ cl_log(LOG_INFO, "Invalid pid in pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+
+ if (kill(pid, SIGKILL) < 0 && errno != ESRCH) {
+ cl_log(LOG_INFO, "Error killing old process [%lu] "
+ "from pid-file [%s]: %s", pid,
+ pidfilename, strerror(errno));
+ return -1;
+ }
+
+ cl_log(LOG_INFO, "Killed old send_arp process [%lu]\n",
+ pid);
+ }
+
+ if (snprintf(pidbuf, sizeof(pidbuf), "%u"
+ , getpid()) >= (int)sizeof(pidbuf)) {
+ cl_log(LOG_INFO, "Pid too long for buffer [%u]", getpid());
+ return -1;
+ }
+
+ while (1) {
+ bytes = write(pidfilefd, pidbuf, strlen(pidbuf));
+ if (bytes != (ssize_t)strlen(pidbuf)) {
+ if (bytes < 0 && errno == EINTR) {
+ continue;
+ }
+ cl_log(LOG_INFO, "Could not write pid-file "
+ "[%s]: %s", pidfilename,
+ strerror(errno));
+ return -1;
+ }
+ break;
+ }
+
+ close(pidfilefd);
+
+ return 0;
+}
+
+
diff --git a/tools/send_arp.linux.c b/tools/send_arp.linux.c
new file mode 100644
index 0000000..2aa9b5d
--- /dev/null
+++ b/tools/send_arp.linux.c
@@ -0,0 +1,1336 @@
+/*
+ * arping.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+ */
+
+/* Andrew Beekhof, Lars Ellenberg:
+ * Based on arping from iputils,
+ * adapted to the command line conventions established by the libnet based
+ * send_arp tool as used by the IPaddr and IPaddr2 resource agents.
+ * The libnet based send_arp, and its command line argument convention,
+ * was first added to the heartbeat project by Matt Soffen.
+ *
+ * Latest "resync" with iputils as of:
+ * git://git.linux-ipv6.org/gitroot/iputils.git
+ * 511f8356e22615479c3cc16bca64d72d204f6df3
+ * Fri Jul 24 10:48:47 2015
+ * To get various bugfixes and support for infiniband and other link layer
+ * addresses which do not fit into plain "sockaddr_ll", and broadcast addresses
+ * that may be different from memset(,0xff,).
+ */
+
+#include <stdlib.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <linux/sockios.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <net/if_arp.h>
+#include <sys/uio.h>
+#ifdef CAPABILITIES
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
+
+#include <netdb.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifdef USE_SYSFS
+#include <sysfs/libsysfs.h>
+struct sysfs_devattr_values;
+#endif
+
+#ifndef WITHOUT_IFADDRS
+#include <ifaddrs.h>
+#endif
+
+#ifdef USE_IDN
+#include <idna.h>
+#include <locale.h>
+#endif
+
+static char SNAPSHOT[] = "s20121221";
+
+static void usage(void) __attribute__((noreturn));
+
+#ifndef DEFAULT_DEVICE
+#define DEFAULT_DEVICE "eth0"
+#endif
+#ifdef DEFAULT_DEVICE
+# define DEFAULT_DEVICE_STR DEFAULT_DEVICE
+#else
+# define DEFAULT_DEVICE NULL
+#endif
+
+struct device {
+ const char *name;
+ int ifindex;
+#ifndef WITHOUT_IFADDRS
+ struct ifaddrs *ifa;
+#endif
+#ifdef USE_SYSFS
+ struct sysfs_devattr_values *sysfs;
+#endif
+};
+
+int quit_on_reply=0;
+struct device device = {
+ .name = DEFAULT_DEVICE,
+};
+char *source;
+struct in_addr src, dst;
+char *target;
+int dad, unsolicited, advert;
+int quiet;
+int count=-1;
+int timeout;
+int unicasting;
+int s;
+int broadcast_only;
+
+struct sockaddr_storage me;
+struct sockaddr_storage he;
+
+struct timeval start, last;
+
+int sent, brd_sent;
+int received, brd_recv, req_recv;
+
+#ifndef CAPABILITIES
+static uid_t euid;
+#endif
+
+#define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \
+ ((tv1).tv_usec-(tv2).tv_usec)/1000 )
+
+#define OFFSET_OF(name,ele) ((size_t)&((name *)0)->ele)
+
+static socklen_t sll_len(size_t halen)
+{
+ socklen_t len = OFFSET_OF(struct sockaddr_ll, sll_addr) + halen;
+ if (len < sizeof(struct sockaddr_ll))
+ len = sizeof(struct sockaddr_ll);
+ return len;
+}
+
+#define SLL_LEN(hln) sll_len(hln)
+
+#if 1 /* hb_mode: always print hb_mode usage in this binary */
+static char print_usage[]={
+"send_arp: sends out custom ARP packet.\n"
+" usage: send_arp [-i repeatinterval-ms] [-r repeatcount] [-p pidfile] \\\n"
+" device src_ip_addr src_hw_addr broadcast_ip_addr netmask\n"
+"\n"
+" where:\n"
+" repeatinterval-ms: ignored\n"
+"\n"
+" repeatcount: how many ARP packets to send.\n"
+"\n"
+" pidfile: pid file to use\n"
+"\n"
+" device: network interface to use\n"
+"\n"
+" src_ip_addr: source ip address\n"
+"\n"
+" src_hw_addr: only \"auto\" is supported.\n"
+" If other specified, it will exit without sending any ARP packets.\n"
+"\n"
+" broadcast_ip_addr: ignored\n"
+"\n"
+" netmask: ignored\n"
+"\n"
+" Notes: Other options of iputils-arping may be accepted but it's not\n"
+" intended to be supported in this binary.\n"
+"\n"
+};
+
+void usage(void)
+{
+ fprintf(stderr, "%s\n", print_usage);
+ exit(2);
+}
+
+#else /* hb_mode */
+
+void usage(void)
+{
+ fprintf(stderr,
+ "Usage: arping [-fqbDUAV] [-c count] [-w timeout] [-I device] [-s source] destination\n"
+ " -f : quit on first reply\n"
+ " -q : be quiet\n"
+ " -b : keep broadcasting, don't go unicast\n"
+ " -D : duplicate address detection mode\n"
+ " -U : Unsolicited ARP mode, update your neighbours\n"
+ " -A : ARP answer mode, update your neighbours\n"
+ " -V : print version and exit\n"
+ " -c count : how many packets to send\n"
+ " -w timeout : how long to wait for a reply\n"
+ " -I device : which ethernet device to use"
+#ifdef DEFAULT_DEVICE_STR
+ " (" DEFAULT_DEVICE_STR ")"
+#endif
+ "\n"
+ " -s source : source ip address\n"
+ " destination : ask for what ip address\n"
+ );
+ exit(2);
+}
+#endif /* hb_mode */
+
+static void set_signal(int signo, void (*handler)(void))
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = (void (*)(int))handler;
+ sa.sa_flags = SA_RESTART;
+ sigaction(signo, &sa, NULL);
+}
+
+#ifdef CAPABILITIES
+static const cap_value_t caps[] = { CAP_NET_RAW, };
+static cap_flag_value_t cap_raw = CAP_CLEAR;
+#endif
+
+static void limit_capabilities(void)
+{
+#ifdef CAPABILITIES
+ cap_t cap_p;
+
+ cap_p = cap_get_proc();
+ if (!cap_p) {
+ perror("arping: cap_get_proc");
+ exit(-1);
+ }
+
+ cap_get_flag(cap_p, CAP_NET_RAW, CAP_PERMITTED, &cap_raw);
+
+ if (cap_raw != CAP_CLEAR) {
+ if (cap_clear(cap_p) < 0) {
+ perror("arping: cap_clear");
+ exit(-1);
+ }
+
+ cap_set_flag(cap_p, CAP_PERMITTED, 1, caps, CAP_SET);
+
+ if (cap_set_proc(cap_p) < 0) {
+ perror("arping: cap_set_proc");
+ if (errno != EPERM)
+ exit(-1);
+ }
+ }
+
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+ perror("arping: prctl");
+ exit(-1);
+ }
+
+ if (setuid(getuid()) < 0) {
+ perror("arping: setuid");
+ exit(-1);
+ }
+
+ if (prctl(PR_SET_KEEPCAPS, 0) < 0) {
+ perror("arping: prctl");
+ exit(-1);
+ }
+
+ cap_free(cap_p);
+#else
+ euid = geteuid();
+#endif
+}
+
+static int modify_capability_raw(int on)
+{
+#ifdef CAPABILITIES
+ cap_t cap_p;
+
+ if (cap_raw != CAP_SET)
+ return on ? -1 : 0;
+
+ cap_p = cap_get_proc();
+ if (!cap_p) {
+ perror("arping: cap_get_proc");
+ return -1;
+ }
+
+ cap_set_flag(cap_p, CAP_EFFECTIVE, 1, caps, on ? CAP_SET : CAP_CLEAR);
+
+ if (cap_set_proc(cap_p) < 0) {
+ perror("arping: cap_set_proc");
+ return -1;
+ }
+
+ cap_free(cap_p);
+#else
+ if (setuid(on ? euid : getuid())) {
+ perror("arping: setuid");
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+static int enable_capability_raw(void)
+{
+ return modify_capability_raw(1);
+}
+
+static int disable_capability_raw(void)
+{
+ return modify_capability_raw(0);
+}
+
+static void drop_capabilities(void)
+{
+#ifdef CAPABILITIES
+ cap_t cap_p = cap_init();
+
+ if (!cap_p) {
+ perror("arping: cap_init");
+ exit(-1);
+ }
+
+ if (cap_set_proc(cap_p) < 0) {
+ perror("arping: cap_set_proc");
+ exit(-1);
+ }
+
+ cap_free(cap_p);
+#else
+ if (setuid(getuid()) < 0) {
+ perror("arping: setuid");
+ exit(-1);
+ }
+#endif
+}
+
+static int send_pack(int s, struct in_addr src, struct in_addr dst,
+ struct sockaddr_ll *ME, struct sockaddr_ll *HE)
+{
+ int err;
+ struct timeval now;
+ unsigned char buf[256];
+ struct arphdr *ah = (struct arphdr*)buf;
+ unsigned char *p = (unsigned char *)(ah+1);
+
+ ah->ar_hrd = htons(ME->sll_hatype);
+ if (ah->ar_hrd == htons(ARPHRD_FDDI))
+ ah->ar_hrd = htons(ARPHRD_ETHER);
+ ah->ar_pro = htons(ETH_P_IP);
+ ah->ar_hln = ME->sll_halen;
+ ah->ar_pln = 4;
+ ah->ar_op = advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST);
+
+ memcpy(p, &ME->sll_addr, ah->ar_hln);
+ p+=ME->sll_halen;
+
+ memcpy(p, &src, 4);
+ p+=4;
+
+ if (advert)
+ memcpy(p, &ME->sll_addr, ah->ar_hln);
+ else
+ memcpy(p, &HE->sll_addr, ah->ar_hln);
+ p+=ah->ar_hln;
+
+ memcpy(p, &dst, 4);
+ p+=4;
+
+ gettimeofday(&now, NULL);
+ err = sendto(s, buf, p-buf, 0, (struct sockaddr*)HE, SLL_LEN(ah->ar_hln));
+ if (err == p-buf) {
+ last = now;
+ sent++;
+ if (!unicasting)
+ brd_sent++;
+ }
+ return err;
+}
+
+static void finish(void)
+{
+ if (!quiet) {
+ printf("Sent %d probes (%d broadcast(s))\n", sent, brd_sent);
+ printf("Received %d response(s)", received);
+ if (brd_recv || req_recv) {
+ printf(" (");
+ if (req_recv)
+ printf("%d request(s)", req_recv);
+ if (brd_recv)
+ printf("%s%d broadcast(s)",
+ req_recv ? ", " : "",
+ brd_recv);
+ printf(")");
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+ fflush(stdout);
+ if (dad)
+ exit(!!received);
+ if (unsolicited)
+ exit(0);
+ exit(!received);
+}
+
+static void catcher(void)
+{
+ struct timeval tv, tv_s, tv_o;
+
+ gettimeofday(&tv, NULL);
+
+ if (start.tv_sec==0)
+ start = tv;
+
+ timersub(&tv, &start, &tv_s);
+ tv_o.tv_sec = timeout;
+ tv_o.tv_usec = 500 * 1000;
+
+ if (count-- == 0 || (timeout && timercmp(&tv_s, &tv_o, >)))
+ finish();
+
+ timersub(&tv, &last, &tv_s);
+ tv_o.tv_sec = 0;
+
+ if (last.tv_sec==0 || timercmp(&tv_s, &tv_o, >)) {
+ send_pack(s, src, dst,
+ (struct sockaddr_ll *)&me, (struct sockaddr_ll *)&he);
+ if (count == 0 && unsolicited)
+ finish();
+ }
+ alarm(1);
+}
+
+static void print_hex(unsigned char *p, int len)
+{
+ int i;
+ for (i=0; i<len; i++) {
+ printf("%02X", p[i]);
+ if (i != len-1)
+ printf(":");
+ }
+}
+
+static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM)
+{
+ struct timeval tv;
+ struct arphdr *ah = (struct arphdr*)buf;
+ unsigned char *p = (unsigned char *)(ah+1);
+ struct in_addr src_ip, dst_ip;
+
+ gettimeofday(&tv, NULL);
+
+ /* Filter out wild packets */
+ if (FROM->sll_pkttype != PACKET_HOST &&
+ FROM->sll_pkttype != PACKET_BROADCAST &&
+ FROM->sll_pkttype != PACKET_MULTICAST)
+ return 0;
+
+ /* Only these types are recognised */
+ if (ah->ar_op != htons(ARPOP_REQUEST) &&
+ ah->ar_op != htons(ARPOP_REPLY))
+ return 0;
+
+ /* ARPHRD check and this darned FDDI hack here :-( */
+ if (ah->ar_hrd != htons(FROM->sll_hatype) &&
+ (FROM->sll_hatype != ARPHRD_FDDI || ah->ar_hrd != htons(ARPHRD_ETHER)))
+ return 0;
+
+ /* Protocol must be IP. */
+ if (ah->ar_pro != htons(ETH_P_IP))
+ return 0;
+ if (ah->ar_pln != 4)
+ return 0;
+ if (ah->ar_hln != ((struct sockaddr_ll *)&me)->sll_halen)
+ return 0;
+ if (len < sizeof(*ah) + 2*(4 + ah->ar_hln))
+ return 0;
+ memcpy(&src_ip, p+ah->ar_hln, 4);
+ memcpy(&dst_ip, p+ah->ar_hln+4+ah->ar_hln, 4);
+ if (!dad) {
+ if (src_ip.s_addr != dst.s_addr)
+ return 0;
+ if (src.s_addr != dst_ip.s_addr)
+ return 0;
+ if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln))
+ return 0;
+ } else {
+ /* DAD packet was:
+ src_ip = 0 (or some src)
+ src_hw = ME
+ dst_ip = tested address
+ dst_hw = <unspec>
+
+ We fail, if receive request/reply with:
+ src_ip = tested_address
+ src_hw != ME
+ if src_ip in request was not zero, check
+ also that it matches to dst_ip, otherwise
+ dst_ip/dst_hw do not matter.
+ */
+ if (src_ip.s_addr != dst.s_addr)
+ return 0;
+ if (memcmp(p, ((struct sockaddr_ll *)&me)->sll_addr, ((struct sockaddr_ll *)&me)->sll_halen) == 0)
+ return 0;
+ if (src.s_addr && src.s_addr != dst_ip.s_addr)
+ return 0;
+ }
+ if (!quiet) {
+ int s_printed = 0;
+ printf("%s ", FROM->sll_pkttype==PACKET_HOST ? "Unicast" : "Broadcast");
+ printf("%s from ", ah->ar_op == htons(ARPOP_REPLY) ? "reply" : "request");
+ printf("%s [", inet_ntoa(src_ip));
+ print_hex(p, ah->ar_hln);
+ printf("] ");
+ if (dst_ip.s_addr != src.s_addr) {
+ printf("for %s ", inet_ntoa(dst_ip));
+ s_printed = 1;
+ }
+ if (memcmp(p+ah->ar_hln+4, ((struct sockaddr_ll *)&me)->sll_addr, ah->ar_hln)) {
+ if (!s_printed)
+ printf("for ");
+ printf("[");
+ print_hex(p+ah->ar_hln+4, ah->ar_hln);
+ printf("]");
+ }
+ if (last.tv_sec) {
+ long usecs = (tv.tv_sec-last.tv_sec) * 1000000 +
+ tv.tv_usec-last.tv_usec;
+ long msecs = (usecs+500)/1000;
+ usecs -= msecs*1000 - 500;
+ printf(" %ld.%03ldms\n", msecs, usecs);
+ } else {
+ printf(" UNSOLICITED?\n");
+ }
+ fflush(stdout);
+ }
+ received++;
+ if (FROM->sll_pkttype != PACKET_HOST)
+ brd_recv++;
+ if (ah->ar_op == htons(ARPOP_REQUEST))
+ req_recv++;
+ if (quit_on_reply || (count == 0 && received == sent))
+ finish();
+ if(!broadcast_only) {
+ memcpy(((struct sockaddr_ll *)&he)->sll_addr, p, ((struct sockaddr_ll *)&me)->sll_halen);
+ unicasting=1;
+ }
+ return 1;
+}
+
+#ifdef USE_SYSFS
+union sysfs_devattr_value {
+ unsigned long ulong;
+ void *ptr;
+};
+
+enum {
+ SYSFS_DEVATTR_IFINDEX,
+ SYSFS_DEVATTR_FLAGS,
+ SYSFS_DEVATTR_ADDR_LEN,
+#if 0
+ SYSFS_DEVATTR_TYPE,
+ SYSFS_DEVATTR_ADDRESS,
+#endif
+ SYSFS_DEVATTR_BROADCAST,
+ SYSFS_DEVATTR_NUM
+};
+
+struct sysfs_devattr_values
+{
+ char *ifname;
+ union sysfs_devattr_value value[SYSFS_DEVATTR_NUM];
+};
+
+static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned idx);
+static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned idx);
+static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned idx);
+
+struct sysfs_devattrs {
+ const char *name;
+ int (*handler)(char *ptr, struct sysfs_devattr_values *v, unsigned int idx);
+ int free;
+} sysfs_devattrs[SYSFS_DEVATTR_NUM] = {
+ [SYSFS_DEVATTR_IFINDEX] = {
+ .name = "ifindex",
+ .handler = sysfs_devattr_ulong_dec,
+ },
+ [SYSFS_DEVATTR_ADDR_LEN] = {
+ .name = "addr_len",
+ .handler = sysfs_devattr_ulong_dec,
+ },
+ [SYSFS_DEVATTR_FLAGS] = {
+ .name = "flags",
+ .handler = sysfs_devattr_ulong_hex,
+ },
+#if 0
+ [SYSFS_DEVATTR_TYPE] = {
+ .name = "type",
+ .handler = sysfs_devattr_ulong_dec,
+ },
+ [SYSFS_DEVATTR_ADDRESS] = {
+ .name = "address",
+ .handler = sysfs_devattr_macaddr,
+ .free = 1,
+ },
+#endif
+ [SYSFS_DEVATTR_BROADCAST] = {
+ .name = "broadcast",
+ .handler = sysfs_devattr_macaddr,
+ .free = 1,
+ },
+};
+#endif
+
+static void byebye(int nsig)
+{
+ /* Avoid an "error exit" log message if we're killed */
+ nsig = 0;
+ exit(nsig);
+}
+
+/*
+ * find_device()
+ *
+ * This function checks 1) if the device (if given) is okay for ARP,
+ * or 2) find fist appropriate device on the system.
+ *
+ * Return value:
+ * >0 : Succeeded, and appropriate device not found.
+ * device.ifindex remains 0.
+ * 0 : Succeeded, and approptiate device found.
+ * device.ifindex is set.
+ * <0 : Failed. Support not found, or other
+ * : system error. Try other method.
+ *
+ * If an appropriate device found, it is recorded inside the
+ * "device" variable for later reference.
+ *
+ * We have several implementations for this.
+ * by_ifaddrs(): requires getifaddr() in glibc, and rtnetlink in
+ * kernel. default and recommended for recent systems.
+ * by_sysfs(): requires libsysfs , and sysfs in kernel.
+ * by_ioctl(): unable to list devices without ipv4 address; this
+ * means, you need to supply the device name for
+ * DAD purpose.
+ */
+/* Common check for ifa->ifa_flags */
+static int check_ifflags(unsigned int ifflags, int fatal)
+{
+ if (!(ifflags & IFF_UP)) {
+ if (fatal) {
+ if (!quiet)
+ printf("Interface \"%s\" is down\n", device.name);
+ exit(2);
+ }
+ return -1;
+ }
+ if (ifflags & (IFF_NOARP | IFF_LOOPBACK)) {
+ if (fatal) {
+ if (!quiet)
+ printf("Interface \"%s\" is not ARPable\n", device.name);
+ exit(dad ? 0 : 2);
+ }
+ return -1;
+ }
+ return 0;
+}
+
+static int find_device_by_ifaddrs(void)
+{
+#ifndef WITHOUT_IFADDRS
+ int rc;
+ struct ifaddrs *ifa0, *ifa;
+ int count = 0;
+
+ rc = getifaddrs(&ifa0);
+ if (rc) {
+ perror("getifaddrs");
+ return -1;
+ }
+
+ for (ifa = ifa0; ifa; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr)
+ continue;
+ if (ifa->ifa_addr->sa_family != AF_PACKET)
+ continue;
+ if (device.name && ifa->ifa_name && strcmp(ifa->ifa_name, device.name))
+ continue;
+
+ if (check_ifflags(ifa->ifa_flags, device.name != NULL) < 0)
+ continue;
+
+ if (!((struct sockaddr_ll *)ifa->ifa_addr)->sll_halen)
+ continue;
+ if (!ifa->ifa_broadaddr)
+ continue;
+
+ device.ifa = ifa;
+
+ if (count++)
+ break;
+ }
+
+ if (count == 1 && device.ifa) {
+ device.ifindex = if_nametoindex(device.ifa->ifa_name);
+ if (!device.ifindex) {
+ perror("arping: if_nametoindex");
+ freeifaddrs(ifa0);
+ return -1;
+ }
+ device.name = device.ifa->ifa_name;
+ return 0;
+ }
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+#ifdef USE_SYSFS
+static void sysfs_devattr_values_init(struct sysfs_devattr_values *v, int do_free)
+{
+ int i;
+ if (do_free) {
+ free(v->ifname);
+ for (i = 0; i < SYSFS_DEVATTR_NUM; i++) {
+ if (sysfs_devattrs[i].free)
+ free(v->value[i].ptr);
+ }
+ }
+ memset(v, 0, sizeof(*v));
+}
+
+static int sysfs_devattr_ulong(char *ptr, struct sysfs_devattr_values *v, unsigned int idx,
+ unsigned int base)
+{
+ unsigned long *p;
+ char *ep;
+
+ if (!ptr || !v)
+ return -1;
+
+ p = &v->value[idx].ulong;
+ errno = 0;
+ *p = strtoul(ptr, &ep, base);
+ if ((*ptr && isspace(*ptr & 0xff)) || errno || (*ep != '\0' && *ep != '\n'))
+ goto out;
+
+ return 0;
+out:
+ return -1;
+}
+
+static int sysfs_devattr_ulong_dec(char *ptr, struct sysfs_devattr_values *v, unsigned int idx)
+{
+ int rc = sysfs_devattr_ulong(ptr, v, idx, 10);
+ return rc;
+}
+
+static int sysfs_devattr_ulong_hex(char *ptr, struct sysfs_devattr_values *v, unsigned int idx)
+{
+ int rc = sysfs_devattr_ulong(ptr, v, idx, 16);
+ return rc;
+}
+
+static int sysfs_devattr_macaddr(char *ptr, struct sysfs_devattr_values *v, unsigned int idx)
+{
+ unsigned char *m;
+ int i;
+ unsigned int addrlen;
+
+ if (!ptr || !v)
+ return -1;
+
+ addrlen = v->value[SYSFS_DEVATTR_ADDR_LEN].ulong;
+ m = malloc(addrlen);
+
+ for (i = 0; i < addrlen; i++) {
+ if (i && *(ptr + i * 3 - 1) != ':')
+ goto out;
+ if (sscanf(ptr + i * 3, "%02hhx", &m[i]) != 1)
+ goto out;
+ }
+
+ v->value[idx].ptr = m;
+ return 0;
+out:
+ free(m);
+ return -1;
+}
+#endif
+
+static int find_device_by_sysfs(void)
+{
+ int rc = -1;
+#ifdef USE_SYSFS
+ struct sysfs_class *cls_net;
+ struct dlist *dev_list;
+ struct sysfs_class_device *dev;
+ struct sysfs_attribute *dev_attr;
+ struct sysfs_devattr_values sysfs_devattr_values;
+ int count = 0;
+
+ if (!device.sysfs) {
+ device.sysfs = malloc(sizeof(*device.sysfs));
+ sysfs_devattr_values_init(device.sysfs, 0);
+ }
+
+ cls_net = sysfs_open_class("net");
+ if (!cls_net) {
+ perror("sysfs_open_class");
+ return -1;
+ }
+
+ dev_list = sysfs_get_class_devices(cls_net);
+ if (!dev_list) {
+ perror("sysfs_get_class_devices");
+ goto out;
+ }
+
+ sysfs_devattr_values_init(&sysfs_devattr_values, 0);
+
+ dlist_for_each_data(dev_list, dev, struct sysfs_class_device) {
+ int i;
+ int rc = -1;
+
+ if (device.name && strcmp(dev->name, device.name))
+ goto do_next;
+
+ sysfs_devattr_values_init(&sysfs_devattr_values, 1);
+
+ for (i = 0; i < SYSFS_DEVATTR_NUM; i++) {
+
+ dev_attr = sysfs_get_classdev_attr(dev, sysfs_devattrs[i].name);
+ if (!dev_attr) {
+ perror("sysfs_get_classdev_attr");
+ rc = -1;
+ break;
+ }
+ if (sysfs_read_attribute(dev_attr)) {
+ perror("sysfs_read_attribute");
+ rc = -1;
+ break;
+ }
+ rc = sysfs_devattrs[i].handler(dev_attr->value, &sysfs_devattr_values, i);
+
+ if (rc < 0)
+ break;
+ }
+
+ if (rc < 0)
+ goto do_next;
+
+ if (check_ifflags(sysfs_devattr_values.value[SYSFS_DEVATTR_FLAGS].ulong,
+ device.name != NULL) < 0)
+ goto do_next;
+
+ if (!sysfs_devattr_values.value[SYSFS_DEVATTR_ADDR_LEN].ulong)
+ goto do_next;
+
+ if (device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong) {
+ if (device.sysfs->value[SYSFS_DEVATTR_FLAGS].ulong & IFF_RUNNING)
+ goto do_next;
+ }
+
+ sysfs_devattr_values.ifname = strdup(dev->name);
+ if (!sysfs_devattr_values.ifname) {
+ perror("malloc");
+ goto out;
+ }
+
+ sysfs_devattr_values_init(device.sysfs, 1);
+ memcpy(device.sysfs, &sysfs_devattr_values, sizeof(*device.sysfs));
+ sysfs_devattr_values_init(&sysfs_devattr_values, 0);
+
+ if (count++)
+ break;
+
+ continue;
+do_next:
+ sysfs_devattr_values_init(&sysfs_devattr_values, 1);
+ }
+
+ if (count == 1) {
+ device.ifindex = device.sysfs->value[SYSFS_DEVATTR_IFINDEX].ulong;
+ device.name = device.sysfs->ifname;
+ }
+ rc = !device.ifindex;
+out:
+ sysfs_close_class(cls_net);
+#endif
+ return rc;
+}
+
+static int check_device_by_ioctl(int s, struct ifreq *ifr)
+{
+ if (ioctl(s, SIOCGIFFLAGS, ifr) < 0) {
+ perror("ioctl(SIOCGIFINDEX");
+ return -1;
+ }
+
+ if (check_ifflags(ifr->ifr_flags, device.name != NULL) < 0)
+ return 1;
+
+ if (ioctl(s, SIOCGIFINDEX, ifr) < 0) {
+ perror("ioctl(SIOCGIFINDEX");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int find_device_by_ioctl(void)
+{
+ int s;
+ struct ifreq *ifr0, *ifr, *ifr_end;
+ size_t ifrsize = sizeof(*ifr);
+ struct ifconf ifc;
+ static struct ifreq ifrbuf;
+ int count = 0;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ memset(&ifrbuf, 0, sizeof(ifrbuf));
+
+ if (device.name) {
+ strncpy(ifrbuf.ifr_name, device.name, sizeof(ifrbuf.ifr_name) - 1);
+ if (check_device_by_ioctl(s, &ifrbuf))
+ goto out;
+ count++;
+ } else {
+ do {
+ int rc;
+ ifr0 = malloc(ifrsize);
+ if (!ifr0) {
+ perror("malloc");
+ goto out;
+ }
+
+ ifc.ifc_buf = (char *)ifr0;
+ ifc.ifc_len = ifrsize;
+
+ rc = ioctl(s, SIOCGIFCONF, &ifc);
+ if (rc < 0) {
+ perror("ioctl(SIOCFIFCONF");
+ goto out;
+ }
+
+ if (ifc.ifc_len + sizeof(*ifr0) + sizeof(struct sockaddr_storage) - sizeof(struct sockaddr) <= ifrsize)
+ break;
+ ifrsize *= 2;
+ free(ifr0);
+ ifr0 = NULL;
+ } while(ifrsize < INT_MAX / 2);
+
+ if (!ifr0) {
+ fprintf(stderr, "arping: too many interfaces!?\n");
+ goto out;
+ }
+
+ ifr_end = (struct ifreq *)(((char *)ifr0) + ifc.ifc_len - sizeof(*ifr0));
+ for (ifr = ifr0; ifr <= ifr_end; ifr++) {
+ if (check_device_by_ioctl(s, &ifrbuf))
+ continue;
+ memcpy(&ifrbuf.ifr_name, ifr->ifr_name, sizeof(ifrbuf.ifr_name));
+ if (count++)
+ break;
+ }
+ }
+
+ close(s);
+
+ if (count == 1) {
+ device.ifindex = ifrbuf.ifr_ifindex;
+ device.name = ifrbuf.ifr_name;
+ }
+ return !device.ifindex;
+out:
+ close(s);
+ return -1;
+}
+
+static int find_device(void)
+{
+ int rc;
+ rc = find_device_by_ifaddrs();
+ if (rc >= 0)
+ goto out;
+ rc = find_device_by_sysfs();
+ if (rc >= 0)
+ goto out;
+ rc = find_device_by_ioctl();
+out:
+ return rc;
+}
+
+/*
+ * set_device_broadcast()
+ *
+ * This fills the device "broadcast address"
+ * based on information found by find_device() funcion.
+ */
+static int set_device_broadcast_ifaddrs_one(struct device *device, unsigned char *ba, size_t balen, int fatal)
+{
+#ifndef WITHOUT_IFADDRS
+ struct ifaddrs *ifa;
+ struct sockaddr_ll *sll;
+
+ if (!device)
+ return -1;
+
+ ifa = device->ifa;
+ if (!ifa)
+ return -1;
+
+ sll = (struct sockaddr_ll *)ifa->ifa_broadaddr;
+
+ if (sll->sll_halen != balen) {
+ if (fatal) {
+ if (!quiet)
+ printf("Address length does not match...\n");
+ exit(2);
+ }
+ return -1;
+ }
+ memcpy(ba, sll->sll_addr, sll->sll_halen);
+ return 0;
+#else
+ return -1;
+#endif
+}
+static int set_device_broadcast_sysfs(struct device *device, unsigned char *ba, size_t balen)
+{
+#ifdef USE_SYSFS
+ struct sysfs_devattr_values *v;
+ if (!device)
+ return -1;
+ v = device->sysfs;
+ if (!v)
+ return -1;
+ if (v->value[SYSFS_DEVATTR_ADDR_LEN].ulong != balen)
+ return -1;
+ memcpy(ba, v->value[SYSFS_DEVATTR_BROADCAST].ptr, balen);
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static int set_device_broadcast_fallback(struct device *device, unsigned char *ba, size_t balen)
+{
+ if (!quiet)
+ fprintf(stderr, "WARNING: using default broadcast address.\n");
+ memset(ba, -1, balen);
+ return 0;
+}
+
+static void set_device_broadcast(struct device *dev, unsigned char *ba, size_t balen)
+{
+ if (!set_device_broadcast_ifaddrs_one(dev, ba, balen, 0))
+ return;
+ if (!set_device_broadcast_sysfs(dev, ba, balen))
+ return;
+ set_device_broadcast_fallback(dev, ba, balen);
+}
+
+int
+main(int argc, char **argv)
+{
+ int socket_errno;
+ int ch;
+ int hb_mode = 0;
+
+ signal(SIGTERM, byebye);
+ signal(SIGPIPE, byebye);
+
+ limit_capabilities();
+
+#ifdef USE_IDN
+ setlocale(LC_ALL, "");
+#endif
+
+ enable_capability_raw();
+
+ s = socket(PF_PACKET, SOCK_DGRAM, 0);
+ socket_errno = errno;
+
+ disable_capability_raw();
+
+ while ((ch = getopt(argc, argv, "h?bfDUAqc:w:s:I:Vr:i:p:")) != EOF) {
+ switch(ch) {
+ case 'b':
+ broadcast_only=1;
+ break;
+ case 'D':
+ dad++;
+ quit_on_reply=1;
+ break;
+ case 'U':
+ unsolicited++;
+ break;
+ case 'A':
+ advert++;
+ unsolicited++;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ case 'r': /* send_arp.libnet compatibility option */
+ hb_mode = 1;
+ /* fall-through */
+ case 'c':
+ count = atoi(optarg);
+ break;
+ case 'w':
+ timeout = atoi(optarg);
+ break;
+ case 'I':
+ device.name = optarg;
+ break;
+ case 'f':
+ quit_on_reply=1;
+ break;
+ case 's':
+ source = optarg;
+ break;
+ case 'V':
+ printf("send_arp utility, based on arping from iputils-%s\n", SNAPSHOT);
+ exit(0);
+ case 'p':
+ case 'i':
+ hb_mode = 1;
+ /* send_arp.libnet compatibility options, ignore */
+ break;
+ case 'h':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if(hb_mode) {
+ /* send_arp.libnet compatibility mode */
+ if (argc - optind != 5) {
+ usage();
+ return 1;
+ }
+ /*
+ * argv[optind+1] DEVICE dc0,eth0:0,hme0:0,
+ * argv[optind+2] IP 192.168.195.186
+ * argv[optind+3] MAC ADDR 00a0cc34a878
+ * argv[optind+4] BROADCAST 192.168.195.186
+ * argv[optind+5] NETMASK ffffffffffff
+ */
+
+ unsolicited = 1;
+ device.name = argv[optind];
+ target = argv[optind+1];
+ if (strcmp(argv[optind+2], "auto")) {
+ fprintf(stderr, "send_arp.linux: Gratuitous ARPs are not sent in the Cluster IP configuration\n");
+ /* return success to suppress an error log by the RA */
+ exit(0);
+ }
+
+ } else {
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+
+ target = *argv;
+ }
+
+ if (device.name && !*device.name)
+ device.name = NULL;
+
+ if (s < 0) {
+ errno = socket_errno;
+ perror("arping: socket");
+ exit(2);
+ }
+
+ if (find_device() < 0)
+ exit(2);
+
+ if (!device.ifindex) {
+ if (device.name) {
+ fprintf(stderr, "arping: Device %s not available.\n", device.name);
+ exit(2);
+ }
+ fprintf(stderr, "arping: device (option -I) is required.\n");
+ usage();
+ }
+
+ if (inet_aton(target, &dst) != 1) {
+ struct hostent *hp;
+ char *idn = target;
+#ifdef USE_IDN
+ int rc;
+
+ rc = idna_to_ascii_lz(target, &idn, 0);
+
+ if (rc != IDNA_SUCCESS) {
+ fprintf(stderr, "arping: IDN encoding failed: %s\n", idna_strerror(rc));
+ exit(2);
+ }
+#endif
+
+ hp = gethostbyname2(idn, AF_INET);
+ if (!hp) {
+ fprintf(stderr, "arping: unknown host %s\n", target);
+ exit(2);
+ }
+
+#ifdef USE_IDN
+ free(idn);
+#endif
+
+ memcpy(&dst, hp->h_addr, 4);
+ }
+
+ if (source && inet_aton(source, &src) != 1) {
+ fprintf(stderr, "arping: invalid source %s\n", source);
+ exit(2);
+ }
+
+ if (!dad && unsolicited && src.s_addr == 0)
+ src = dst;
+
+ if (!dad || src.s_addr) {
+ struct sockaddr_in saddr;
+ int probe_fd = socket(AF_INET, SOCK_DGRAM, 0);
+
+ if (probe_fd < 0) {
+ perror("socket");
+ exit(2);
+ }
+ if (device.name) {
+ enable_capability_raw();
+
+ if (setsockopt(probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device.name, strlen(device.name)+1) == -1)
+ perror("WARNING: interface is ignored");
+
+ disable_capability_raw();
+ }
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ if (src.s_addr) {
+ saddr.sin_addr = src;
+ if (bind(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
+ perror("bind");
+ exit(2);
+ }
+ } else if (!dad) {
+ int on = 1;
+ socklen_t alen = sizeof(saddr);
+
+ saddr.sin_port = htons(1025);
+ saddr.sin_addr = dst;
+
+ if (setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, (char*)&on, sizeof(on)) == -1)
+ perror("WARNING: setsockopt(SO_DONTROUTE)");
+ if (connect(probe_fd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
+ perror("connect");
+ exit(2);
+ }
+ if (getsockname(probe_fd, (struct sockaddr*)&saddr, &alen) == -1) {
+ perror("getsockname");
+ exit(2);
+ }
+ src = saddr.sin_addr;
+ }
+ close(probe_fd);
+ };
+
+ ((struct sockaddr_ll *)&me)->sll_family = AF_PACKET;
+ ((struct sockaddr_ll *)&me)->sll_ifindex = device.ifindex;
+ ((struct sockaddr_ll *)&me)->sll_protocol = htons(ETH_P_ARP);
+ if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) {
+ perror("bind");
+ exit(2);
+ }
+
+ if (1) {
+ socklen_t alen = sizeof(me);
+ if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) {
+ perror("getsockname");
+ exit(2);
+ }
+ }
+ if (((struct sockaddr_ll *)&me)->sll_halen == 0) {
+ if (!quiet)
+ printf("Interface \"%s\" is not ARPable (no ll address)\n", device.name);
+ exit(dad?0:2);
+ }
+
+ he = me;
+
+ set_device_broadcast(&device, ((struct sockaddr_ll *)&he)->sll_addr,
+ ((struct sockaddr_ll *)&he)->sll_halen);
+
+ if (!quiet) {
+ printf("ARPING %s ", inet_ntoa(dst));
+ printf("from %s %s\n", inet_ntoa(src), device.name ? : "");
+ }
+
+ if (!src.s_addr && !dad) {
+ fprintf(stderr, "arping: no source address in not-DAD mode\n");
+ exit(2);
+ }
+
+ drop_capabilities();
+
+ set_signal(SIGINT, finish);
+ set_signal(SIGALRM, catcher);
+
+ catcher();
+
+ while(1) {
+ sigset_t sset, osset;
+ unsigned char packet[4096];
+ struct sockaddr_storage from;
+ socklen_t alen = sizeof(from);
+ int cc;
+
+ if ((cc = recvfrom(s, packet, sizeof(packet), 0,
+ (struct sockaddr *)&from, &alen)) < 0) {
+ perror("arping: recvfrom");
+ continue;
+ }
+
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGALRM);
+ sigaddset(&sset, SIGINT);
+ sigprocmask(SIG_BLOCK, &sset, &osset);
+ recv_pack(packet, cc, (struct sockaddr_ll *)&from);
+ sigprocmask(SIG_SETMASK, &osset, NULL);
+ }
+}
+
+
diff --git a/tools/sfex.h b/tools/sfex.h
new file mode 100644
index 0000000..3795c29
--- /dev/null
+++ b/tools/sfex.h
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * Shared Disk File EXclusiveness Control Program(SF-EX)
+ *
+ * sfex.h --- Primary include file for SF-EX *.c files.
+ *
+ * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------*/
+
+#ifndef SFEX_H
+#define SFEX_H
+
+#include <clplumbing/cl_log.h>
+#include <clplumbing/coredumps.h>
+#include <clplumbing/realtime.h>
+
+#include <stdint.h>
+
+/* version, revision */
+/* These numbers are integer and, max number is 999.
+ If these numbers change, version numbers in the configure.ac
+ (AC_INIT, AM_INIT_AUTOMAKE) must change together.
+ */
+#define SFEX_VERSION 1
+#define SFEX_REVISION 3
+
+#if 0
+#ifndef TRUE
+# define TRUE 1
+#endif
+#ifndef FALSE
+# define FALSE 0
+#endif
+#ifndef MIN
+# define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+#endif
+
+/* for Linux >= 2.6, the alignment should be 512
+ for Linux < 2.6, the alignment should be sysconf(_SC_PAGESIZE)
+ we default to _SC_PAGESIZE
+ */
+#define SFEX_ODIRECT_ALIGNMENT sysconf(_SC_PAGESIZE)
+
+/*
+ * sfex_controldata --- control data
+ *
+ * This is allocated the head of sfex mata-data area.
+ *
+ * magic number --- 4 bytes. This is fixed in {0x01, 0x1f, 0x71, 0x7f}.
+ *
+ * version number --- 4 bytes. This is printable integer number and
+ * range is from 0 to 999. This must be left-justify, null(0x00) padding, and
+ * make a last byte null.
+ *
+ * revision number --- 4 bytes. This is printable integer number and
+ * range is from 0 to 999. This must be left-justify, null(0x00) padding, and
+ * make a last byte null.
+ *
+ * blocksize --- 8bytes. This is printable integer number and range is from
+ * 512 to 9999999. This must be left-justify, null(0x00) padding, and make a
+ * last byte null. This is a size of control data and lock data(one lock data
+ * size when there are plural), and it is shown by number of bytes.
+ * For avoiding partial writing, usually block size is set 512 byte etc.
+ * If you use direct I/O(if you spacificate --enable-directio for configure
+ * script), note that this value is used for input and output buffer alignment.
+ * (In the Linux kernel 2.6, if this value is not 512 multibles, direct I/O
+ * does not work)
+
+ * number of locks --- 4 bytes. This is printable integer number and range
+ * is from 1 to 999. This must be left-justify, null(0x00) padding, and make
+ * a last byte null. This is the number of locks following this control data.
+ *
+ * padding --- The size of this member depend on blocksize. It is adjusted so
+ * that the whole of the control data including this padding area becomes
+ * blocksize. The contents of padding area are all 0x00.
+ */
+typedef struct sfex_controldata {
+ char magic[4]; /* magic number */
+ int version; /* version number */
+ int revision; /* revision number */
+ size_t blocksize; /* block size */
+ int numlocks; /* number of locks */
+} sfex_controldata;
+
+typedef struct sfex_controldata_ondisk {
+ uint8_t magic[4];
+ uint8_t version[4];
+ uint8_t revision[4];
+ uint8_t blocksize[8];
+ uint8_t numlocks[4];
+} sfex_controldata_ondisk;
+
+/*
+ * sfex_lockdata --- lock data
+ *
+ * This data(number is sfex_controldata.numlocks) are allocated behind of
+ * sfex_controldata in the sfex meta-data area. The meaning of each member
+ * and the storage method to mata data area are following;
+ *
+ * lock status --- 1 byte. printable character. Content is either one of
+ * following;
+ * SFEX_STATUS_UNLOCK: It show the status that no node locks.
+ * SFEX_STATUS_LOCK: It show the status that nodename node is holding lock.
+ * (But there is an exception. Refer to explanation of "count" member.)
+ *
+ * increment counter --- 4 bytes. This is printable integer number and range
+ * is from 1 to 999. This must be left-justify, null(0x00) padding, and make
+ * a last byte null. The node holding a lock increments this counter
+ * periodically. If this counter does not increment for a certain period of
+ * time, we consider that the lock is invalid. If it overflow, return to 0.
+ * Initial value is 0.
+ *
+ * node name --- 256bytes. This is printable string. This must be left-justify,
+ * null(0x00) padding, and make a last byte null. This is node name that update
+ * lock data last. The node name must be same to get uname(2). Initial values
+ * are white spaces.
+ *
+ * padding --- The size of this member depend on blocksize. It is adjusted so
+ * that the whole of the control data including this padding area becomes
+ * blocksize. The contents of padding area are all 0x00.
+ */
+typedef struct sfex_lockdata {
+ char status; /* status of lock */
+ int count; /* increment counter */
+ char nodename[256]; /* node name */
+} sfex_lockdata;
+
+typedef struct sfex_lockdata_ondisk {
+ uint8_t status;
+ uint8_t count[4];
+ uint8_t nodename[256];
+} sfex_lockdata_ondisk;
+
+/* character for lock status. This is used in sfex_lockdata.status */
+#define SFEX_STATUS_UNLOCK 'u' /* unlock */
+#define SFEX_STATUS_LOCK 'l' /* lock */
+
+/* features of each member of control data and lock data */
+#define SFEX_MAGIC "SFEX"
+#define SFEX_MIN_NUMLOCKS 1
+#define SFEX_MAX_NUMLOCKS 999
+#define SFEX_MIN_COUNT 0
+#define SFEX_MAX_COUNT 999
+#define SFEX_MAX_NODENAME (sizeof(((sfex_lockdata *)0)->nodename) - 1)
+
+/* update macro for increment counter */
+#define SFEX_NEXT_COUNT(c) (c >= SFEX_MAX_COUNT ? c - SFEX_MAX_COUNT : c + 1)
+
+/* extern variables */
+extern const char *progname;
+extern char *nodename;
+extern unsigned long sector_size;
+
+#endif /* SFEX_H */
diff --git a/tools/sfex_daemon.c b/tools/sfex_daemon.c
new file mode 100644
index 0000000..9a761b8
--- /dev/null
+++ b/tools/sfex_daemon.c
@@ -0,0 +1,345 @@
+#include <config.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include "sfex.h"
+#include "sfex_lib.h"
+
+#if HAVE_GLUE_CONFIG_H
+#include <glue_config.h> /* for HA_LOG_FACILITY */
+#endif
+
+static int sysrq_fd;
+static int lock_index = 1; /* default 1st lock */
+static time_t collision_timeout = 1; /* default 1 sec */
+static time_t lock_timeout = 60; /* default 60 sec */
+time_t unlock_timeout = 60;
+static time_t monitor_interval = 10;
+
+static sfex_controldata cdata;
+static sfex_lockdata ldata;
+static sfex_lockdata ldata_new;
+
+static const char *device;
+const char *progname;
+char *nodename;
+static const char *rsc_id = "sfex";
+
+static void usage(FILE *dist) {
+ fprintf(dist, "usage: %s [-i <index>] [-c <collision_timeout>] [-t <lock_timeout>] <device>\n", progname);
+}
+
+static void acquire_lock(void)
+{
+ if (read_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "read_lockdata failed in acquire_lock\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if ((ldata.status == SFEX_STATUS_LOCK) && (strncmp(nodename, (const char*)(ldata.nodename), sizeof(ldata.nodename)))) {
+ unsigned int t = lock_timeout;
+ while (t > 0)
+ t = sleep(t);
+ read_lockdata(&cdata, &ldata_new, lock_index);
+ if (ldata.count != ldata_new.count) {
+ cl_log(LOG_ERR, "can\'t acquire lock: the lock's already hold by some other node.\n");
+ exit(2);
+ }
+ }
+
+ /* The lock acquisition is possible because it was not updated. */
+ ldata.status = SFEX_STATUS_LOCK;
+ ldata.count = SFEX_NEXT_COUNT(ldata.count);
+ strncpy((char*)(ldata.nodename), nodename, sizeof(ldata.nodename) - 1);
+ if (write_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "write_lockdata failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* detect the collision of lock */
+ /* The collision occurs when two or more nodes do the reservation
+ processing of the lock at the same time. It waits for collision_timeout
+ seconds to detect this,and whether the superscription of lock data by
+ another node is done is checked. If the superscription was done by
+ another node, the lock acquisition with the own node is given up.
+ */
+ {
+ unsigned int t = collision_timeout;
+ while (t > 0)
+ t = sleep(t);
+ if (read_lockdata(&cdata, &ldata_new, lock_index) == -1) {
+ cl_log(LOG_ERR, "read_lockdata failed in collision detection\n");
+ }
+ if (strncmp((char*)(ldata.nodename), (const char*)(ldata_new.nodename), sizeof(ldata.nodename))) {
+ cl_log(LOG_ERR, "can\'t acquire lock: collision detected in the air.\n");
+ exit(2);
+ }
+ }
+
+ /* extension of lock */
+ /* Validly time of the lock is extended. It is because of spending at
+ the collision_timeout seconds to detect the collision. */
+ ldata.count = SFEX_NEXT_COUNT(ldata.count);
+ if (write_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "write_lockdata failed in extension of lock\n");
+ exit(EXIT_FAILURE);
+ }
+ cl_log(LOG_INFO, "lock acquired\n");
+}
+
+static void error_todo (void)
+{
+ if (fork() == 0) {
+ cl_log(LOG_INFO, "Execute \"crm_resource -F -r %s --node %s\" command\n", rsc_id, nodename);
+ execl("/usr/sbin/crm_resource", "crm_resource", "-F", "-r", rsc_id, "--node", nodename, NULL);
+ } else {
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void failure_todo(void)
+{
+#ifdef SFEX_TESTING
+ exit(EXIT_FAILURE);
+#else
+ /*execl("/usr/sbin/crm_resource", "crm_resource", "-F", "-r", rsc_id, "--node", nodename, NULL); */
+ int ret;
+
+ cl_log(LOG_INFO, "Force reboot node %s\n", nodename);
+ ret = write(sysrq_fd, "b\n", 2);
+ if (ret == -1) {
+ cl_log(LOG_ERR, "%s\n", strerror(errno));
+ }
+ close(sysrq_fd);
+ exit(EXIT_FAILURE);
+#endif
+}
+
+static void update_lock(void)
+{
+ /* read lock data */
+ if (read_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "read_lockdata failed in update_lock\n");
+ error_todo();
+ exit(EXIT_FAILURE);
+ }
+
+ /* check current lock status */
+ /* if own node is not locking, lock update is failed */
+ if (ldata.status != SFEX_STATUS_LOCK || strncmp((const char*)(ldata.nodename), nodename, sizeof(ldata.nodename))) {
+ cl_log(LOG_ERR, "can't update lock.\n");
+ failure_todo();
+ exit(EXIT_FAILURE);
+ }
+
+ /* lock update */
+ ldata.count = SFEX_NEXT_COUNT(ldata.count);
+ if (write_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "write_lockdata failed in update_lock\n");
+ error_todo();
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void release_lock(void)
+{
+ /* The only thing I care about in release_lock(), is to terminate the process */
+
+ /* read lock data */
+ if (read_lockdata(&cdata, &ldata, lock_index) == -1) {
+ cl_log(LOG_ERR, "read_lockdata failed in release_lock\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* check current lock status */
+ /* if own node is not locking, we judge that lock has been released already */
+ if (ldata.status != SFEX_STATUS_LOCK || strncmp((const char*)(ldata.nodename), nodename, sizeof(ldata.nodename))) {
+ cl_log(LOG_ERR, "lock was already released.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* lock release */
+ ldata.status = SFEX_STATUS_UNLOCK;
+ if (write_lockdata(&cdata, &ldata, lock_index) == -1) {
+ /*FIXME: We are going to self-stop */
+ cl_log(LOG_ERR, "write_lockdata failed in release_lock\n");
+ exit(EXIT_FAILURE);
+ }
+ cl_log(LOG_INFO, "lock released\n");
+}
+
+static void quit_handler(int signo, siginfo_t *info, void *context)
+{
+ cl_log(LOG_INFO, "quit_handler called. now releasing lock\n");
+ release_lock();
+ cl_log(LOG_INFO, "Shutdown sfex_daemon with EXIT_SUCCESS\n");
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+
+ int ret;
+
+ progname = get_progname(argv[0]);
+ nodename = get_nodename();
+
+ cl_log_set_entity(progname);
+ cl_log_set_facility(HA_LOG_FACILITY);
+ cl_inherit_logging_environment(0);
+
+ /* read command line option */
+ opterr = 0;
+ while (1) {
+ int c = getopt(argc, argv, "hi:c:t:m:n:r:");
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h': /* help*/
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ case 'i': /* -i <index> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) {
+ cl_log(LOG_ERR,
+ "index %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ optarg,
+ (unsigned long)SFEX_MIN_NUMLOCKS,
+ (unsigned long)SFEX_MAX_NUMLOCKS);
+ exit(4);
+ }
+ lock_index = l;
+ }
+ break;
+ case 'c': /* -c <collision_timeout> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < 1 || l > INT_MAX) {
+ cl_log(LOG_ERR,
+ "collision_timeout %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ optarg,
+ (unsigned long)1,
+ (unsigned long)INT_MAX);
+ exit(4);
+ }
+ collision_timeout = l;
+ }
+ break;
+ case 'm': /* -m <monitor_interval> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < 1 || l > INT_MAX) {
+ cl_log(LOG_ERR,
+ "monitor_interval %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ optarg,
+ (unsigned long)1,
+ (unsigned long)INT_MAX);
+ exit(4);
+ }
+ monitor_interval = l;
+ }
+ break;
+ case 't': /* -t <lock_timeout> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < 1 || l > INT_MAX) {
+ cl_log(LOG_ERR,
+ "lock_timeout %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ optarg,
+ (unsigned long)1,
+ (unsigned long)INT_MAX);
+ exit(4);
+ }
+ lock_timeout = l;
+ }
+ break;
+ case 'n':
+ {
+ free(nodename);
+ if (strlen(optarg) > SFEX_MAX_NODENAME) {
+ cl_log(LOG_ERR, "nodename %s is too long. must be less than %d byte.\n",
+ optarg,
+ (unsigned int)SFEX_MAX_NODENAME);
+ exit(EXIT_FAILURE);
+ }
+ nodename = strdup(optarg);
+ }
+ break;
+ case 'r':
+ {
+ rsc_id = strdup(optarg);
+ }
+ break;
+ case '?': /* error */
+ usage(stderr);
+ exit(4);
+ }
+ }
+ /* check parameter except the option */
+ if (optind >= argc) {
+ cl_log(LOG_ERR, "no device specified.\n");
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ } else if (optind + 1 < argc) {
+ cl_log(LOG_ERR, "too many arguments.\n");
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+ device = argv[optind];
+
+ prepare_lock(device);
+#if !SFEX_TESTING
+ sysrq_fd = open("/proc/sysrq-trigger", O_WRONLY);
+ if (sysrq_fd == -1) {
+ cl_log(LOG_ERR, "failed to open /proc/sysrq-trigger due to %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ ret = lock_index_check(&cdata, lock_index);
+ if (ret == -1)
+ exit(EXIT_FAILURE);
+
+ {
+ struct sigaction sig_act;
+ sigemptyset (&sig_act.sa_mask);
+ sig_act.sa_flags = SA_SIGINFO;
+
+ sig_act.sa_sigaction = quit_handler;
+ ret = sigaction(SIGTERM, &sig_act, NULL);
+ if (ret == -1) {
+ cl_log(LOG_ERR, "sigaction failed\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ cl_log(LOG_INFO, "Starting SFeX Daemon...\n");
+
+ /* acquire lock first.*/
+ acquire_lock();
+
+ if (daemon(0, 1) != 0) {
+ cl_perror("%s::%d: daemon() failed.", __FUNCTION__, __LINE__);
+ release_lock();
+ exit(EXIT_FAILURE);
+ }
+
+ cl_make_realtime(-1, -1, 128, 128);
+
+ cl_log(LOG_INFO, "SFeX Daemon started.\n");
+ while (1) {
+ sleep (monitor_interval);
+ update_lock();
+ }
+}
diff --git a/tools/sfex_init.8 b/tools/sfex_init.8
new file mode 100644
index 0000000..bdcf8b9
--- /dev/null
+++ b/tools/sfex_init.8
@@ -0,0 +1,19 @@
+.TH SFEX_INIT "8" "May 2010" "sfex_init 1.0.3" "System Administration Utilities"
+.SH NAME
+sfex_init \- Part of the Linux-HA project
+.SH SYNOPSIS
+.B sfex_init
+[\fI-Lh\fR] \fR[\fI-n numlocks\fR]\fI device
+.SH DESCRIPTION
+Initialize Shared Disk File EXclusiveness Control Program (SF-EX) meta-data.
+.SH OPTIONS
+.TP
+\fB\-n\fR numlocks
+The number of storing lock data is specified by integer
+of one or more. When you want to control two or more resources by one
+meta-data, you set the value of two or more to numlocks.
+Default is 1.
+.TP
+\fBdevice\fR
+This is file path which stored meta-data.
+It is usually expressed in "/dev/...", because it is partition on the shared disk.
diff --git a/tools/sfex_init.c b/tools/sfex_init.c
new file mode 100644
index 0000000..fed24dd
--- /dev/null
+++ b/tools/sfex_init.c
@@ -0,0 +1,177 @@
+/*-------------------------------------------------------------------------
+ *
+ * Shared Disk File EXclusiveness Control Program(SF-EX)
+ *
+ * sfex_init.c --- Initialize SF-EX meta-data.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------
+ *
+ * sfex_init [-b <blocksize>] [-n <numlocks>] <device>
+ *
+ * -b <blocksize> --- The size of the block is specified by the number of
+ * bytes. In general, to prevent a partial writing to the disk, the size
+ * of block is set to 512 bytes etc.
+ * Note a set value because this value is used also for the alignment
+ * adjustment in the input-output buffer in the program when direct I/O
+ * is used(When you specify --enable-directio option for configure script).
+ * (In Linux kernel 2.6, "direct I/O " does not work if this value is not
+ * a multiple of 512.) Default is 512 bytes.
+ *
+ * -n <numlocks> --- The number of storing lock data is specified by integer
+ * of one or more. When you want to control two or more resources by one
+ * meta-data, you set the value of two or more to numlocks. A necessary disk
+ * area for meta data are (blocksize*(1+numlocks))bytes. Default is 1.
+ *
+ * <device> --- This is file path which stored meta-data. It is usually
+ * expressed in "/dev/...", because it is partition on the shared disk.
+ *
+ * exit code --- 0 - Normal end. 3 - Error occurs while processing it.
+ * The content of the error is displayed into stderr. 4 - The mistake is
+ * found in the command line parameter.
+ *
+ *-------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "sfex.h"
+#include "sfex_lib.h"
+
+const char *progname;
+char *nodename;
+
+/*
+ * usage --- display command line syntax
+ *
+ * display command line syntax. By the purpose, it can specify destination
+ * stream. stdout or stderr is set usually.
+ *
+ * dist --- destination stream of the command line syntax, such as stderr.
+ *
+ * return value --- void
+ */
+static void usage(FILE *dist) {
+ fprintf(dist, "usage: %s [-n <numlocks>] <device>\n", progname);
+}
+
+/*
+ * main --- main function
+ *
+ * entry point of sfex_init command.
+ *
+ * exit code --- 0 - Normal end. 3 - Error occurs while processing it.
+ * The content of the error is displayed into stderr. 4 - The mistake is
+ * found in the command line parameter.
+ */
+int
+main(int argc, char *argv[]) {
+ sfex_controldata cdata;
+ sfex_lockdata ldata;
+
+ /* command line parameter */
+ int numlocks = 1; /* default 1 locks */
+ const char *device;
+
+ /*
+ * startup process
+ */
+
+ /* get a program name */
+ progname = get_progname(argv[0]);
+
+ /* enable the cl_log output from the sfex library */
+ cl_log_set_entity(progname);
+ /* The cl_log is output only to the standard error output */
+ cl_log_enable_stderr(TRUE);
+
+ /* read command line option */
+ opterr = 0;
+ while (1) {
+ int c = getopt(argc, argv, "hn:");
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h': /* help */
+ usage(stdout);
+ exit(0);
+ case 'n': /* -n <numlocks> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) {
+ fprintf(stderr,
+ "%s: ERROR: numlocks %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ progname, optarg,
+ (unsigned long)SFEX_MIN_NUMLOCKS,
+ (unsigned long)SFEX_MAX_NUMLOCKS);
+ exit(4);
+ }
+ numlocks = l;
+ }
+ break;
+ case '?': /* error */
+ usage(stderr);
+ exit(4);
+ }
+ }
+
+ /* check parameter except the option */
+ if (optind >= argc) {
+ fprintf(stderr, "%s: ERROR: no device specified.\n", progname);
+ usage(stderr);
+ exit(4);
+ } else if (optind + 1 < argc) {
+ fprintf(stderr, "%s: ERROR: too many arguments.\n", progname);
+ usage(stderr);
+ exit(4);
+ }
+ device = argv[optind];
+
+ prepare_lock(device);
+
+ /* main processes start */
+
+ /* get a node name */
+ nodename = get_nodename();
+
+ /* create and control data and lock data */
+ init_controldata(&cdata, sector_size, numlocks);
+ init_lockdata(&ldata);
+
+ /* write out control data and lock data */
+ write_controldata(&cdata);
+ {
+ int index;
+ for (index = 1; index <= numlocks; index++)
+ if (write_lockdata(&cdata, &ldata, index) == -1) {
+ fprintf(stderr, "%s: ERROR: cannot write lock data (index=%d).\n",
+ progname, index);
+ exit(3);
+ }
+ }
+
+ exit(0);
+}
diff --git a/tools/sfex_lib.c b/tools/sfex_lib.c
new file mode 100644
index 0000000..2d01602
--- /dev/null
+++ b/tools/sfex_lib.c
@@ -0,0 +1,474 @@
+/*-------------------------------------------------------------------------
+ *
+ * Shared Disk File EXclusiveness Control Program(SF-EX)
+ *
+ * sfex_lib.c --- Libraries for other SF-EX modules.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+#include <linux/fs.h>
+
+#include "sfex.h"
+#include "sfex_lib.h"
+
+static void *locked_mem;
+static int dev_fd;
+unsigned long sector_size = 0;
+
+int
+prepare_lock (const char *device)
+{
+ int sec_tmp = 0;
+
+ do {
+ dev_fd = open (device, O_RDWR | O_DIRECT | O_SYNC);
+ if (dev_fd == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ cl_log(LOG_ERR, "can't open device %s: %s\n",
+ device, strerror (errno));
+ exit (3);
+ }
+ break;
+ }
+ while (1);
+
+ ioctl(dev_fd, BLKSSZGET, &sec_tmp);
+ sector_size = (unsigned long)sec_tmp;
+ if (sector_size == 0) {
+ cl_log(LOG_ERR, "Get sector size failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (posix_memalign
+ ((void **) (&locked_mem), SFEX_ODIRECT_ALIGNMENT,
+ sector_size) != 0) {
+ cl_log(LOG_ERR, "Failed to allocate aligned memory\n");
+ exit (3);
+ }
+ memset (locked_mem, 0, sector_size);
+
+ return 0;
+}
+
+/*
+ * get_progname --- a program name
+ *
+ * We get program name from directory path. It does not include delimiter
+ * characters. Return value is pointer that point string of program name.
+ * We assume delimiter is '/'.
+ */
+const char *
+get_progname (const char *argv0)
+{
+ char *p;
+
+ p = strrchr (argv0, '/');
+ if (p)
+ return p + 1;
+ else
+ return argv0;
+}
+
+/*
+ * get_nodename --- get a node name(hostname)
+ *
+ * We get a node name by using uname(2) and return pointer of it.
+ * The error checks are done in this function. The caller does not have
+ * to check return value.
+ */
+char *
+get_nodename (void)
+{
+ struct utsname u;
+ char *n;
+
+ if (uname (&u)) {
+ cl_log(LOG_ERR, "%s\n", strerror (errno));
+ exit (3);
+ }
+ if (strlen (u.nodename) > SFEX_MAX_NODENAME) {
+ cl_log(LOG_ERR,
+ "nodename %s is too long. must be less than %lu byte.\n",
+ u.nodename, (unsigned long)SFEX_MAX_NODENAME);
+ exit (3);
+ }
+ n = strdup (&u.nodename[0]);
+ if (!n) {
+ cl_log(LOG_ERR, "%s\n", strerror (errno));
+ exit (3);
+ }
+ return n;
+}
+
+/*
+ * init_controldata --- initialize control data
+ *
+ * We initialize each member of sfex_controldata structure.
+ */
+void
+init_controldata (sfex_controldata * cdata, size_t blocksize, int numlocks)
+{
+ memcpy (cdata->magic, SFEX_MAGIC, sizeof (cdata->magic));
+ cdata->version = SFEX_VERSION;
+ cdata->revision = SFEX_REVISION;
+ cdata->blocksize = blocksize;
+ cdata->numlocks = numlocks;
+}
+
+/*
+ * init_lockdata --- initialize lock data
+ *
+ * We initialize each member of sfex_lockdata structure.
+ */
+void
+init_lockdata (sfex_lockdata * ldata)
+{
+ ldata->status = SFEX_STATUS_UNLOCK;
+ ldata->count = 0;
+ ldata->nodename[0] = 0;
+}
+
+/*
+ * write_controldata --- write control data into file
+ *
+ * We write sfex_controldata struct into file. We open a file with
+ * synchronization mode and write out control data.
+ *
+ * cdata --- pointer of control data
+ *
+ * device --- name of target file
+ */
+void
+write_controldata (const sfex_controldata * cdata)
+{
+ sfex_controldata_ondisk *block;
+ int fd;
+
+ block = (sfex_controldata_ondisk *) (locked_mem);
+
+ /* We write control data into the buffer with given format. */
+ /* We write the offset value of each field of the control data directly.
+ * Because a point using this value is limited to two places, we do not
+ * use macro. If you change the following offset values, you must change
+ * values in the read_controldata() function.
+ */
+ memset (block, 0, cdata->blocksize);
+ memcpy (block->magic, cdata->magic, sizeof (block->magic));
+ snprintf ((char *) (block->version), sizeof (block->version), "%d",
+ cdata->version);
+ snprintf ((char *) (block->revision), sizeof (block->revision), "%d",
+ cdata->revision);
+ snprintf ((char *) (block->blocksize), sizeof (block->blocksize), "%u",
+ (unsigned)cdata->blocksize);
+ snprintf ((char *) (block->numlocks), sizeof (block->numlocks), "%d",
+ cdata->numlocks);
+
+ fd = dev_fd;
+ if (lseek (fd, 0, SEEK_SET) == -1) {
+ cl_log(LOG_ERR, "can't seek file pointer: %s\n",
+ strerror (errno));
+ exit (3);
+ }
+
+ /* write buffer into a file */
+ do {
+ ssize_t s = write (fd, block, cdata->blocksize);
+ if (s == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ cl_log(LOG_ERR, "can't write meta-data: %s\n",
+ strerror (errno));
+ exit (3);
+ }
+ else
+ break;
+ }
+ while (1);
+}
+
+/*
+ * write_lockdata --- write lock data into file
+ *
+ * We write sfex_lockdata into file and seek file pointer to the given
+ * position of lock data.
+ *
+ * cdata --- pointer for control data
+ *
+ * ldata --- pointer for lock data
+ *
+ * device --- file name for write
+ *
+ * index --- index number for lock data. 1 origine.
+ */
+int
+write_lockdata (const sfex_controldata * cdata, const sfex_lockdata * ldata,
+ int index)
+{
+ sfex_lockdata_ondisk *block;
+ int fd;
+
+ block = (sfex_lockdata_ondisk *) locked_mem;
+ /* We write lock data into buffer with given format */
+ /* We write the offset value of each field of the control data directly.
+ * Because a point using this value is limited to two places, we do not
+ * use macro. If you chage the following offset values, you must change
+ * values in the read_lockdata() function.
+ */
+ memset (block, 0, cdata->blocksize);
+ block->status = ldata->status;
+ snprintf ((char *) (block->count), sizeof (block->count), "%d",
+ ldata->count);
+ snprintf ((char *) (block->nodename), sizeof (block->nodename), "%s",
+ ldata->nodename);
+
+ fd = dev_fd;
+
+ /* seek a file pointer to given position */
+ if (lseek (fd, cdata->blocksize * index, SEEK_SET) == -1) {
+ cl_log(LOG_ERR, "can't seek file pointer: %s\n",
+ strerror (errno));
+ return -1;
+ }
+
+ /* write buffer into file */
+ do {
+ ssize_t s = write (fd, block, cdata->blocksize);
+ if (s == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ cl_log(LOG_ERR, "can't write meta-data: %s\n",
+ strerror (errno));
+ return -1;
+ }
+ else if (s != cdata->blocksize) {
+ /* if writing atomically failed, this process is error */
+ cl_log(LOG_ERR, "can't write meta-data atomically.\n");
+ return -1;
+ }
+ break;
+ }
+ while (1);
+ return 0;
+}
+
+/*
+ * read_controldata --- read control data from file
+ *
+ * read sfex_controldata structure from file.
+ *
+ * cdata --- pointer for control data
+ *
+ * device --- file name for reading
+ */
+int
+read_controldata (sfex_controldata * cdata)
+{
+ sfex_controldata_ondisk *block;
+
+ block = (sfex_controldata_ondisk *) (locked_mem);
+
+ if (lseek (dev_fd, 0, SEEK_SET) == -1) {
+ cl_log(LOG_ERR, "can't seek file pointer: %s\n",
+ strerror (errno));
+ return -1;
+ }
+
+ /* read data from file */
+ do {
+ ssize_t s = read (dev_fd, block, sector_size);
+ if (s == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ cl_log(LOG_ERR,
+ "can't read controldata meta-data: %s\n",
+ strerror (errno));
+ return -1;
+ }
+ else
+ break;
+ } while (1);
+
+ /* read control data from buffer */
+ /* 1. check the magic number. 2. check null terminator of each field
+ 3. check the version number. 4. Unmuch of revision number is allowed */
+ /* We write the offset value of each field of the control data directly.
+ * Because a point using this value is limited to two places, we do not
+ * use macro. If you chage the following offset values, you must change
+ * values in the write_controldata() function.
+ */
+ memcpy (cdata->magic, block->magic, 4);
+ if (memcmp (cdata->magic, SFEX_MAGIC, sizeof (cdata->magic))) {
+ cl_log(LOG_ERR, "magic number mismatched. %c%c%c%c <-> %s\n", block->magic[0], block->magic[1], block->magic[2], block->magic[3], SFEX_MAGIC);
+ return -1;
+ }
+ if (block->version[sizeof (block->version)-1]
+ || block->revision[sizeof (block->revision)-1]
+ || block->blocksize[sizeof (block->blocksize)-1]
+ || block->numlocks[sizeof (block->numlocks)-1]) {
+ cl_log(LOG_ERR, "control data format error.\n");
+ return -1;
+ }
+ cdata->version = atoi ((char *) (block->version));
+ if (cdata->version != SFEX_VERSION) {
+ cl_log(LOG_ERR,
+ "version number mismatched. program is %d, data is %d.\n",
+ SFEX_VERSION, cdata->version);
+ return -1;
+ }
+ cdata->revision = atoi ((char *) (block->revision));
+ cdata->blocksize = atoi ((char *) (block->blocksize));
+ cdata->numlocks = atoi ((char *) (block->numlocks));
+
+ return 0;
+}
+
+/*
+ * read_lockdata --- read lock data from file
+ *
+ * read sfex_lockdata from file and seek file pointer to head position of the
+ * file.
+ *
+ * cdata --- pointer for control data
+ *
+ * ldata --- pointer for lock data. Read lock data are stored into this
+ * pointed area.
+ *
+ * device --- file name of source file
+ *
+ * index --- index number. 1 origin.
+ */
+int
+read_lockdata (const sfex_controldata * cdata, sfex_lockdata * ldata,
+ int index)
+{
+ sfex_lockdata_ondisk *block;
+ int fd;
+
+ block = (sfex_lockdata_ondisk *) (locked_mem);
+
+ fd = dev_fd;
+
+ /* seek a file pointer to given position */
+ if (lseek (fd, cdata->blocksize * index, SEEK_SET) == -1) {
+ cl_log(LOG_ERR, "can't seek file pointer: %s\n",
+ strerror (errno));
+ return -1;
+ }
+
+ /* read from file */
+ do {
+ ssize_t s = read (fd, block, cdata->blocksize);
+ if (s == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ cl_log(LOG_ERR, "can't read lockdata meta-data: %s\n",
+ strerror (errno));
+ return -1;
+ }
+ else if (s != cdata->blocksize) {
+ cl_log(LOG_ERR, "can't read meta-data atomically.\n");
+ return -1;
+ }
+ break;
+ }
+ while (1);
+
+ /* read control data form buffer */
+ /* 1. check null terminator of each field 2. check the status */
+ /* We write the offset value of each field of the control data directly.
+ * Because a point using this value is limited to two places, we do not
+ * use macro. If you chage the following offset values, you must change
+ * values in the write_lockdata() function.
+ */
+ if (block->count[sizeof(block->count)-1] || block->nodename[sizeof(block->nodename)-1]) {
+ cl_log(LOG_ERR, "lock data format error.\n");
+ return -1;
+ }
+ ldata->status = block->status;
+ if (ldata->status != SFEX_STATUS_UNLOCK
+ && ldata->status != SFEX_STATUS_LOCK) {
+ cl_log(LOG_ERR, "lock data format error.\n");
+ return -1;
+ }
+ ldata->count = atoi ((char *) (block->count));
+ strncpy ((char *) (ldata->nodename), (const char *) (block->nodename), sizeof(ldata->nodename));
+
+#ifdef SFEX_DEBUG
+ cl_log(LOG_INFO, "status: %c\n", ldata->status);
+ cl_log(LOG_INFO, "count: %d\n", ldata->count);
+ cl_log(LOG_INFO, "nodename: %s\n", ldata->nodename);
+#endif
+ return 0;
+}
+
+/*
+ * lock_index_check --- check the value of index
+ *
+ * The lock_index_check function checks whether the value of index exceeds
+ * the number of lock data on the shared disk.
+ *
+ * cdata --- pointer for control data
+ *
+ * index --- index number
+ */
+int
+lock_index_check(sfex_controldata * cdata, int index)
+{
+ if (read_controldata(cdata) == -1) {
+ cl_log(LOG_ERR, "%s\n", "read_controldata failed in lock_index_check");
+ return -1;
+ }
+#ifdef SFEX_DEBUG
+ cl_log(LOG_INFO, "version: %d\n", cdata->version);
+ cl_log(LOG_INFO, "revision: %d\n", cdata->revision);
+ cl_log(LOG_INFO, "blocksize: %d\n", cdata->blocksize);
+ cl_log(LOG_INFO, "numlocks: %d\n", cdata->numlocks);
+#endif
+
+ if (index > cdata->numlocks) {
+ cl_log(LOG_ERR, "index %d is too large. %d locks are stored.\n",
+ index, cdata->numlocks);
+ return -1;
+ }
+
+ if (cdata->blocksize != sector_size) {
+ cl_log(LOG_ERR, "sector_size is not the same as the blocksize.\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/tools/sfex_lib.h b/tools/sfex_lib.h
new file mode 100644
index 0000000..f71281f
--- /dev/null
+++ b/tools/sfex_lib.h
@@ -0,0 +1,42 @@
+/*-------------------------------------------------------------------------
+ *
+ * Shared Disk File EXclusiveness Control Program(SF-EX)
+ *
+ * sfex_lib.h --- Prototypes for lib.c.
+ *
+ * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------*/
+
+#ifndef LIB_H
+#define LIB_H
+
+const char *get_progname(const char *argv0);
+char *get_nodename(void);
+void init_controldata(sfex_controldata *cdata, size_t blocksize, int numlocks);
+void init_lockdata(sfex_lockdata *ldata);
+void write_controldata(const sfex_controldata *cdata);
+int write_lockdata(const sfex_controldata *cdata, const sfex_lockdata *ldata, int index);
+int read_controldata(sfex_controldata *cdata);
+int read_lockdata(const sfex_controldata *cdata, sfex_lockdata *ldata, int index);
+int prepare_lock(const char *device);
+int lock_index_check(sfex_controldata * cdata, int index);
+
+#endif /* LIB_H */
diff --git a/tools/sfex_stat.c b/tools/sfex_stat.c
new file mode 100644
index 0000000..8be679c
--- /dev/null
+++ b/tools/sfex_stat.c
@@ -0,0 +1,218 @@
+/*-------------------------------------------------------------------------
+ *
+ * Shared Disk File EXclusiveness Control Program(SF-EX)
+ *
+ * sfex_stat.c --- Display lock status. This is a part of the SF-EX.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * Copyright (c) 2007 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------
+ *
+ * sfex_stat [-i <index>] <device>
+ *
+ * -i <index> --- The index is number of the resource that display the lock.
+ * This number is specified by the integer of one or more. When two or more
+ * resources are exclusively controlled by one meta-data, this option is used.
+ * Default is 1.
+ *
+ * <device> --- This is file path which stored meta-data. It is usually
+ * expressed in "/dev/...", because it is partition on the shared disk.
+ *
+ * exit code --- 0 - Normal end. Own node is holding lock. 2 - Normal
+ * end. Own node does not hold a lock. 3 - Error occurs while processing
+ * it. The content of the error is displayed into stderr. 4 - The mistake
+ * is found in the command line parameter.
+ *
+ *-------------------------------------------------------------------------*/
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "sfex.h"
+#include "sfex_lib.h"
+
+const char *progname;
+char *nodename;
+
+void print_controldata(const sfex_controldata *cdata);
+void print_lockdata(const sfex_lockdata *ldata, int index);
+
+/*
+ * print_controldata --- print sfex control data to the display
+ *
+ * print sfex_controldata to the display.
+ *
+ * cdata --- pointer for control data
+ */
+void
+print_controldata(const sfex_controldata *cdata)
+{
+ printf("control data:\n");
+ printf(" magic: 0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
+ cdata->magic[0], cdata->magic[1], cdata->magic[2], cdata->magic[3]);
+ printf(" version: %d\n", cdata->version);
+ printf(" revision: %d\n", cdata->revision);
+ printf(" blocksize: %d\n", (int)cdata->blocksize);
+ printf(" numlocks: %d\n", cdata->numlocks);
+}
+
+/*
+ * print_lockdata --- print sfex lock data to the display
+ *
+ * print sfex_lockdata to the display.
+ *
+ * ldata --- pointer for lock data
+ *
+ * index --- index number
+ */
+void
+print_lockdata(const sfex_lockdata *ldata, int index)
+{
+ printf("lock data #%d:\n", index);
+ printf(" status: %s\n", ldata->status == SFEX_STATUS_UNLOCK ? "unlock" : "lock");
+ printf(" count: %d\n", ldata->count);
+ printf(" nodename: %s\n",ldata->nodename);
+}
+
+/*
+ * usage --- display command line syntax
+ *
+ * display command line syntax. By the purpose, it can specify destination
+ * stream. stdout or stderr is set usually.
+ *
+ * dist --- destination stream of the command line syntax, such as stderr.
+ *
+ * retrun value --- void
+ */
+static void usage(FILE *dist) {
+ fprintf(dist, "usage: %s [-i <index>] <device>\n", progname);
+}
+
+/*
+ * main --- main function
+ *
+ * entry point of sfex_stat command.
+ *
+ * exit code --- 0 - Normal end. Own node is holding lock. 2 - Normal
+ * end. Own node dose not hold a lock. 3 - Error occurs while processing
+ * it. The content of the error is displayed into stderr. 4 - The mistake
+ * is found in the command line parameter.
+ */
+int
+main(int argc, char *argv[]) {
+ sfex_controldata cdata;
+ sfex_lockdata ldata;
+ int ret = 0;
+
+ /* command line parameter */
+ int index = 1; /* default 1st lock */
+ const char *device;
+
+ /*
+ * startup process
+ */
+
+ /* get a program name */
+ progname = get_progname(argv[0]);
+
+ /* enable the cl_log output from the sfex library */
+ cl_log_set_entity(progname);
+ /* The cl_log is output only to the standard error output */
+ cl_log_enable_stderr(TRUE);
+
+ /* read command line option */
+ opterr = 0;
+ while (1) {
+ int c = getopt(argc, argv, "hi:");
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h': /* help */
+ usage(stdout);
+ exit(0);
+ case 'i': /* -i <index> */
+ {
+ unsigned long l = strtoul(optarg, NULL, 10);
+ if (l < SFEX_MIN_NUMLOCKS || l > SFEX_MAX_NUMLOCKS) {
+ fprintf(stderr,
+ "%s: ERROR: index %s is out of range or invalid. it must be integer value between %lu and %lu.\n",
+ progname, optarg,
+ (unsigned long)SFEX_MIN_NUMLOCKS,
+ (unsigned long)SFEX_MAX_NUMLOCKS);
+ exit(4);
+ }
+ index = l;
+ }
+ break;
+ case '?': /* error */
+ usage(stderr);
+ exit(4);
+ }
+ }
+
+ /* check parameter except the option */
+ if (optind >= argc) {
+ fprintf(stderr, "%s: ERROR: no device specified.\n", progname);
+ usage(stderr);
+ exit(4);
+ } else if (optind + 1 < argc) {
+ fprintf(stderr, "%s: ERROR: too many arguments.\n", progname);
+ usage(stderr);
+ exit(4);
+ }
+ device = argv[optind];
+
+ /*
+ * main processes start
+ */
+
+ /* get a node name */
+ nodename = get_nodename();
+
+ prepare_lock(device);
+
+ ret = lock_index_check(&cdata, index);
+ if (ret == -1)
+ exit(EXIT_FAILURE);
+
+ /* read lock data */
+ read_lockdata(&cdata, &ldata, index);
+
+ /* display status */
+ print_controldata(&cdata);
+ print_lockdata(&ldata, index);
+
+ /* check current lock status */
+ if (ldata.status != SFEX_STATUS_LOCK || strcmp(ldata.nodename, nodename)) {
+ fprintf(stdout, "status is UNLOCKED.\n");
+ exit(2);
+ } else {
+ fprintf(stdout, "status is LOCKED.\n");
+ exit(0);
+ }
+}
diff --git a/tools/storage_mon.c b/tools/storage_mon.c
new file mode 100644
index 0000000..1aae29e
--- /dev/null
+++ b/tools/storage_mon.c
@@ -0,0 +1,891 @@
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#ifdef __FreeBSD__
+#include <sys/disk.h>
+#endif
+#include <config.h>
+#include <glib.h>
+#include <libgen.h>
+
+#include <qb/qbdefs.h>
+#include <qb/qblog.h>
+#include <qb/qbloop.h>
+#include <qb/qbutil.h>
+#include <qb/qbipcs.h>
+#include <qb/qbipcc.h>
+
+#define MAX_DEVICES 25
+#define DEFAULT_TIMEOUT 10
+#define DEFAULT_INTERVAL 30
+#define DEFAULT_PIDFILE HA_VARRUNDIR "storage_mon.pid"
+#define DEFAULT_ATTRNAME "#health-storage_mon"
+#define SMON_GET_RESULT_COMMAND "get_check_value"
+#define SMON_RESULT_OK "green"
+#define SMON_RESULT_NG "red"
+#define SMON_RESULT_COMMAND_ERROR "unknown command"
+#define SMON_BUFF_1MEG 1048576
+#define SMON_MAX_IPCSNAME 256
+#define SMON_MAX_MSGSIZE 128
+#define SMON_MAX_RESP_SIZE 100
+
+#define PRINT_STORAGE_MON_ERR(fmt, ...) if (!daemonize) { \
+ fprintf(stderr, fmt"\n", __VA_ARGS__); \
+ } else { \
+ syslog(LOG_ERR, fmt, __VA_ARGS__); \
+ }
+#define PRINT_STORAGE_MON_ERR_NOARGS(str) if (!daemonize) { \
+ fprintf(stderr, str"\n"); \
+ } else { \
+ syslog(LOG_ERR, str); \
+ }
+
+#define PRINT_STORAGE_MON_INFO(fmt, ...) if (!daemonize) { \
+ printf(fmt"\n", __VA_ARGS__); \
+ } else { \
+ syslog(LOG_INFO, fmt, __VA_ARGS__); \
+ }
+
+struct storage_mon_timer_data {
+ int interval;
+};
+
+struct storage_mon_check_value_req {
+ struct qb_ipc_request_header hdr;
+ char message[SMON_MAX_MSGSIZE];
+};
+
+
+struct storage_mon_check_value_res {
+ struct qb_ipc_response_header hdr;
+ char message[SMON_MAX_MSGSIZE];
+};
+
+
+char *devices[MAX_DEVICES];
+int scores[MAX_DEVICES];
+size_t device_count = 0;
+int timeout = DEFAULT_TIMEOUT;
+int verbose = 0;
+int inject_error_percent = 0;
+const char *attrname = DEFAULT_ATTRNAME;
+gboolean daemonize = FALSE;
+int shutting_down = FALSE;
+static qb_ipcs_service_t *ipcs;
+int final_score = 0;
+int response_final_score = 0;
+pid_t test_forks[MAX_DEVICES];
+size_t finished_count = 0;
+gboolean daemon_check_first_all_devices = FALSE;
+
+static qb_loop_t *storage_mon_poll_handle;
+static qb_loop_timer_handle timer_handle;
+static qb_loop_timer_handle expire_handle;
+static struct storage_mon_timer_data timer_d;
+
+static int test_device_main(gpointer data);
+static void wrap_test_device_main(void *data);
+
+static void usage(char *name, FILE *f)
+{
+ fprintf(f, "usage: %s [-hv] [-d <device>]... [-s <score>]... [-t <secs>]\n", name);
+ fprintf(f, " --device <dev> device to test, up to %d instances\n", MAX_DEVICES);
+ fprintf(f, " --score <n> score if device fails the test. Must match --device count\n");
+ fprintf(f, " --timeout <n> max time to wait for a device test to come back. in seconds (default %d)\n", DEFAULT_TIMEOUT);
+ fprintf(f, " --inject-errors-percent <n> Generate EIO errors <n>%% of the time (for testing only)\n");
+ fprintf(f, " --daemonize test run in daemons.\n");
+ fprintf(f, " --client client connection to daemon. requires the attrname option.\n");
+ fprintf(f, " --interval <n> interval to test. in seconds (default %d)(for daemonize only)\n", DEFAULT_INTERVAL);
+ fprintf(f, " --pidfile <path> file path to record pid (default %s)(for daemonize only)\n", DEFAULT_PIDFILE);
+ fprintf(f, " --attrname <attr> attribute name to update test result (default %s)(for daemonize/client only)\n", DEFAULT_ATTRNAME);
+ fprintf(f, " --verbose emit extra output to stdout\n");
+ fprintf(f, " --help print this message\n");
+}
+
+/* Check one device */
+static void *test_device(const char *device, int verbose, int inject_error_percent)
+{
+ uint64_t devsize;
+ int flags = O_RDONLY | O_DIRECT;
+ int device_fd;
+ int res;
+ off_t seek_spot;
+
+ if (verbose) {
+ printf("Testing device %s\n", device);
+ }
+
+ device_fd = open(device, flags);
+ if (device_fd < 0) {
+ if (errno != EINVAL) {
+ PRINT_STORAGE_MON_ERR("Failed to open %s: %s", device, strerror(errno));
+ exit(-1);
+ }
+ flags &= ~O_DIRECT;
+ device_fd = open(device, flags);
+ if (device_fd < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to open %s: %s", device, strerror(errno));
+ exit(-1);
+ }
+ }
+#ifdef __FreeBSD__
+ res = ioctl(device_fd, DIOCGMEDIASIZE, &devsize);
+#else
+ res = ioctl(device_fd, BLKGETSIZE64, &devsize);
+#endif
+ if (res < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to get device size for %s: %s", device, strerror(errno));
+ goto error;
+ }
+ if (verbose) {
+ PRINT_STORAGE_MON_INFO("%s: opened %s O_DIRECT, size=%zu", device, (flags & O_DIRECT)?"with":"without", devsize);
+ }
+
+ /* Don't fret about real randomness */
+ srand(time(NULL) + getpid());
+ /* Pick a random place on the device - sector aligned */
+ seek_spot = (rand() % (devsize-1024)) & 0xFFFFFFFFFFFFFE00;
+ res = lseek(device_fd, seek_spot, SEEK_SET);
+ if (res < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to seek %s: %s", device, strerror(errno));
+ goto error;
+ }
+ if (verbose) {
+ PRINT_STORAGE_MON_INFO("%s: reading from pos %ld", device, seek_spot);
+ }
+
+ if (flags & O_DIRECT) {
+ int sec_size = 0;
+ void *buffer;
+
+#ifdef __FreeBSD__
+ res = ioctl(device_fd, DIOCGSECTORSIZE, &sec_size);
+#else
+ res = ioctl(device_fd, BLKSSZGET, &sec_size);
+#endif
+ if (res < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to get block device sector size for %s: %s", device, strerror(errno));
+ goto error;
+ }
+
+ if (posix_memalign(&buffer, sysconf(_SC_PAGESIZE), sec_size) != 0) {
+ PRINT_STORAGE_MON_ERR("Failed to allocate aligned memory: %s", strerror(errno));
+ goto error;
+ }
+ res = read(device_fd, buffer, sec_size);
+ free(buffer);
+ if (res < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to read %s: %s", device, strerror(errno));
+ goto error;
+ }
+ if (res < sec_size) {
+ PRINT_STORAGE_MON_ERR("Failed to read %d bytes from %s, got %d", sec_size, device, res);
+ goto error;
+ }
+ } else {
+ char buffer[512];
+
+ res = read(device_fd, buffer, sizeof(buffer));
+ if (res < 0) {
+ PRINT_STORAGE_MON_ERR("Failed to read %s: %s", device, strerror(errno));
+ goto error;
+ }
+ if (res < (int)sizeof(buffer)) {
+ PRINT_STORAGE_MON_ERR("Failed to read %ld bytes from %s, got %d", sizeof(buffer), device, res);
+ goto error;
+ }
+ }
+
+ /* Fake an error */
+ if (inject_error_percent && ((rand() % 100) < inject_error_percent)) {
+ PRINT_STORAGE_MON_ERR_NOARGS("People, please fasten your seatbelts, injecting errors!");
+ goto error;
+ }
+ res = close(device_fd);
+ if (res != 0) {
+ PRINT_STORAGE_MON_ERR("Failed to close %s: %s", device, strerror(errno));
+ exit(-1);
+ }
+
+ if (verbose) {
+ PRINT_STORAGE_MON_INFO("%s: done", device);
+ }
+ exit(0);
+
+error:
+ close(device_fd);
+ exit(-1);
+}
+
+static gboolean is_child_runnning(void)
+{
+ size_t i;
+
+ for (i=0; i<device_count; i++) {
+ if (test_forks[i] != 0) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void stop_child(pid_t pid, int signal)
+{
+ errno = 0;
+
+ if (kill(pid, signal) == 0) {
+ syslog(LOG_DEBUG, "Stopping chilg sent signal %d to process %lld", signal, (long long) pid);
+ } else {
+ syslog(LOG_ERR, "Could not stop child (process %lld) with signal %d: %s", (long long) pid, signal, strerror(errno));
+ }
+}
+
+static int32_t sigterm_handler(int num, void *data)
+{
+ size_t i;
+ shutting_down = TRUE;
+
+ /* If there is an unfired timer, stop it. */
+ qb_loop_timer_del(storage_mon_poll_handle, timer_handle);
+
+ /* Send SIGTERM to non-terminating device monitoring processes. */
+ if (is_child_runnning()) {
+ /* See if threads have finished */
+ for (i=0; i<device_count; i++) {
+ if (test_forks[i] > 0 ) {
+ stop_child(test_forks[i], SIGTERM);
+ }
+ }
+
+ }
+
+ /* Set a timer for termination. */
+ qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_HIGH, 0, NULL, wrap_test_device_main, &timer_handle);
+
+ return 0;
+}
+
+static size_t find_child_pid(int pid)
+{
+ size_t i;
+
+ for (i=0; i<device_count; i++) {
+ if (test_forks[i] > 0 ) {
+ if (test_forks[i] == pid) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+static int32_t sigchld_handler(int32_t sig, void *data)
+{
+ pid_t pid;
+ size_t index;
+ int status;
+
+ if (is_child_runnning()) {
+ while(1) {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid > 0) {
+ if (WIFEXITED(status)) {
+ index = find_child_pid(pid);
+ if (index >= 0) {
+ /* If the expire timer is running, no timeout has occurred, */
+ /* so add the final_score from the exit code of the terminated child process. */
+ if (qb_loop_timer_is_running(storage_mon_poll_handle, expire_handle)) {
+ if (WEXITSTATUS(status) !=0) {
+ final_score += scores[index];
+
+ /* Update response values immediately in preparation for inquiries from clients. */
+ response_final_score = final_score;
+
+ /* Even in the first demon mode check, if there is an error device, clear */
+ /* the flag to return the response to the client without waiting for all devices to finish. */
+ daemon_check_first_all_devices = TRUE;
+ }
+ }
+
+ finished_count++;
+ test_forks[index] = 0;
+
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static void child_shutdown(int nsig)
+{
+ exit(1);
+}
+
+static int write_pid_file(const char *pidfile)
+{
+ char *pid;
+ char *dir, *str = NULL;
+ int fd = -1;
+ int rc = -1;
+ int i, len;
+
+ if (asprintf(&pid, "%jd", (intmax_t)getpid()) < 0) {
+ syslog(LOG_ERR, "Failed to allocate memory to store PID");
+ pid = NULL;
+ goto done;
+ }
+
+ str = strdup(pidfile);
+ if (str == NULL) {
+ syslog(LOG_ERR, "Failed to duplicate string ['%s']", pidfile);
+ goto done;
+ }
+ dir = dirname(str);
+ for (i = 1, len = strlen(dir); i < len; i++) {
+ if (dir[i] == '/') {
+ dir[i] = 0;
+ if ((mkdir(dir, 0640) < 0) && (errno != EEXIST)) {
+ syslog(LOG_ERR, "Failed to create directory %s: %s", dir, strerror(errno));
+ goto done;
+ }
+ dir[i] = '/';
+ }
+ }
+ if ((mkdir(dir, 0640) < 0) && (errno != EEXIST)) {
+ syslog(LOG_ERR, "Failed to create directory %s: %s", dir, strerror(errno));
+ goto done;
+ }
+
+ fd = open(pidfile, O_CREAT | O_WRONLY, 0640);
+ if (fd < 0) {
+ syslog(LOG_ERR, "Failed to open %s: %s", pidfile, strerror(errno));
+ goto done;
+ }
+
+ if (write(fd, pid, strlen(pid)) != strlen(pid)) {
+ syslog(LOG_ERR, "Failed to write '%s' to %s: %s", pid, pidfile, strerror(errno));
+ goto done;
+ }
+ close(fd);
+ rc = 0;
+done:
+ if (pid != NULL) {
+ free(pid);
+ }
+ if (str != NULL) {
+ free(str);
+ }
+ return rc;
+}
+
+static void child_timeout_handler(void *data)
+{
+ size_t i;
+
+ if (is_child_runnning()) {
+ for (i=0; i<device_count; i++) {
+ if (test_forks[i] > 0) {
+ /* If timeout occurs before SIGCHLD, add child process failure score to final_score. */
+ final_score += scores[i];
+
+ /* Update response values immediately in preparation for inquiries from clients. */
+ response_final_score = final_score;
+
+ /* Even in the first demon mode check, if there is an error device, clear */
+ /* the flag to return the response to the client without waiting for all devices to finish. */
+ daemon_check_first_all_devices = TRUE;
+ }
+ }
+ }
+}
+
+static void wrap_test_device_main(void *data)
+{
+ struct storage_mon_timer_data *timer_data = (struct storage_mon_timer_data*)data;
+ test_device_main((timer_data != NULL) ? &timer_data->interval : NULL);
+}
+
+static int test_device_main(gpointer data)
+{
+ size_t i;
+ struct timespec ts;
+ time_t start_time;
+ gboolean device_check = TRUE;
+
+ if (daemonize) {
+ if (shutting_down == TRUE) {
+ goto done;
+ }
+
+ /* In the case of daemon mode, it is avoided that the timer is triggered and the number of */
+ /* child processes increases while the device monitoring child process is not completed. */
+ if (is_child_runnning()) {
+ device_check = FALSE;
+ }
+
+ if (device_count == finished_count && device_check) {
+ /* Update the result value for the client response once all checks have completed. */
+ response_final_score = final_score;
+
+ if (!daemon_check_first_all_devices) {
+ daemon_check_first_all_devices = TRUE;
+ }
+ }
+ }
+
+ if (device_check) {
+ /* Reset final_score, finished_count, test_forks[] */
+ final_score = 0;
+ finished_count = 0;
+
+ memset(test_forks, 0, sizeof(test_forks));
+ for (i=0; i<device_count; i++) {
+ test_forks[i] = fork();
+ if (test_forks[i] < 0) {
+ PRINT_STORAGE_MON_ERR("Error spawning fork for %s: %s\n", devices[i], strerror(errno));
+ /* Just test the devices we have */
+ break;
+ }
+ /* child */
+ if (test_forks[i] == 0) {
+ if (daemonize) {
+ signal(SIGTERM, &child_shutdown);
+ }
+ test_device(devices[i], verbose, inject_error_percent);
+ }
+ }
+
+ if (!daemonize) {
+ /* See if they have finished */
+ clock_gettime(CLOCK_REALTIME, &ts);
+ start_time = ts.tv_sec;
+
+ while ((finished_count < device_count) && ((start_time + timeout) > ts.tv_sec)) {
+ for (i=0; i<device_count; i++) {
+ int wstatus;
+ pid_t w;
+
+ if (test_forks[i] > 0) {
+ w = waitpid(test_forks[i], &wstatus, WUNTRACED | WNOHANG | WCONTINUED);
+ if (w < 0) {
+ PRINT_STORAGE_MON_ERR("waitpid on %s failed: %s", devices[i], strerror(errno));
+ return -1;
+ }
+
+ if (w == test_forks[i]) {
+ if (WIFEXITED(wstatus)) {
+ if (WEXITSTATUS(wstatus) != 0) {
+ syslog(LOG_ERR, "Error reading from device %s", devices[i]);
+ final_score += scores[i];
+ }
+
+ finished_count++;
+ test_forks[i] = 0;
+ }
+ }
+ }
+ }
+
+ usleep(100000);
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ }
+
+ /* See which threads have not finished */
+ for (i=0; i<device_count; i++) {
+ if (test_forks[i] != 0) {
+ syslog(LOG_ERR, "Reading from device %s did not complete in %d seconds timeout", devices[i], timeout);
+ fprintf(stderr, "Thread for device %s did not complete in time\n", devices[i]);
+ final_score += scores[i];
+ }
+ }
+ } else {
+ /* Run the child process timeout watch timer. */
+ qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, timeout * QB_TIME_NS_IN_SEC, NULL, child_timeout_handler, &expire_handle);
+ }
+ }
+ if (!daemonize) {
+ if (verbose) {
+ printf("Final score is %d\n", final_score);
+ }
+ return final_score;
+ } else {
+ if (data != NULL) {
+ /* Sets the device check to run on the next timer. */
+ qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, timer_d.interval * QB_TIME_NS_IN_SEC, &timer_d, wrap_test_device_main, &timer_handle);
+ }
+ return TRUE;
+ }
+done:
+ qb_loop_stop(storage_mon_poll_handle);
+ return FALSE;
+}
+
+static int32_t
+storage_mon_job_add(enum qb_loop_priority p, void *data, qb_loop_job_dispatch_fn fn)
+{
+ return qb_loop_job_add(storage_mon_poll_handle, p, data, fn);
+}
+
+static int32_t
+storage_mon_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts,
+ void *data, qb_ipcs_dispatch_fn_t fn)
+{
+ return qb_loop_poll_add(storage_mon_poll_handle, p, fd, evts, data, fn);
+}
+
+static int32_t
+storage_mon_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts,
+ void *data, qb_ipcs_dispatch_fn_t fn)
+{
+ return qb_loop_poll_mod(storage_mon_poll_handle, p, fd, evts, data, fn);
+}
+
+static int32_t
+storage_mon_dispatch_del(int32_t fd)
+{
+ return qb_loop_poll_del(storage_mon_poll_handle, fd);
+}
+
+static int32_t
+storage_mon_ipcs_connection_accept_fn(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
+{
+ return 0;
+}
+
+static void
+storage_mon_ipcs_connection_created_fn(qb_ipcs_connection_t *c)
+{
+ struct qb_ipcs_stats srv_stats;
+
+ qb_ipcs_stats_get(ipcs, &srv_stats, QB_FALSE);
+ syslog(LOG_DEBUG, "Connection created (active:%d, closed:%d)",
+ srv_stats.active_connections, srv_stats.closed_connections);
+}
+
+static void
+storage_mon_ipcs_connection_destroyed_fn(qb_ipcs_connection_t *c)
+{
+ syslog(LOG_DEBUG, "Connection about to be freed");
+}
+
+static int32_t
+storage_mon_ipcs_connection_closed_fn(qb_ipcs_connection_t *c)
+{
+ struct qb_ipcs_connection_stats stats;
+ struct qb_ipcs_stats srv_stats;
+
+ qb_ipcs_stats_get(ipcs, &srv_stats, QB_FALSE);
+ qb_ipcs_connection_stats_get(c, &stats, QB_FALSE);
+
+ syslog(LOG_DEBUG,
+ "Connection to pid:%d destroyed (active:%d, closed:%d)",
+ stats.client_pid, srv_stats.active_connections,
+ srv_stats.closed_connections);
+
+ return 0;
+}
+
+static int32_t
+storage_mon_ipcs_msg_process_fn(qb_ipcs_connection_t *c, void *data, size_t size)
+{
+ struct storage_mon_check_value_req *request;
+ struct qb_ipc_response_header resps;
+ ssize_t res;
+ struct iovec iov[2];
+ char resp[SMON_MAX_RESP_SIZE];
+ int32_t rc;
+ int send_score = response_final_score;
+
+ request = (struct storage_mon_check_value_req *)data;
+ syslog(LOG_DEBUG, "msg received (id:%d, size:%d, data:%s)",
+ request->hdr.id, request->hdr.size, request->message);
+
+ if (strcmp(request->message, SMON_GET_RESULT_COMMAND) != 0) {
+ syslog(LOG_DEBUG, "request command is unknown.");
+ send_score = -1;
+ } else if (!daemon_check_first_all_devices) {
+ send_score = -2;
+ }
+
+ resps.size = sizeof(struct qb_ipc_response_header);
+ resps.id = 13;
+ resps.error = 0;
+
+ rc = snprintf(resp, SMON_MAX_RESP_SIZE, "%d", send_score) + 1;
+ iov[0].iov_len = sizeof(resps);
+ iov[0].iov_base = &resps;
+ iov[1].iov_len = rc;
+ iov[1].iov_base = resp;
+ resps.size += rc;
+
+ res = qb_ipcs_response_sendv(c, iov, 2);
+ if (res < 0) {
+ errno = -res;
+ syslog(LOG_ERR, "qb_ipcs_response_send : errno = %d", errno);
+ }
+ return 0;
+}
+
+static int32_t
+storage_mon_client(void)
+{
+ struct storage_mon_check_value_req request;
+ struct storage_mon_check_value_res response;
+ qb_ipcc_connection_t *conn;
+ char ipcs_name[SMON_MAX_IPCSNAME];
+ int32_t rc;
+
+
+ snprintf(ipcs_name, SMON_MAX_IPCSNAME, "storage_mon_%s", attrname);
+ conn = qb_ipcc_connect(ipcs_name, 0);
+ if (conn == NULL) {
+ syslog(LOG_ERR, "qb_ipcc_connect error\n");
+ return(-1);
+ }
+
+ snprintf(request.message, SMON_MAX_MSGSIZE, "%s", SMON_GET_RESULT_COMMAND);
+ request.hdr.id = 0;
+ request.hdr.size = sizeof(struct storage_mon_check_value_req);
+ rc = qb_ipcc_send(conn, &request, request.hdr.size);
+ if (rc < 0) {
+ syslog(LOG_ERR, "qb_ipcc_send error : %d\n", rc);
+ return(-1);
+ }
+ if (rc > 0) {
+ rc = qb_ipcc_recv(conn, &response, sizeof(response), -1);
+ if (rc < 0) {
+ syslog(LOG_ERR, "qb_ipcc_recv error : %d\n", rc);
+ return(-1);
+ }
+ }
+
+ qb_ipcc_disconnect(conn);
+
+ /* Set score to result */
+ /* 0 : Normal. */
+ /* greater than 0 : monitoring error. */
+ /* -1 : communication system error. */
+ /* -2 : Not all checks completed for first device in daemon mode. */
+ rc = atoi(response.message);
+
+ syslog(LOG_DEBUG, "daemon response[%d]: %s \n", response.hdr.id, response.message);
+
+ return(rc);
+}
+
+static int32_t
+storage_mon_daemon(int interval, const char *pidfile)
+{
+ int32_t rc;
+ char ipcs_name[SMON_MAX_IPCSNAME];
+
+ struct qb_ipcs_service_handlers service_handle = {
+ .connection_accept = storage_mon_ipcs_connection_accept_fn,
+ .connection_created = storage_mon_ipcs_connection_created_fn,
+ .msg_process = storage_mon_ipcs_msg_process_fn,
+ .connection_destroyed = storage_mon_ipcs_connection_destroyed_fn,
+ .connection_closed = storage_mon_ipcs_connection_closed_fn,
+ };
+
+ struct qb_ipcs_poll_handlers poll_handle = {
+ .job_add = storage_mon_job_add,
+ .dispatch_add = storage_mon_dispatch_add,
+ .dispatch_mod = storage_mon_dispatch_mod,
+ .dispatch_del = storage_mon_dispatch_del,
+ };
+
+ if (daemon(0, 0) < 0) {
+ syslog(LOG_ERR, "Failed to daemonize: %s", strerror(errno));
+ return -1;
+ }
+
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+
+ if (write_pid_file(pidfile) < 0) {
+ return -1;
+ }
+
+ snprintf(ipcs_name, SMON_MAX_IPCSNAME, "storage_mon_%s", attrname);
+ ipcs = qb_ipcs_create(ipcs_name, 0, QB_IPC_NATIVE, &service_handle);
+ if (ipcs == 0) {
+ syslog(LOG_ERR, "qb_ipcs_create");
+ return -1;
+ }
+
+ qb_ipcs_enforce_buffer_size(ipcs, SMON_BUFF_1MEG);
+
+ storage_mon_poll_handle = qb_loop_create();
+
+ qb_ipcs_poll_handlers_set(ipcs, &poll_handle);
+ rc = qb_ipcs_run(ipcs);
+ if (rc != 0) {
+ errno = -rc;
+ syslog(LOG_ERR, "qb_ipcs_run");
+ return -1;
+ }
+
+ qb_loop_signal_add(storage_mon_poll_handle, QB_LOOP_HIGH,
+ SIGTERM, NULL, sigterm_handler, NULL);
+
+ qb_loop_signal_add(storage_mon_poll_handle, QB_LOOP_MED,
+ SIGCHLD, NULL, sigchld_handler, NULL);
+
+ timer_d.interval = interval;
+ qb_loop_timer_add(storage_mon_poll_handle, QB_LOOP_MED, 0, &timer_d, wrap_test_device_main, &timer_handle);
+
+ qb_loop_run(storage_mon_poll_handle);
+ qb_loop_destroy(storage_mon_poll_handle);
+
+ unlink(pidfile);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ size_t score_count = 0;
+ int opt, option_index;
+ int interval = DEFAULT_INTERVAL;
+ const char *pidfile = DEFAULT_PIDFILE;
+ gboolean client = FALSE;
+ struct option long_options[] = {
+ {"timeout", required_argument, 0, 't' },
+ {"device", required_argument, 0, 'd' },
+ {"score", required_argument, 0, 's' },
+ {"inject-errors-percent", required_argument, 0, 0 },
+ {"daemonize", no_argument, 0, 0 },
+ {"client", no_argument, 0, 0 },
+ {"interval", required_argument, 0, 'i' },
+ {"pidfile", required_argument, 0, 'p' },
+ {"attrname", required_argument, 0, 'a' },
+ {"verbose", no_argument, 0, 'v' },
+ {"help", no_argument, 0, 'h' },
+ {0, 0, 0, 0 }
+ };
+
+ while ( (opt = getopt_long(argc, argv, "hvt:d:s:i:p:a:",
+ long_options, &option_index)) != -1 ) {
+ switch (opt) {
+ case 0: /* Long-only options */
+ if (strcmp(long_options[option_index].name, "inject-errors-percent") == 0) {
+ inject_error_percent = atoi(optarg);
+ if (inject_error_percent < 1 || inject_error_percent > 100) {
+ fprintf(stderr, "inject_error_percent should be between 1 and 100\n");
+ return -1;
+ }
+ }
+ if (strcmp(long_options[option_index].name, "daemonize") == 0) {
+ daemonize = TRUE;
+ }
+ if (strcmp(long_options[option_index].name, "client") == 0) {
+ client = TRUE;
+ }
+ if (daemonize && client) {
+ fprintf(stderr,"The daemonize option and client option cannot be specified at the same time.");
+ return -1;
+ }
+ break;
+ case 'd':
+ if (device_count < MAX_DEVICES) {
+ devices[device_count++] = strdup(optarg);
+ } else {
+ fprintf(stderr, "too many devices, max is %d\n", MAX_DEVICES);
+ return -1;
+ }
+ break;
+ case 's':
+ if (score_count < MAX_DEVICES) {
+ int score = atoi(optarg);
+ if (score < 1 || score > 10) {
+ fprintf(stderr, "Score must be between 1 and 10 inclusive\n");
+ return -1;
+ }
+ scores[score_count++] = score;
+ } else {
+ fprintf(stderr, "too many scores, max is %d\n", MAX_DEVICES);
+ return -1;
+ }
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 't':
+ timeout = atoi(optarg);
+ if (timeout < 1) {
+ fprintf(stderr, "invalid timeout %d. Min 1, recommended %d (default)\n", timeout, DEFAULT_TIMEOUT);
+ return -1;
+ }
+ break;
+ case 'h':
+ usage(argv[0], stdout);
+ return 0;
+ break;
+ case 'i':
+ interval = atoi(optarg);
+ if (interval < 1) {
+ fprintf(stderr, "invalid interval %d. Min 1, default is %d\n", interval, DEFAULT_INTERVAL);
+ return -1;
+ }
+ break;
+ case 'p':
+ pidfile = strdup(optarg);
+ if (pidfile == NULL) {
+ fprintf(stderr, "Failed to duplicate string ['%s']\n", optarg);
+ return -1;
+ }
+ break;
+ case 'a':
+ attrname = strdup(optarg);
+ if (attrname == NULL) {
+ fprintf(stderr, "Failed to duplicate string ['%s']\n", optarg);
+ return -1;
+ }
+ break;
+ default:
+ usage(argv[0], stderr);
+ return -1;
+ break;
+ }
+
+ }
+
+ if (client) {
+ return(storage_mon_client());
+ }
+
+ if (device_count == 0) {
+ fprintf(stderr, "No devices to test, use the -d or --device argument\n");
+ return -1;
+ }
+
+ if (device_count != score_count) {
+ fprintf(stderr, "There must be the same number of devices and scores\n");
+ return -1;
+ }
+
+ openlog("storage_mon", 0, LOG_DAEMON);
+
+ if (!daemonize) {
+ final_score = test_device_main(NULL);
+ } else {
+ return(storage_mon_daemon(interval, pidfile));
+ }
+ return final_score;
+}
diff --git a/tools/test-findif.sh b/tools/test-findif.sh
new file mode 100755
index 0000000..1eb9d5d
--- /dev/null
+++ b/tools/test-findif.sh
@@ -0,0 +1,352 @@
+#!/bin/sh
+
+# Easy peasy test for findif, configuration via direct edit...
+# Jan Pokorny <jpokorny@redhat.com>
+
+export LC_ALL=C
+test -n "$BASH_VERSION" && set -o posix
+set -u
+COLOR=0
+if [ -t 1 ] && echo -e foo | grep -Eqv "^-e"; then
+ COLOR=1
+else
+ COLOR=0
+fi
+ok () {
+ [ $COLOR -eq 1 ] \
+ && echo -en "[\033[32m OK \033[0m]" \
+ || echo -n "[ OK ]"
+ echo " $*"
+}
+fail () {
+ [ $COLOR -eq 1 ] \
+ && echo -en "[\033[31mFAIL\033[0m]" \
+ || echo -n "[FAIL]"
+ echo " $*"
+}
+info () {
+ [ $COLOR -eq 1 ] \
+ && echo -e "\033[34m$@\033[0m" \
+ || echo "$*"
+}
+die() { echo "$*"; exit 255; }
+warn() { echo "> $*"; }
+mimic_return () { return $1; }
+verbosely () { echo "$1..."; $1; }
+
+HERE="$(dirname "$0")"
+. "${HERE}/../heartbeat/ocf-returncodes" # obtain OCF_ERR_CONFIGURED et al.
+
+#
+# soft-config
+#
+
+: ${DEBUG_IN:=0}
+: ${DEBUG_OUT:=0}
+
+: "${PRG:=${HERE}/findif}"
+: "${SCRIPT:=${HERE}/../heartbeat/findif.sh}"
+
+: ${LO_IF:=lo}
+: ${LO_IP4:=127.0.0.1}
+: ${LO_NM4:=8}
+: ${LO_BC4:=127.255.255.255}
+: ${LO_IP6:=::1}
+
+: ${DUMMY_IF:=dummy0}
+# carefully selected to fit TEST-NET-2
+# http://en.wikipedia.org/wiki/Reserved_IP_addresses#Reserved_IPv4_addresses
+: ${DUMMY_IP4:=198.51.100.1}
+: ${DUMMY_NM4:=24}
+: ${DUMMY_BC4:=198.51.100.255}
+
+: ${DUMMY_IP6:=2001:db8::1}
+: ${DUMMY_NM6:=32}
+: ${DUMMY_BC6:=198.51.100.255}
+
+#
+# hard-wired
+#
+
+PRG_CMD="${PRG} -C"
+SCRIPT_CMD="$(head -n1 "${SCRIPT}" | sed 's|#!||') \
+ -c \"export OCF_FUNCTIONS_DIR=$(dirname "${SCRIPT}"); . $(dirname "${SCRIPT}")/ocf-shellfuncs; . ${SCRIPT}; findif\""
+
+DUMMY_USER=test-findif
+
+LO_IP4_13=${LO_IP4%.[0-9]*}
+LO_IP4_4=${LO_IP4##[0-9][0-9]*.}
+LO_IP4_INC=${LO_IP4_13}.$((LO_IP4_4 + 1))
+
+DUMMY_IP4_13=${DUMMY_IP4%.[0-9]*}
+DUMMY_IP4_4=${DUMMY_IP4##[0-9][0-9]*.}
+DUMMY_IP4_INC=${DUMMY_IP4_13}.$((DUMMY_IP4_4 + 1))
+
+DUMMY_IP6_17=${DUMMY_IP6%:[0-9A-Fa-f:][0-9A-Fa-f:]*}
+DUMMY_IP6_8=${DUMMY_IP6##[0-9A-Fa-f][0-9A-Fa-f]*:}
+DUMMY_IP6_INC=${DUMMY_IP6_17}::$((DUMMY_IP6_8 + 1))
+
+#
+# command-line config
+#
+
+CMD="${PRG_CMD}"
+TESTS="4"
+
+#
+# test declarations
+#
+
+TEST_FORMAT=\
+" OCF_RESKEY_ip , OCF_RESKEY_cidr_netmask , expected_ec , expected_dev , expected_nm , expected_bc"
+
+TEST_DATA4=\
+" # valid: 1-9: loopback, 10-19: dummy if; invalid cases: 20-29: ip, 30-39: netmask bits
+ # 1) LO4_IP
+ ${LO_IP4} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} , ${LO_BC4}
+ # 2) LO4_IP+1
+ ${LO_IP4_INC} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} , ${LO_BC4}
+ #
+ # 10) DUMMY4_IP+1
+ ${DUMMY_IP4_INC} , , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM4} , ${DUMMY_BC4}
+ # 11) DUMMY4_IP+1, explicit netmask
+ ${DUMMY_IP4_INC} , ${DUMMY_NM4} , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM4} , ${DUMMY_BC4}
+ #
+ # 20) *invalid* IPv4 (missing last item in quad)
+ ${DUMMY_IP4_13}. , , $OCF_ERR_CONFIGURED , NA , NA , NA
+ # 21) *invalid* IP (random string)
+ foobar , , $OCF_ERR_CONFIGURED , NA , NA , NA
+ #
+ # 30) DUMMY4_IP+1, explicit *invalid* netmask (33)
+ ${DUMMY_IP4_INC} , 33 , $OCF_ERR_CONFIGURED , NA , NA , NA
+ # 31) DUMMY4_IP+1, explicit *invalid* netmask (-1)
+ ${DUMMY_IP4_INC} , -1 , $OCF_ERR_CONFIGURED , NA , NA , NA
+ # 32) DUMMY4_IP+1, explicit *invalid* netmask (random string)
+ ${DUMMY_IP4_INC} , foobar , $OCF_ERR_CONFIGURED , NA , NA , NA
+"
+
+TEST_DATA6=\
+" # valid: A0-A9: loopback, B0-B9: dummy if; invalid cases: C0-C9: ip, D0-D9: netmask bits
+ # A0) LO6_IP
+ ${LO_IP6} , , $OCF_SUCCESS , ${LO_IF} , ${LO_NM4} ,
+ #
+ # B0) DUMMY6_IP+1
+ ${DUMMY_IP6_INC} , , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM6} ,
+ # B1) DUMMY4_IP+1, explicit netmask
+ ${DUMMY_IP6_INC} , ${DUMMY_NM6} , $OCF_SUCCESS , ${DUMMY_IF} , ${DUMMY_NM6} ,
+"
+
+#
+# private routines
+#
+
+_get_test_field () {
+ echo "$1" | cut -s -d, -f$2 | sed 's|^ \?\(.*[^ ]\) \?$|\1|g'
+}
+
+#
+# public routines
+#
+
+setup () {
+ if [ "$(uname -o)" != "GNU/Linux" ]; then
+ die "Only tested with Linux, feel free to edit the condition."
+ fi
+
+ if [ "${CMD}" = "${PRG_CMD}" ]; then
+ [ -x "${PRG}" ] || die "Forgot to compile ${PRG} for me to test?"
+ fi
+
+ if [ $(id -u) -ne 0 ]; then
+ die "Due to (unobtrusive) juggling with routing, run as root."
+ fi
+
+ if ! useradd -M ${DUMMY_USER} 2>/dev/null; then
+ if ! getent passwd ${DUMMY_USER} >/dev/null; then
+ die "Cannot add user ${DUMMY_USER} for testing purposes."
+ fi
+ warn "User ${DUMMY_USER} already exists, be careful not" \
+ " to confirm its removal."
+ fi
+
+ if lsmod | grep -Eq "^dummy[ ]+"; then
+ warn "Looks like you already use dummy network device."
+ else
+ modprobe dummy || die "No dummy kernel module (per name) at hand."
+ fi
+
+ #if ! ifconfig ${DUMMY_IF} ${DUMMY_IP4}/${DUMMY_NM4}; then
+ #if ! ip addr add ${DUMMY_IP4}/${DUMMY_NM4} broadcast + dev ${DUMMY_IF}; then
+ if ! ip addr add ${DUMMY_IP4}/${DUMMY_NM4} dev ${DUMMY_IF}; then
+ die "Cannot add IPv4 address (${DUMMY_IP4}/${DUMMY_NM4}) to ${DUMMY_IF}."
+ fi
+
+ # TODO: IPv6 support check first, disabling 6'ish tests if negative?
+ if ! ip addr add ${DUMMY_IP6}/${DUMMY_NM6} dev ${DUMMY_IF}; then
+ die "Cannot add IPv6 address (${DUMMY_IP6}/${DUMMY_NM6}) to ${DUMMY_IF}."
+ fi
+
+ if ! ip link set ${DUMMY_IF} up; then
+ die "Cannot bring ${DUMMY_IF} up."
+ fi
+}
+
+teardown () {
+ while true; do
+ echo -n "Remove user ${DUMMY_USER}? [y/n] "
+ case $(read l < /dev/tty; echo "${l}") in
+ n)
+ break
+ ;;
+ y)
+ [ "${DUMMY_USER}" = "$(whoami)" ] && break
+ userdel ${DUMMY_USER} || warn "Cannot kick user ${DUMMY_USER} out."
+ break
+ ;;
+ esac
+ done
+
+ rmmod dummy || warn "Cannot kick dummy kernel module out."
+}
+
+proceed () {
+ err_cnt=0
+ (for test in ${TESTS}; do eval "echo \"\${TEST_DATA${test}}\""; done
+ tty 2>&1 >/dev/null || cat | sed 's|^\(.*\)$|# stdin: \1\n\1|') \
+ | while read curline; do
+ if echo "${curline}" | grep -Eqv -e '^[ \t]*#' -e '^$'; then
+ err_this=0
+ curline="$(echo "${curline}" | tr '\t' ' ' | tr -s ' ')"
+ info "$(echo "${lastline}" | sed 's|^#|------|')"
+
+ ip="$( _get_test_field "${curline}" 1)"
+ [ -z "${ip}" ] && die "test spec.: empty ip"
+ mask="$( _get_test_field "${curline}" 2)"
+ [ -z "${mask}" ] && warn "test spec.: empty mask"
+ exp_ec="$( _get_test_field "${curline}" 3)"
+ [ -z "${exp_ec}" ] && warn "test spec.: empty exp_ec"
+ exp_dev="$(_get_test_field "${curline}" 4)"
+ [ -z "${exp_dev}" ] && warn "test spec.: empty exp_dev"
+ exp_nm="$( _get_test_field "${curline}" 5)"
+ [ -z "${exp_nm}" ] && warn "test spec.: empty exp_nm"
+ exp_bc="$( _get_test_field "${curline}" 6)"
+ [ -z "${exp_bc}" ] && warn "test spec.: empty exp_bc"
+ [ $DEBUG_IN -ne 0 ] \
+ && echo "${ip}, ${mask}, ${exp_ec}, ${exp_dev}, ${exp_nm}, ${exp_bc}"
+
+ env="OCF_RESKEY_ip=${ip} OCF_RESKEY_cidr_netmask=${mask}"
+ echo "${env}"
+ res="$(su ${DUMMY_USER} -c "${env} ${CMD} 2>&1")"
+ got_ec=$?
+
+ if [ ${got_ec} -ne ${exp_ec} ]; then
+ warn "FAIL exit code: ${got_ec} vs ${exp_ec}"
+ err_this=$((err_this+1))
+ fi
+
+ res="$(echo "${res}" | tr '\t' ' ' | tr -s ' ')"
+
+ res_check=
+ stop=0
+ echo "${res}" | while read res_line && [ $stop -eq 0 ]; do
+ echo "${res_line}" | grep -Eq "^[0-9A-Za-z_-]+ netmask [0-9]"
+ if [ $? -ne 0 ]; then
+ if [ -z "${res_line}" ] || echo "${res_line}" \
+ | grep -Fq "Copyright Alan Robertson"; then
+ # findif finishes w/ (C)+usage on failure
+ stop=1
+ else
+ echo "${res_line}"
+ fi
+ elif [ -n "${res_check}" ]; then
+ warn "More than one line with results."
+ stop=1
+ else
+ res_check=${res_line}
+ got_dev="$(echo "${res_check}" | cut -d' ' -f1)"
+ got_nm="$(echo "${res_check}" | cut -d' ' -f3)"
+ got_bc="$(echo "${res_check}" | cut -d' ' -f5)"
+ [ ${DEBUG_OUT} -ne 0 ] \
+ && echo "${got_dev}, ${got_nm}, ${got_bc}"
+
+ if [ "${got_dev}" != "${exp_dev}" ]; then
+ warn "FAIL device: ${got_dev} vs ${exp_dev}"
+ err_this=$((err_this+1))
+ fi
+ if [ "${got_nm}" != "${exp_nm}" ]; then
+ warn "FAIL netmask: ${got_nm} vs ${exp_nm}"
+ err_this=$((err_this+1))
+ fi
+ if [ "${got_bc}" != "${exp_bc}" ]; then
+ warn "FAIL broadcast: ${got_bc} vs ${exp_bc}"
+ err_this=$((err_this+1))
+ fi
+ fi
+
+ mimic_return ${err_this} # workaround separate process limitation
+ done
+
+ err_this=$?
+ if [ ${err_this} -eq 0 ]; then
+ ok
+ else
+ fail
+ err_cnt=$((err_cnt+1))
+ fi
+ fi
+ lastline="${curline}"
+ mimic_return ${err_cnt} # workaround separate process limitation
+ done
+ err_cnt=$?
+
+ echo "--- TOTAL ---"
+ [ $err_cnt -eq 0 ] && ok || fail $err_cnt
+ return $err_cnt
+}
+
+if [ $# -ge 1 ]; then
+ while true; do
+ case $1 in
+ -script)
+ CMD="${SCRIPT_CMD}"
+ [ $# -eq 1 ] && break
+ ;;
+ --)
+ TESTS=
+ [ $# -eq 1 ] && break
+ ;;
+ -4)
+ TESTS="4"
+ [ $# -eq 1 ] && break
+ ;;
+ -6)
+ TESTS="6"
+ [ $# -eq 1 ] && break
+ ;;
+ -46)
+ TESTS="4 6"
+ [ $# -eq 1 ] && break
+ ;;
+ setup|proceed|teardown)
+ verbosely $1
+ ret=$?
+ [ $ret -ne 0 ] && exit $ret
+ ;;
+ *)
+ echo "usage: ./$0 [-script] [--|-4|-6|-46] (setup,proceed,teardown)"
+ echo "additional tests may be piped to standard input, format:"
+ echo "${TEST_FORMAT}" | tr '\t' ' ' | tr -s ' '
+ exit 0
+ ;;
+ esac
+ [ $# -eq 1 ] && exit 0
+ shift
+ done
+fi
+
+verbosely setup
+verbosely proceed
+ret=$?
+verbosely teardown
+
+exit $ret
diff --git a/tools/tickle_tcp.c b/tools/tickle_tcp.c
new file mode 100644
index 0000000..46a6cfe
--- /dev/null
+++ b/tools/tickle_tcp.c
@@ -0,0 +1,380 @@
+/*
+ Tickle TCP connections tool
+
+ Author: Jiaju Zhang
+ Based on the code in CTDB http://ctdb.samba.org/ written by
+ Andrew Tridgell and Ronnie Sahlberg
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/tcp.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#define discard_const(ptr) ((void *)((intptr_t)(ptr)))
+
+typedef union {
+ struct sockaddr sa;
+ struct sockaddr_in ip;
+ struct sockaddr_in6 ip6;
+} sock_addr;
+
+void set_nonblocking(int fd);
+void set_close_on_exec(int fd);
+static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin);
+static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr);
+int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr);
+int parse_ip_port(const char *addr, sock_addr *saddr);
+int send_tickle_ack(const sock_addr *dst,
+ const sock_addr *src,
+ uint32_t seq, uint32_t ack, int rst);
+static void usage(void);
+
+static uint32_t uint16_checksum(uint16_t *data, size_t n)
+{
+ uint32_t sum=0;
+ while (n >= 2) {
+ sum += (uint32_t)ntohs(*data);
+ data++;
+ n -= 2;
+ }
+ if (n == 1) {
+ sum += (uint32_t)ntohs(*(uint8_t *)data);
+ }
+ return sum;
+}
+
+static uint16_t tcp_checksum(uint16_t *data, size_t n, struct iphdr *ip)
+{
+ uint32_t sum = uint16_checksum(data, n);
+ uint16_t sum2;
+ sum += uint16_checksum((uint16_t *)(void *)&ip->saddr,
+ sizeof(ip->saddr));
+ sum += uint16_checksum((uint16_t *)(void *)&ip->daddr,
+ sizeof(ip->daddr));
+ sum += ip->protocol + n;
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ sum2 = htons(sum);
+ sum2 = ~sum2;
+ if (sum2 == 0) {
+ return 0xFFFF;
+ }
+ return sum2;
+}
+
+static uint16_t tcp_checksum6(uint16_t *data, size_t n, struct ip6_hdr *ip6)
+{
+ uint32_t phdr[2];
+ uint32_t sum = 0;
+ uint16_t sum2;
+
+ memset(phdr, 0, sizeof(phdr));
+
+ sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_src, 16);
+ sum += uint16_checksum((uint16_t *)(void *)&ip6->ip6_dst, 16);
+
+ phdr[0] = htonl(n);
+ phdr[1] = htonl(ip6->ip6_nxt);
+ sum += uint16_checksum((uint16_t *)phdr, 8);
+
+ sum += uint16_checksum(data, n);
+
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ sum2 = htons(sum);
+ sum2 = ~sum2;
+ if (sum2 == 0) {
+ return 0xFFFF;
+ }
+ return sum2;
+}
+
+void set_nonblocking(int fd)
+{
+ unsigned v;
+ v = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, v | O_NONBLOCK);
+}
+
+void set_close_on_exec(int fd)
+{
+ unsigned v;
+ v = fcntl(fd, F_GETFD, 0);
+ fcntl(fd, F_SETFD, v | FD_CLOEXEC);
+}
+
+static int parse_ipv4(const char *s, unsigned port, struct sockaddr_in *sin)
+{
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(port);
+
+ if (inet_pton(AF_INET, s, &sin->sin_addr) != 1) {
+ fprintf(stderr, "Failed to translate %s into sin_addr\n", s);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_ipv6(const char *s, const char *iface, unsigned port, sock_addr *saddr)
+{
+ saddr->ip6.sin6_family = AF_INET6;
+ saddr->ip6.sin6_port = htons(port);
+ saddr->ip6.sin6_flowinfo = 0;
+ saddr->ip6.sin6_scope_id = 0;
+
+ if (inet_pton(AF_INET6, s, &saddr->ip6.sin6_addr) != 1) {
+ fprintf(stderr, "Failed to translate %s into sin6_addr\n", s);
+ return -1;
+ }
+
+ if (iface && IN6_IS_ADDR_LINKLOCAL(&saddr->ip6.sin6_addr)) {
+ saddr->ip6.sin6_scope_id = if_nametoindex(iface);
+ }
+
+ return 0;
+}
+
+int parse_ip(const char *addr, const char *iface, unsigned port, sock_addr *saddr)
+{
+ char *p;
+ int ret;
+
+ p = index(addr, ':');
+ if (!p)
+ ret = parse_ipv4(addr, port, &saddr->ip);
+ else
+ ret = parse_ipv6(addr, iface, port, saddr);
+
+ return ret;
+}
+
+int parse_ip_port(const char *addr, sock_addr *saddr)
+{
+ char *s, *p;
+ unsigned port;
+ char *endp = NULL;
+ int ret;
+
+ s = strdup(addr);
+ if (!s) {
+ fprintf(stderr, "Failed strdup()\n");
+ return -1;
+ }
+
+ p = rindex(s, ':');
+ if (!p) {
+ fprintf(stderr, "This addr: %s does not contain a port number\n", s);
+ free(s);
+ return -1;
+ }
+
+ port = strtoul(p+1, &endp, 10);
+ if (!endp || *endp != 0) {
+ fprintf(stderr, "Trailing garbage after the port in %s\n", s);
+ free(s);
+ return -1;
+ }
+ *p = 0;
+
+ ret = parse_ip(s, NULL, port, saddr);
+ free(s);
+ return ret;
+}
+
+int send_tickle_ack(const sock_addr *dst,
+ const sock_addr *src,
+ uint32_t seq, uint32_t ack, int rst)
+{
+ int s;
+ int ret;
+ uint32_t one = 1;
+ uint16_t tmpport;
+ sock_addr *tmpdest;
+ struct {
+ struct iphdr ip;
+ struct tcphdr tcp;
+ } ip4pkt;
+ struct {
+ struct ip6_hdr ip6;
+ struct tcphdr tcp;
+ } ip6pkt;
+
+ switch (src->ip.sin_family) {
+ case AF_INET:
+ memset(&ip4pkt, 0, sizeof(ip4pkt));
+ ip4pkt.ip.version = 4;
+ ip4pkt.ip.ihl = sizeof(ip4pkt.ip)/4;
+ ip4pkt.ip.tot_len = htons(sizeof(ip4pkt));
+ ip4pkt.ip.ttl = 255;
+ ip4pkt.ip.protocol = IPPROTO_TCP;
+ ip4pkt.ip.saddr = src->ip.sin_addr.s_addr;
+ ip4pkt.ip.daddr = dst->ip.sin_addr.s_addr;
+ ip4pkt.ip.check = 0;
+
+ ip4pkt.tcp.source = src->ip.sin_port;
+ ip4pkt.tcp.dest = dst->ip.sin_port;
+ ip4pkt.tcp.seq = seq;
+ ip4pkt.tcp.ack_seq = ack;
+ ip4pkt.tcp.ack = 1;
+ if (rst)
+ ip4pkt.tcp.rst = 1;
+ ip4pkt.tcp.doff = sizeof(ip4pkt.tcp)/4;
+ ip4pkt.tcp.window = htons(1234);
+ ip4pkt.tcp.check = tcp_checksum((uint16_t *)&ip4pkt.tcp, sizeof(ip4pkt.tcp), &ip4pkt.ip);
+
+ s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (s == -1) {
+ fprintf(stderr, "Failed to open raw socket (%s)\n", strerror(errno));
+ return -1;
+ }
+
+ ret = setsockopt(s, SOL_IP, IP_HDRINCL, &one, sizeof(one));
+ if (ret != 0) {
+ fprintf(stderr, "Failed to setup IP headers (%s)\n", strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ set_nonblocking(s);
+ set_close_on_exec(s);
+
+ ret = sendto(s, &ip4pkt, sizeof(ip4pkt), 0,
+ (const struct sockaddr *)&dst->ip, sizeof(dst->ip));
+ close(s);
+ if (ret != sizeof(ip4pkt)) {
+ fprintf(stderr, "Failed sendto (%s)\n", strerror(errno));
+ return -1;
+ }
+ break;
+
+ case AF_INET6:
+ memset(&ip6pkt, 0, sizeof(ip6pkt));
+ ip6pkt.ip6.ip6_vfc = 0x60;
+ ip6pkt.ip6.ip6_plen = htons(20);
+ ip6pkt.ip6.ip6_nxt = IPPROTO_TCP;
+ ip6pkt.ip6.ip6_hlim = 64;
+ ip6pkt.ip6.ip6_src = src->ip6.sin6_addr;
+ ip6pkt.ip6.ip6_dst = dst->ip6.sin6_addr;
+
+ ip6pkt.tcp.source = src->ip6.sin6_port;
+ ip6pkt.tcp.dest = dst->ip6.sin6_port;
+ ip6pkt.tcp.seq = seq;
+ ip6pkt.tcp.ack_seq = ack;
+ ip6pkt.tcp.ack = 1;
+ if (rst)
+ ip6pkt.tcp.rst = 1;
+ ip6pkt.tcp.doff = sizeof(ip6pkt.tcp)/4;
+ ip6pkt.tcp.window = htons(1234);
+ ip6pkt.tcp.check = tcp_checksum6((uint16_t *)&ip6pkt.tcp, sizeof(ip6pkt.tcp), &ip6pkt.ip6);
+
+ s = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW);
+ if (s == -1) {
+ fprintf(stderr, "Failed to open sending socket\n");
+ return -1;
+ }
+
+ tmpdest = discard_const(dst);
+ tmpport = tmpdest->ip6.sin6_port;
+
+ tmpdest->ip6.sin6_port = 0;
+ ret = sendto(s, &ip6pkt, sizeof(ip6pkt), 0, (const struct sockaddr *)&dst->ip6, sizeof(dst->ip6));
+ tmpdest->ip6.sin6_port = tmpport;
+ close(s);
+
+ if (ret != sizeof(ip6pkt)) {
+ fprintf(stderr, "Failed sendto (%s)\n", strerror(errno));
+ return -1;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Not an ipv4/v6 address\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void usage(void)
+{
+ printf("Usage: /usr/lib/heartbeat/tickle_tcp [ -n num ]\n");
+ printf("Please note that this program need to read the list of\n");
+ printf("{local_ip:port remote_ip:port} from stdin.\n");
+ exit(1);
+}
+
+#define OPTION_STRING "n:h"
+
+int main(int argc, char *argv[])
+{
+ int optchar, i, num = 1, cont = 1;
+ sock_addr src, dst;
+ char addrline[128], addr1[64], addr2[64];
+
+ while(cont) {
+ optchar = getopt(argc, argv, OPTION_STRING);
+ switch(optchar) {
+ case 'n':
+ num = atoi(optarg);
+ break;
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+ break;
+ case EOF:
+ cont = 0;
+ break;
+ default:
+ fprintf(stderr, "unknown option, please use '-h' for usage.\n");
+ exit(EXIT_FAILURE);
+ break;
+ };
+ }
+
+ while(fgets(addrline, sizeof(addrline), stdin)) {
+ sscanf(addrline, "%s %s", addr1, addr2);
+
+ if (parse_ip_port(addr1, &src)) {
+ fprintf(stderr, "Bad IP:port '%s'\n", addr1);
+ return -1;
+ }
+ if (parse_ip_port(addr2, &dst)) {
+ fprintf(stderr, "Bad IP:port '%s'\n", addr2);
+ return -1;
+ }
+
+ for (i = 1; i <= num; i++) {
+ if (send_tickle_ack(&dst, &src, 0, 0, 0)) {
+ fprintf(stderr, "Error while sending tickle ack from '%s' to '%s'\n",
+ addr1, addr2);
+ return -1;
+ }
+ }
+
+ }
+ return 0;
+}