From 70f2573f2a99b013500b82f7e46225fff4a57a2a Mon Sep 17 00:00:00 2001
From: Amitay Isaacs <amitay@gmail.com>
Date: Fri, 17 Jul 2015 22:45:04 +1000
Subject: [PATCH] ctdb-tool: Add replacement ctdb tool using new client API

New tool breaks some of the tool unit tests due to improved error
messages.  Those changes are in the following patch.

Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
---
 ctdb/tools/ctdb.c | 6584 +++++++++++++++++++++++++++++++++++++++++++++
 ctdb/wscript      |    7 +
 2 files changed, 6591 insertions(+)
 create mode 100644 ctdb/tools/ctdb.c

diff --git a/ctdb/tools/ctdb.c b/ctdb/tools/ctdb.c
new file mode 100644
index 00000000000..d0381a5aacf
--- /dev/null
+++ b/ctdb/tools/ctdb.c
@@ -0,0 +1,6584 @@
+/*
+   CTDB control tool
+
+   Copyright (C) Amitay Isaacs  2015
+
+   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 "replace.h"
+#include "system/network.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "system/wait.h"
+
+#include <ctype.h>
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <tdb.h>
+
+#include "ctdb_version.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+
+#include "common/db_hash.h"
+#include "common/logging.h"
+#include "protocol/protocol.h"
+#include "protocol/protocol_api.h"
+#include "common/system.h"
+#include "client/client.h"
+
+#define TIMEOUT()	timeval_current_ofs(options.timelimit, 0)
+
+#define SRVID_CTDB_TOOL    (CTDB_SRVID_TOOL_RANGE | 0x0001000000000000LL)
+#define SRVID_CTDB_PUSHDB  (CTDB_SRVID_TOOL_RANGE | 0x0002000000000000LL)
+
+static struct {
+	const char *socket;
+	const char *debuglevelstr;
+	int timelimit;
+	int pnn;
+	int machinereadable;
+	const char *sep;
+	int machineparsable;
+	int verbose;
+	int maxruntime;
+	int printemptyrecords;
+	int printdatasize;
+	int printlmaster;
+	int printhash;
+	int printrecordflags;
+} options;
+
+static poptContext pc;
+
+struct ctdb_context {
+	struct tevent_context *ev;
+	struct ctdb_client_context *client;
+	struct ctdb_node_map *nodemap;
+	uint32_t pnn, cmd_pnn;
+	uint64_t srvid;
+};
+
+static void usage(const char *command);
+
+/*
+ * Utility Functions
+ */
+
+static double timeval_delta(struct timeval *tv2, struct timeval *tv)
+{
+	return (tv2->tv_sec - tv->tv_sec) +
+	       (tv2->tv_usec - tv->tv_usec) * 1.0e-6;
+}
+
+static struct ctdb_node_and_flags *get_node_by_pnn(
+					struct ctdb_node_map *nodemap,
+					uint32_t pnn)
+{
+	int i;
+
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].pnn == pnn) {
+			return &nodemap->node[i];
+		}
+	}
+	return NULL;
+}
+
+static const char *pretty_print_flags(TALLOC_CTX *mem_ctx, uint32_t flags)
+{
+	static const struct {
+		uint32_t flag;
+		const char *name;
+	} flag_names[] = {
+		{ NODE_FLAGS_DISCONNECTED,	    "DISCONNECTED" },
+		{ NODE_FLAGS_PERMANENTLY_DISABLED,  "DISABLED" },
+		{ NODE_FLAGS_BANNED,		    "BANNED" },
+		{ NODE_FLAGS_UNHEALTHY,		    "UNHEALTHY" },
+		{ NODE_FLAGS_DELETED,		    "DELETED" },
+		{ NODE_FLAGS_STOPPED,		    "STOPPED" },
+		{ NODE_FLAGS_INACTIVE,		    "INACTIVE" },
+	};
+	char *flags_str = NULL;
+	int i;
+
+	for (i=0; i<ARRAY_SIZE(flag_names); i++) {
+		if (flags & flag_names[i].flag) {
+			if (flags_str == NULL) {
+				flags_str = talloc_asprintf(mem_ctx,
+						"%s", flag_names[i].name);
+			} else {
+				flags_str = talloc_asprintf_append(flags_str,
+						"|%s", flag_names[i].name);
+			}
+			if (flags_str == NULL) {
+				return "OUT-OF-MEMORY";
+			}
+		}
+	}
+	if (flags_str == NULL) {
+		return "OK";
+	}
+
+	return flags_str;
+}
+
+static uint64_t next_srvid(struct ctdb_context *ctdb)
+{
+	ctdb->srvid += 1;
+	return ctdb->srvid;
+}
+
+/*
+ * Get consistent nodemap information.
+ *
+ * If nodemap is already cached, use that. If not get it.
+ * If the current node is BANNED, then get nodemap from "better" node.
+ */
+static struct ctdb_node_map *get_nodemap(struct ctdb_context *ctdb, bool force)
+{
+	TALLOC_CTX *tmp_ctx;
+	struct ctdb_node_map *nodemap;
+	struct ctdb_node_and_flags *node;
+	uint32_t current_node;
+	int ret;
+
+	if (force) {
+		TALLOC_FREE(ctdb->nodemap);
+	}
+
+	if (ctdb->nodemap != NULL) {
+		return ctdb->nodemap;
+	}
+
+	tmp_ctx = talloc_new(ctdb);
+	if (tmp_ctx == NULL) {
+		return false;
+	}
+
+	current_node = ctdb->pnn;
+again:
+	ret = ctdb_ctrl_get_nodemap(tmp_ctx, ctdb->ev, ctdb->client,
+				    current_node, TIMEOUT(), &nodemap);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get nodemap from node %u\n",
+			current_node);
+		goto failed;
+	}
+
+	node = get_node_by_pnn(nodemap, current_node);
+	if (node->flags & NODE_FLAGS_BANNED) {
+		/* Pick next node */
+		do {
+			current_node = (current_node + 1) % nodemap->num;
+			node = get_node_by_pnn(nodemap, current_node);
+			if (! (node->flags &
+			      (NODE_FLAGS_DELETED|NODE_FLAGS_DISCONNECTED))) {
+				break;
+			}
+		} while (current_node != ctdb->pnn);
+
+		if (current_node == ctdb->pnn) {
+			/* Tried all nodes in the cluster */
+			fprintf(stderr, "Warning: All nodes are banned.\n");
+			goto failed;
+		}
+
+		goto again;
+	}
+
+	ctdb->nodemap = talloc_steal(ctdb, nodemap);
+	return nodemap;
+
+failed:
+	talloc_free(tmp_ctx);
+	return NULL;
+}
+
+static bool verify_pnn(struct ctdb_context *ctdb, int pnn)
+{
+	struct ctdb_node_map *nodemap;
+	bool found;
+	int i;
+
+	if (pnn == -1) {
+		return false;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return false;
+	}
+
+	found = false;
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].pnn == pnn) {
+			found = true;
+			break;
+		}
+	}
+	if (! found) {
+		fprintf(stderr, "Node %u does not exist\n", pnn);
+		return false;
+	}
+
+	if (nodemap->node[i].flags &
+	    (NODE_FLAGS_DISCONNECTED|NODE_FLAGS_DELETED)) {
+		fprintf(stderr, "Node %u has status %s\n", pnn,
+			pretty_print_flags(ctdb, nodemap->node[i].flags));
+		return false;
+	}
+
+	return true;
+}
+
+static struct ctdb_node_map *talloc_nodemap(TALLOC_CTX *mem_ctx,
+					    struct ctdb_node_map *nodemap)
+{
+	struct ctdb_node_map *nodemap2;
+
+	nodemap2 = talloc_zero(mem_ctx, struct ctdb_node_map);
+	if (nodemap2 == NULL) {
+		return NULL;
+	}
+
+	nodemap2->node = talloc_array(nodemap2, struct ctdb_node_and_flags,
+				      nodemap->num);
+	if (nodemap2->node == NULL) {
+		talloc_free(nodemap2);
+		return NULL;
+	}
+
+	return nodemap2;
+}
+
+/*
+ * Get the number and the list of matching nodes
+ *
+ *   nodestring :=  NULL | all | pnn,[pnn,...]
+ *
+ * If nodestring is NULL, use the current node.
+ */
+static bool parse_nodestring(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     const char *nodestring,
+			     struct ctdb_node_map **out)
+{
+	struct ctdb_node_map *nodemap, *nodemap2;
+	struct ctdb_node_and_flags *node;
+	int i;
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return false;
+	}
+
+	nodemap2 = talloc_nodemap(mem_ctx, nodemap);
+	if (nodemap2 == NULL) {
+		return false;
+	}
+
+	if (nodestring == NULL) {
+		for (i=0; i<nodemap->num; i++) {
+			if (nodemap->node[i].pnn == ctdb->cmd_pnn) {
+				nodemap2->node[0] = nodemap->node[i];
+				break;
+			}
+		}
+		nodemap2->num = 1;
+
+		goto done;
+	}
+
+	if (strcmp(nodestring, "all") == 0) {
+		for (i=0; i<nodemap->num; i++) {
+			nodemap2->node[i] = nodemap->node[i];
+		}
+		nodemap2->num = nodemap->num;
+
+		goto done;
+	} else {
+		char *ns, *tok;
+
+		ns = talloc_strdup(mem_ctx, nodestring);
+		if (ns == NULL) {
+			return false;
+		}
+
+		tok = strtok(ns, ",");
+		while (tok != NULL) {
+			uint32_t pnn;
+			char *endptr;
+
+			pnn = (uint32_t)strtoul(tok, &endptr, 0);
+			if (pnn == 0 && tok == endptr) {
+				fprintf(stderr, "Invalid node %s\n", tok);
+					return false;
+			}
+
+			node = get_node_by_pnn(nodemap, pnn);
+			if (node == NULL) {
+				fprintf(stderr, "Node %u does not exist\n",
+					pnn);
+				return false;
+			}
+
+			nodemap2->node[nodemap2->num] = *node;
+			nodemap2->num += 1;
+
+			tok = strtok(NULL, ",");
+		}
+	}
+
+done:
+	*out = nodemap2;
+	return true;
+}
+
+/* Compare IP address */
+static bool ctdb_same_ip(ctdb_sock_addr *ip1, ctdb_sock_addr *ip2)
+{
+	bool ret = false;
+
+	if (ip1->sa.sa_family != ip2->sa.sa_family) {
+		return false;
+	}
+
+	switch (ip1->sa.sa_family) {
+	case AF_INET:
+		ret = (memcmp(&ip1->ip.sin_addr, &ip2->ip.sin_addr,
+			      sizeof(struct in_addr)) == 0);
+		break;
+
+	case AF_INET6:
+		ret = (memcmp(&ip1->ip6.sin6_addr, &ip2->ip6.sin6_addr,
+			      sizeof(struct in6_addr)) == 0);
+		break;
+	}
+
+	return ret;
+}
+
+/* Append a node to a node map with given address and flags */
+static bool node_map_add(struct ctdb_node_map *nodemap,
+			 const char *nstr, uint32_t flags)
+{
+	ctdb_sock_addr addr;
+	uint32_t num;
+	struct ctdb_node_and_flags *n;
+
+	if (! parse_ip(nstr, NULL, 0, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", nstr);
+		return false;
+	}
+
+	num = nodemap->num;
+	nodemap->node = talloc_realloc(nodemap, nodemap->node,
+				       struct ctdb_node_and_flags, num+1);
+	if (nodemap->node == NULL) {
+		return false;
+	}
+
+	n = &nodemap->node[num];
+	n->addr = addr;
+	n->pnn = num;
+	n->flags = flags;
+
+	nodemap->num = num+1;
+	return true;
+}
+
+/* Read a nodes file into a node map */
+static struct ctdb_node_map *ctdb_read_nodes_file(TALLOC_CTX *mem_ctx,
+						  const char *nlist)
+{
+	char **lines;
+	int nlines;
+	int i;
+	struct ctdb_node_map *nodemap;
+
+	nodemap = talloc_zero(mem_ctx, struct ctdb_node_map);
+	if (nodemap == NULL) {
+		return NULL;
+	}
+
+	lines = file_lines_load(nlist, &nlines, 0, mem_ctx);
+	if (lines == NULL) {
+		return NULL;
+	}
+
+	while (nlines > 0 && strcmp(lines[nlines-1], "") == 0) {
+		nlines--;
+	}
+
+	for (i=0; i<nlines; i++) {
+		char *node;
+		uint32_t flags;
+		size_t len;
+
+		node = lines[i];
+		/* strip leading spaces */
+		while((*node == ' ') || (*node == '\t')) {
+			node++;
+		}
+
+		len = strlen(node);
+
+		/* strip trailing spaces */
+		while ((len > 1) &&
+		       ((node[len-1] == ' ') || (node[len-1] == '\t')))
+		{
+			node[len-1] = '\0';
+			len--;
+		}
+
+		if (len == 0) {
+			continue;
+		}
+		if (*node == '#') {
+			/* A "deleted" node is a node that is
+			   commented out in the nodes file.  This is
+			   used instead of removing a line, which
+			   would cause subsequent nodes to change
+			   their PNN. */
+			flags = NODE_FLAGS_DELETED;
+			node = discard_const("0.0.0.0");
+		} else {
+			flags = 0;
+		}
+		if (! node_map_add(nodemap, node, flags)) {
+			talloc_free(lines);
+			TALLOC_FREE(nodemap);
+			return NULL;
+		}
+	}
+
+	talloc_free(lines);
+	return nodemap;
+}
+
+static struct ctdb_node_map *read_nodes_file(TALLOC_CTX *mem_ctx, uint32_t pnn)
+{
+	struct ctdb_node_map *nodemap;
+	char *nodepath;
+	const char *nodes_list = NULL;
+
+	if (pnn != CTDB_UNKNOWN_PNN) {
+		nodepath = talloc_asprintf(mem_ctx, "CTDB_NODES_%u", pnn);
+		if (nodepath != NULL) {
+			nodes_list = getenv(nodepath);
+		}
+	}
+	if (nodes_list == NULL) {
+		nodes_list = getenv("CTDB_NODES");
+	}
+	if (nodes_list == NULL) {
+		const char *basedir = getenv("CTDB_BASE");
+		if (basedir == NULL) {
+			basedir = CTDB_ETCDIR;
+		}
+		nodes_list = talloc_asprintf(mem_ctx, "%s/nodes", basedir);
+		if (nodes_list == NULL) {
+			fprintf(stderr, "Memory allocation error\n");
+			return NULL;
+		}
+	}
+
+	nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list);
+	if (nodemap == NULL) {
+		fprintf(stderr, "Failed to read nodes file \"%s\"\n",
+			nodes_list);
+		return NULL;
+	}
+
+	return nodemap;
+}
+
+static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx,
+				 struct ctdb_context *ctdb,
+				 struct ctdb_dbid_map *dbmap,
+				 const char *db_name)
+{
+	struct ctdb_dbid *db = NULL;
+	const char *name;
+	int ret, i;
+
+	for (i=0; i<dbmap->num; i++) {
+		ret = ctdb_ctrl_get_dbname(mem_ctx, ctdb->ev, ctdb->client,
+					   ctdb->pnn, TIMEOUT(),
+					   dbmap->dbs[i].db_id, &name);
+		if (ret != 0) {
+			return false;
+		}
+
+		if (strcmp(db_name, name) == 0) {
+			talloc_free(discard_const(name));
+			db = &dbmap->dbs[i];
+			break;
+		}
+	}
+
+	return db;
+}
+
+static bool db_exists(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		      const char *db_arg, uint32_t *db_id,
+		      const char **db_name, uint8_t *db_flags)
+{
+	struct ctdb_dbid_map *dbmap;
+	struct ctdb_dbid *db = NULL;
+	uint32_t id = 0;
+	const char *name = NULL;
+	int ret, i;
+
+	ret = ctdb_ctrl_get_dbmap(mem_ctx, ctdb->ev, ctdb->client,
+				  ctdb->pnn, TIMEOUT(), &dbmap);
+	if (ret != 0) {
+		return false;
+	}
+
+	if (strncmp(db_arg, "0x", 2) == 0) {
+		id = strtoul(db_arg, NULL, 0);
+		for (i=0; i<dbmap->num; i++) {
+			if (id == dbmap->dbs[i].db_id) {
+				db = &dbmap->dbs[i];
+				break;
+			}
+		}
+	} else {
+		name = db_arg;
+		db = db_find(mem_ctx, ctdb, dbmap, name);
+	}
+
+	if (db == NULL) {
+		fprintf(stderr, "No database matching '%s' found\n", db_arg);
+		return false;
+	}
+
+	if (name == NULL) {
+		ret = ctdb_ctrl_get_dbname(mem_ctx, ctdb->ev, ctdb->client,
+					   ctdb->pnn, TIMEOUT(), id, &name);
+		if (ret != 0) {
+			return false;
+		}
+	}
+
+	if (db_id != NULL) {
+		*db_id = db->db_id;
+	}
+	if (db_name != NULL) {
+		*db_name = talloc_strdup(mem_ctx, name);
+	}
+	if (db_flags != NULL) {
+		*db_flags = db->flags;
+	}
+	return true;
+}
+
+static int h2i(char h)
+{
+	if (h >= 'a' && h <= 'f') {
+		return h - 'a' + 10;
+	}
+	if (h >= 'A' && h <= 'F') {
+		return h - 'f' + 10;
+	}
+	return h - '0';
+}
+
+static int hex_to_data(const char *str, size_t len, TALLOC_CTX *mem_ctx,
+		       TDB_DATA *out)
+{
+	int i;
+	TDB_DATA data;
+
+	if (len & 0x01) {
+		fprintf(stderr, "Key (%s) contains odd number of hex digits\n",
+			str);
+		return EINVAL;
+	}
+
+	data.dsize = len / 2;
+	data.dptr = talloc_size(mem_ctx, data.dsize);
+	if (data.dptr == NULL) {
+		return ENOMEM;
+	}
+
+	for (i=0; i<data.dsize; i++) {
+		data.dptr[i] = h2i(str[i*2]) << 4 | h2i(str[i*2+1]);
+	}
+
+	*out = data;
+	return 0;
+}
+
+static int str_to_data(const char *str, size_t len, TALLOC_CTX *mem_ctx,
+		       TDB_DATA *out)
+{
+	TDB_DATA data;
+	int ret = 0;
+
+	if (strncmp(str, "0x", 2) == 0) {
+		ret = hex_to_data(str+2, len-2, mem_ctx, &data);
+	} else {
+		data.dptr = talloc_memdup(mem_ctx, str, len);
+		if (data.dptr == NULL) {
+			return ENOMEM;
+		}
+		data.dsize = len;
+	}
+
+	*out = data;
+	return ret;
+}
+
+static int run_helper(const char *command, const char *path, const char *arg1)
+{
+	pid_t pid;
+	int save_errno, status, ret;
+
+	pid = fork();
+	if (pid < 0) {
+		save_errno = errno;
+		fprintf(stderr, "Failed to fork %s (%s) - %s\n",
+			command, path, strerror(save_errno));
+		return save_errno;
+	}
+
+	if (pid == 0) {
+		ret = execl(path, path, arg1, NULL);
+		if (ret == -1) {
+			_exit(errno);
+		}
+		/* Should not happen */
+		_exit(ENOEXEC);
+	}
+
+	ret = waitpid(pid, &status, 0);
+	if (ret == -1) {
+		save_errno = errno;
+		fprintf(stderr, "waitpid() failed for %s - %s\n",
+			command, strerror(save_errno));
+		return save_errno;
+	}
+
+	if (WIFEXITED(status)) {
+		ret = WEXITSTATUS(status);
+		return ret;
+	}
+
+	if (WIFSIGNALED(status)) {
+		fprintf(stderr, "%s terminated with signal %d\n",
+			command, WTERMSIG(status));
+		return EINTR;
+	}
+
+	return 0;
+}
+
+/*
+ * Command Functions
+ */
+
+static int control_version(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	printf("%s\n", CTDB_VERSION_STRING);
+	return 0;
+}
+
+static bool partially_online(TALLOC_CTX *mem_ctx,
+			     struct ctdb_context *ctdb,
+			     struct ctdb_node_and_flags *node)
+{
+	struct ctdb_iface_list *iface_list;
+	int ret, i;
+	bool status = false;
+
+	if (node->flags != 0) {
+		return false;
+	}
+
+	ret = ctdb_ctrl_get_ifaces(mem_ctx, ctdb->ev, ctdb->client,
+				   node->pnn, TIMEOUT(), &iface_list);
+	if (ret != 0) {
+		return false;
+	}
+
+	status = false;
+	for (i=0; i < iface_list->num; i++) {
+		if (iface_list->iface[i].link_state == 0) {
+			status = true;
+			break;
+		}
+	}
+
+	return status;
+}
+
+static void print_nodemap_machine(TALLOC_CTX *mem_ctx,
+				  struct ctdb_context *ctdb,
+				  struct ctdb_node_map *nodemap,
+				  uint32_t mypnn)
+{
+	struct ctdb_node_and_flags *node;
+	int i;
+
+	printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+	       options.sep,
+	       "Node", options.sep,
+	       "IP", options.sep,
+	       "Disconnected", options.sep,
+	       "Banned", options.sep,
+	       "Disabled", options.sep,
+	       "Unhealthy", options.sep,
+	       "Stopped", options.sep,
+	       "Inactive", options.sep,
+	       "PartiallyOnline", options.sep,
+	       "ThisNode", options.sep);
+
+	for (i=0; i<nodemap->num; i++) {
+		node = &nodemap->node[i];
+		if (node->flags & NODE_FLAGS_DELETED) {
+			continue;
+		}
+
+		printf("%s%u%s%s%s%d%s%d%s%d%s%d%s%d%s%d%s%d%s%c%s\n",
+		       options.sep,
+		       node->pnn, options.sep,
+		       ctdb_sock_addr_to_string(mem_ctx, &node->addr),
+		       options.sep,
+		       !! (node->flags & NODE_FLAGS_DISCONNECTED), options.sep,
+		       !! (node->flags & NODE_FLAGS_BANNED), options.sep,
+		       !! (node->flags & NODE_FLAGS_PERMANENTLY_DISABLED),
+		       options.sep,
+		       !! (node->flags & NODE_FLAGS_UNHEALTHY), options.sep,
+		       !! (node->flags & NODE_FLAGS_STOPPED), options.sep,
+		       !! (node->flags & NODE_FLAGS_INACTIVE), options.sep,
+		       partially_online(mem_ctx, ctdb, node), options.sep,
+		       (node->pnn == mypnn)?'Y':'N', options.sep);
+	}
+
+}
+
+static void print_nodemap(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  struct ctdb_node_map *nodemap, uint32_t mypnn)
+{
+	struct ctdb_node_and_flags *node;
+	int num_deleted_nodes = 0;
+	int i;
+
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].flags & NODE_FLAGS_DELETED) {
+			num_deleted_nodes++;
+		}
+	}
+
+	if (num_deleted_nodes == 0) {
+		printf("Number of nodes:%d\n", nodemap->num);
+	} else {
+		printf("Number of nodes:%d (including %d deleted nodes)\n",
+		       nodemap->num, num_deleted_nodes);
+	}
+
+	for (i=0; i<nodemap->num; i++) {
+		node = &nodemap->node[i];
+		if (node->flags & NODE_FLAGS_DELETED) {
+			continue;
+		}
+
+		printf("pnn:%u %-16s %s%s\n",
+		       node->pnn,
+		       ctdb_sock_addr_to_string(mem_ctx, &node->addr),
+		       partially_online(mem_ctx, ctdb, node) ?
+				"PARTIALLYONLINE" :
+				pretty_print_flags(mem_ctx, node->flags),
+		       node->pnn == mypnn ? " (THIS NODE)" : "");
+	}
+}
+
+static void print_status(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 struct ctdb_node_map *nodemap, uint32_t mypnn,
+			 struct ctdb_vnn_map *vnnmap, int recmode,
+			 uint32_t recmaster)
+{
+	int i;
+
+	print_nodemap(mem_ctx, ctdb, nodemap, mypnn);
+
+	if (vnnmap->generation == INVALID_GENERATION) {
+		printf("Generation:INVALID\n");
+	} else {
+		printf("Generation:%u\n", vnnmap->generation);
+	}
+	printf("Size:%d\n", vnnmap->size);
+	for (i=0; i<vnnmap->size; i++) {
+		printf("hash:%d lmaster:%d\n", i, vnnmap->map[i]);
+	}
+
+	printf("Recovery mode:%s (%d)\n",
+	       recmode == CTDB_RECOVERY_NORMAL ? "NORMAL" : "RECOVERY",
+	       recmode);
+	printf("Recovery master:%d\n", recmaster);
+}
+
+static int control_status(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_node_map *nodemap;
+	struct ctdb_vnn_map *vnnmap;
+	int recmode;
+	uint32_t recmaster;
+	int ret;
+
+	if (argc != 0) {
+		usage("status");
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	if (options.machinereadable == 1) {
+		print_nodemap_machine(mem_ctx, ctdb, nodemap, ctdb->cmd_pnn);
+		return 0;
+	}
+
+	ret = ctdb_ctrl_getvnnmap(mem_ctx, ctdb->ev, ctdb->client,
+				  ctdb->cmd_pnn, TIMEOUT(), &vnnmap);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_ctrl_get_recmode(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), &recmode);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_ctrl_get_recmaster(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &recmaster);
+	if (ret != 0) {
+		return ret;
+	}
+
+	print_status(mem_ctx, ctdb, nodemap, ctdb->cmd_pnn, vnnmap,
+		     recmode, recmaster);
+	return 0;
+}
+
+static int control_uptime(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_uptime *uptime;
+	int ret, tmp, days, hours, minutes, seconds;
+
+	ret = ctdb_ctrl_uptime(mem_ctx, ctdb->ev, ctdb->client,
+			       ctdb->cmd_pnn, TIMEOUT(), &uptime);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("Current time of node %-4u     :                %s",
+	       ctdb->cmd_pnn, ctime(&uptime->current_time.tv_sec));
+
+	tmp = uptime->current_time.tv_sec - uptime->ctdbd_start_time.tv_sec;
+	seconds = tmp % 60; tmp /= 60;
+	minutes = tmp % 60; tmp /= 60;
+	hours = tmp % 24; tmp /= 24;
+	days = tmp;
+
+	printf("Ctdbd start time              : (%03d %02d:%02d:%02d) %s",
+	       days, hours, minutes, seconds,
+	       ctime(&uptime->ctdbd_start_time.tv_sec));
+
+	tmp = uptime->current_time.tv_sec - uptime->last_recovery_finished.tv_sec;
+	seconds = tmp % 60; tmp /= 60;
+	minutes = tmp % 60; tmp /= 60;
+	hours = tmp % 24; tmp /= 24;
+	days = tmp;
+
+	printf("Time of last recovery/failover: (%03d %02d:%02d:%02d) %s",
+	       days, hours, minutes, seconds,
+	       ctime(&uptime->last_recovery_finished.tv_sec));
+
+	printf("Duration of last recovery/failover: %lf seconds\n",
+	       timeval_delta(&uptime->last_recovery_finished,
+			     &uptime->last_recovery_started));
+
+	return 0;
+}
+
+static int control_ping(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			int argc, const char **argv)
+{
+	struct timeval tv;
+	int ret, num_clients;
+
+	tv = timeval_current();
+	ret = ctdb_ctrl_ping(mem_ctx, ctdb->ev, ctdb->client,
+			     ctdb->cmd_pnn, TIMEOUT(), &num_clients);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("response from %u time=%.6f sec  (%d clients)\n",
+	       ctdb->cmd_pnn, timeval_elapsed(&tv), num_clients);
+	return 0;
+}
+
+const char *runstate_to_string(enum ctdb_runstate runstate);
+enum ctdb_runstate runstate_from_string(const char *runstate_str);
+
+static int control_runstate(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	enum ctdb_runstate runstate;
+	bool found;
+	int ret, i;
+
+	ret = ctdb_ctrl_get_runstate(mem_ctx, ctdb->ev, ctdb->client,
+				     ctdb->cmd_pnn, TIMEOUT(), &runstate);
+	if (ret != 0) {
+		return ret;
+	}
+
+	found = true;
+	for (i=0; i<argc; i++) {
+		enum ctdb_runstate t;
+
+		found = false;
+		t = ctdb_runstate_from_string(argv[i]);
+		if (t == CTDB_RUNSTATE_UNKNOWN) {
+			printf("Invalid run state (%s)\n", argv[i]);
+			return 1;
+		}
+
+		if (t == runstate) {
+			found = true;
+			break;
+		}
+	}
+
+	if (! found) {
+		printf("CTDB not in required run state (got %s)\n",
+		       ctdb_runstate_to_string(runstate));
+		return 1;
+	}
+
+	printf("%s\n", ctdb_runstate_to_string(runstate));
+	return 0;
+}
+
+static int control_getvar(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_var_list *tun_var_list;
+	uint32_t value;
+	int ret, i;
+	bool found;
+
+	if (argc != 1) {
+		usage("getvar");
+	}
+
+	ret = ctdb_ctrl_list_tunables(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &tun_var_list);
+	if (ret != 0) {
+		fprintf(stderr,
+			"Failed to get list of variables from node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	found = false;
+	for (i=0; i<tun_var_list->count; i++) {
+		if (strcasecmp(tun_var_list->var[i], argv[0]) == 0) {
+			found = true;
+			break;
+		}
+	}
+
+	if (! found) {
+		printf("No such tunable %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_tunable(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), argv[0], &value);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("%-26s = %u\n", argv[0], value);
+	return 0;
+}
+
+static int control_setvar(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_var_list *tun_var_list;
+	struct ctdb_tunable tunable;
+	int ret, i;
+	bool found;
+
+	if (argc != 2) {
+		usage("setvar");
+	}
+
+	ret = ctdb_ctrl_list_tunables(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &tun_var_list);
+	if (ret != 0) {
+		fprintf(stderr,
+			"Failed to get list of variables from node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	found = false;
+	for (i=0; i<tun_var_list->count; i++) {
+		if (strcasecmp(tun_var_list->var[i], argv[0]) == 0) {
+			found = true;
+			break;
+		}
+	}
+
+	if (! found) {
+		printf("No such tunable %s\n", argv[0]);
+		return 1;
+	}
+
+	tunable.name = argv[0];
+	tunable.value = strtoul(argv[1], NULL, 0);
+
+	ret = ctdb_ctrl_set_tunable(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), &tunable);
+	if (ret != 0) {
+		if (ret == 1) {
+			fprintf(stderr,
+			        "Setting obsolete tunable variable '%s'\n",
+			       tunable.name);
+			return 0;
+		}
+	}
+
+	return ret;
+}
+
+static int control_listvars(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	struct ctdb_var_list *tun_var_list;
+	int ret, i;
+
+	if (argc != 0) {
+		usage("listvars");
+	}
+
+	ret = ctdb_ctrl_list_tunables(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &tun_var_list);
+	if (ret != 0) {
+		return ret;
+	}
+
+	for (i=0; i<tun_var_list->count; i++) {
+		control_getvar(mem_ctx, ctdb, 1, &tun_var_list->var[i]);
+	}
+
+	return 0;
+}
+
+const struct {
+	const char *name;
+	uint32_t offset;
+} stats_fields[] = {
+#define STATISTICS_FIELD(n) { #n, offsetof(struct ctdb_statistics, n) }
+	STATISTICS_FIELD(num_clients),
+	STATISTICS_FIELD(frozen),
+	STATISTICS_FIELD(recovering),
+	STATISTICS_FIELD(num_recoveries),
+	STATISTICS_FIELD(client_packets_sent),
+	STATISTICS_FIELD(client_packets_recv),
+	STATISTICS_FIELD(node_packets_sent),
+	STATISTICS_FIELD(node_packets_recv),
+	STATISTICS_FIELD(keepalive_packets_sent),
+	STATISTICS_FIELD(keepalive_packets_recv),
+	STATISTICS_FIELD(node.req_call),
+	STATISTICS_FIELD(node.reply_call),
+	STATISTICS_FIELD(node.req_dmaster),
+	STATISTICS_FIELD(node.reply_dmaster),
+	STATISTICS_FIELD(node.reply_error),
+	STATISTICS_FIELD(node.req_message),
+	STATISTICS_FIELD(node.req_control),
+	STATISTICS_FIELD(node.reply_control),
+	STATISTICS_FIELD(client.req_call),
+	STATISTICS_FIELD(client.req_message),
+	STATISTICS_FIELD(client.req_control),
+	STATISTICS_FIELD(timeouts.call),
+	STATISTICS_FIELD(timeouts.control),
+	STATISTICS_FIELD(timeouts.traverse),
+	STATISTICS_FIELD(locks.num_calls),
+	STATISTICS_FIELD(locks.num_current),
+	STATISTICS_FIELD(locks.num_pending),
+	STATISTICS_FIELD(locks.num_failed),
+	STATISTICS_FIELD(total_calls),
+	STATISTICS_FIELD(pending_calls),
+	STATISTICS_FIELD(childwrite_calls),
+	STATISTICS_FIELD(pending_childwrite_calls),
+	STATISTICS_FIELD(memory_used),
+	STATISTICS_FIELD(max_hop_count),
+	STATISTICS_FIELD(total_ro_delegations),
+	STATISTICS_FIELD(total_ro_revokes),
+};
+
+#define LATENCY_AVG(v)	((v).num ? (v).total / (v).num : 0.0 )
+
+static void print_statistics_machine(struct ctdb_statistics *s,
+				     bool show_header)
+{
+	int i;
+
+	if (show_header) {
+		printf("CTDB version%s", options.sep);
+		printf("Current time of statistics%s", options.sep);
+		printf("Statistics collected since%s", options.sep);
+		for (i=0; i<ARRAY_SIZE(stats_fields); i++) {
+			printf("%s%s", stats_fields[i].name, options.sep);
+		}
+		printf("num_reclock_ctdbd_latency%s", options.sep);
+		printf("min_reclock_ctdbd_latency%s", options.sep);
+		printf("avg_reclock_ctdbd_latency%s", options.sep);
+		printf("max_reclock_ctdbd_latency%s", options.sep);
+
+		printf("num_reclock_recd_latency%s", options.sep);
+		printf("min_reclock_recd_latency%s", options.sep);
+		printf("avg_reclock_recd_latency%s", options.sep);
+		printf("max_reclock_recd_latency%s", options.sep);
+
+		printf("num_call_latency%s", options.sep);
+		printf("min_call_latency%s", options.sep);
+		printf("avg_call_latency%s", options.sep);
+		printf("max_call_latency%s", options.sep);
+
+		printf("num_lockwait_latency%s", options.sep);
+		printf("min_lockwait_latency%s", options.sep);
+		printf("avg_lockwait_latency%s", options.sep);
+		printf("max_lockwait_latency%s", options.sep);
+
+		printf("num_childwrite_latency%s", options.sep);
+		printf("min_childwrite_latency%s", options.sep);
+		printf("avg_childwrite_latency%s", options.sep);
+		printf("max_childwrite_latency%s", options.sep);
+		printf("\n");
+	}
+
+	printf("%u%s", CTDB_PROTOCOL, options.sep);
+	printf("%u%s", (uint32_t)s->statistics_current_time.tv_sec, options.sep);
+	printf("%u%s", (uint32_t)s->statistics_start_time.tv_sec, options.sep);
+	for (i=0;i<ARRAY_SIZE(stats_fields);i++) {
+		printf("%u%s",
+		       *(uint32_t *)(stats_fields[i].offset+(uint8_t *)s),
+		       options.sep);
+	}
+	printf("%u%s", s->reclock.ctdbd.num, options.sep);
+	printf("%.6f%s", s->reclock.ctdbd.min, options.sep);
+	printf("%.6f%s", LATENCY_AVG(s->reclock.ctdbd), options.sep);
+	printf("%.6f%s", s->reclock.ctdbd.max, options.sep);
+
+	printf("%u%s", s->reclock.recd.num, options.sep);
+	printf("%.6f%s", s->reclock.recd.min, options.sep);
+	printf("%.6f%s", LATENCY_AVG(s->reclock.recd), options.sep);
+	printf("%.6f%s", s->reclock.recd.max, options.sep);
+
+	printf("%d%s", s->call_latency.num, options.sep);
+	printf("%.6f%s", s->call_latency.min, options.sep);
+	printf("%.6f%s", LATENCY_AVG(s->call_latency), options.sep);
+	printf("%.6f%s", s->call_latency.max, options.sep);
+
+	printf("%d%s", s->childwrite_latency.num, options.sep);
+	printf("%.6f%s", s->childwrite_latency.min, options.sep);
+	printf("%.6f%s", LATENCY_AVG(s->childwrite_latency), options.sep);
+	printf("%.6f%s", s->childwrite_latency.max, options.sep);
+	printf("\n");
+}
+
+static void print_statistics(struct ctdb_statistics *s)
+{
+	int tmp, days, hours, minutes, seconds;
+	int i;
+	const char *prefix = NULL;
+	int preflen = 0;
+
+	tmp = s->statistics_current_time.tv_sec -
+	      s->statistics_start_time.tv_sec;
+	seconds = tmp % 60; tmp /= 60;
+	minutes = tmp % 60; tmp /= 60;
+	hours   = tmp % 24; tmp /= 24;
+	days    = tmp;
+
+	printf("CTDB version %u\n", CTDB_PROTOCOL);
+	printf("Current time of statistics  :                %s",
+	       ctime(&s->statistics_current_time.tv_sec));
+	printf("Statistics collected since  : (%03d %02d:%02d:%02d) %s",
+	       days, hours, minutes, seconds,
+	       ctime(&s->statistics_start_time.tv_sec));
+
+	for (i=0; i<ARRAY_SIZE(stats_fields); i++) {
+		if (strchr(stats_fields[i].name, '.') != NULL) {
+			preflen = strcspn(stats_fields[i].name, ".") + 1;
+			if (! prefix ||
+			    strncmp(prefix, stats_fields[i].name, preflen) != 0) {
+				prefix = stats_fields[i].name;
+				printf(" %*.*s\n", preflen-1, preflen-1,
+				       stats_fields[i].name);
+			}
+		} else {
+			preflen = 0;
+		}
+		printf(" %*s%-22s%*s%10u\n", preflen ? 4 : 0, "",
+		       stats_fields[i].name+preflen, preflen ? 0 : 4, "",
+		       *(uint32_t *)(stats_fields[i].offset+(uint8_t *)s));
+	}
+
+	printf(" hop_count_buckets:");
+	for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+		printf(" %d", s->hop_count_bucket[i]);
+	}
+	printf("\n");
+	printf(" lock_buckets:");
+	for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+		printf(" %d", s->locks.buckets[i]);
+	}
+	printf("\n");
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "locks_latency      MIN/AVG/MAX",
+	       s->locks.latency.min, LATENCY_AVG(s->locks.latency),
+	       s->locks.latency.max, s->locks.latency.num);
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "reclock_ctdbd      MIN/AVG/MAX",
+	       s->reclock.ctdbd.min, LATENCY_AVG(s->reclock.ctdbd),
+	       s->reclock.ctdbd.max, s->reclock.ctdbd.num);
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "reclock_recd       MIN/AVG/MAX",
+	       s->reclock.recd.min, LATENCY_AVG(s->reclock.recd),
+	       s->reclock.recd.max, s->reclock.recd.num);
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "call_latency       MIN/AVG/MAX",
+	       s->call_latency.min, LATENCY_AVG(s->call_latency),
+	       s->call_latency.max, s->call_latency.num);
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "childwrite_latency MIN/AVG/MAX",
+	       s->childwrite_latency.min,
+	       LATENCY_AVG(s->childwrite_latency),
+	       s->childwrite_latency.max, s->childwrite_latency.num);
+}
+
+static int control_statistics(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	struct ctdb_statistics *stats;
+	int ret;
+
+	if (argc != 0) {
+		usage("statistics");
+	}
+
+	ret = ctdb_ctrl_statistics(mem_ctx, ctdb->ev, ctdb->client,
+				   ctdb->cmd_pnn, TIMEOUT(), &stats);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (options.machinereadable) {
+		print_statistics_machine(stats, true);
+	} else {
+		print_statistics(stats);
+	}
+
+	return 0;
+}
+
+static int control_statistics_reset(TALLOC_CTX *mem_ctx,
+				    struct ctdb_context *ctdb,
+				    int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("statisticsreset");
+	}
+
+	ret = ctdb_ctrl_statistics_reset(mem_ctx, ctdb->ev, ctdb->client,
+					 ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_stats(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	struct ctdb_statistics_list *slist;
+	int ret, count = 0, i;
+	bool show_header = true;
+
+	if (argc > 1) {
+		usage("stats");
+	}
+
+	if (argc == 1) {
+		count = atoi(argv[0]);
+	}
+
+	ret = ctdb_ctrl_get_stat_history(mem_ctx, ctdb->ev, ctdb->client,
+					 ctdb->cmd_pnn, TIMEOUT(), &slist);
+	if (ret != 0) {
+		return ret;
+	}
+
+	for (i=0; i<slist->num; i++) {
+		if (slist->stats[i].statistics_start_time.tv_sec == 0) {
+			continue;
+		}
+		if (options.machinereadable == 1) {
+			print_statistics_machine(&slist->stats[i],
+						 show_header);
+			show_header = false;
+		} else {
+			print_statistics(&slist->stats[i]);
+		}
+		if (count > 0 && i == count) {
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static void print_ip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		     struct ctdb_public_ip_list *ips,
+		     struct ctdb_public_ip_info **ipinfo,
+		     bool all_nodes)
+{
+	int i, j;
+	char *conf, *avail, *active;
+
+	if (options.machinereadable == 1) {
+		printf("%s%s%s%s%s", options.sep,
+		       "Public IP", options.sep,
+		       "Node", options.sep);
+		if (options.verbose == 1) {
+			printf("%s%s%s%s%s%s\n",
+			       "ActiveInterfaces", options.sep,
+			       "AvailableInterfaces", options.sep,
+			       "ConfiguredInterfaces", options.sep);
+		} else {
+			printf("\n");
+		}
+	} else {
+		if (all_nodes) {
+			printf("Public IPs on ALL nodes\n");
+		} else {
+			printf("Public IPs on node %u\n", ctdb->cmd_pnn);
+		}
+	}
+
+	/* IPs are reverse sorted */
+	for (i=ips->num-1; i>=0; i--) {
+
+		if (options.machinereadable == 1) {
+			printf("%s%s%s%d%s", options.sep,
+			       ctdb_sock_addr_to_string(
+					mem_ctx, &ips->ip[i].addr),
+			       options.sep,
+			       (int)ips->ip[i].pnn, options.sep);
+		} else {
+			printf("%s", ctdb_sock_addr_to_string(
+						mem_ctx, &ips->ip[i].addr));
+		}
+
+		if (options.verbose == 0) {
+			if (options.machinereadable == 1) {
+				printf("\n");
+			} else {
+				printf(" %d\n", (int)ips->ip[i].pnn);
+			}
+			continue;
+		}
+
+		conf = NULL;
+		avail = NULL;
+		active = NULL;
+
+		for (j=0; j<ipinfo[i]->ifaces->num; j++) {
+			struct ctdb_iface *iface;
+
+			iface = &ipinfo[i]->ifaces->iface[j];
+			if (conf == NULL) {
+				conf = talloc_strdup(mem_ctx, iface->name);
+			} else {
+				conf = talloc_asprintf_append(
+						mem_ctx, ",%s", iface->name);
+			}
+
+			if (ipinfo[i]->active_idx == j) {
+				active = iface->name;
+			}
+
+			if (iface->link_state == 0) {
+				continue;
+			}
+
+			if (avail == NULL) {
+				avail = talloc_strdup(mem_ctx, iface->name);
+			} else {
+				avail = talloc_asprintf_append(
+						mem_ctx, ",%s", iface->name);
+			}
+		}
+
+		if (options.machinereadable == 1) {
+			printf("%s%s%s%s%s%s\n",
+			       active ? active : "", options.sep,
+			       avail ? avail : "", options.sep,
+			       conf ? conf : "", options.sep);
+		} else {
+			printf(" node[%u] active[%s] available[%s] configured[%s]\n",
+			       ips->ip[i].pnn, active ? active : "",
+			       avail ? avail : "", conf ? conf : "");
+		}
+	}
+}
+
+static int collect_ips(uint8_t *keybuf, size_t keylen, uint8_t *databuf,
+		       size_t datalen, void *private_data)
+{
+	struct ctdb_public_ip_list *ips = talloc_get_type_abort(
+		private_data, struct ctdb_public_ip_list);
+	struct ctdb_public_ip *ip;
+
+	ip = (struct ctdb_public_ip *)databuf;
+	ips->ip[ips->num] = *ip;
+	ips->num += 1;
+
+	return 0;
+}
+
+static int get_all_public_ips(struct ctdb_context *ctdb, TALLOC_CTX *mem_ctx,
+			      struct ctdb_public_ip_list **out)
+{
+	struct ctdb_node_map *nodemap;
+	struct ctdb_public_ip_list *ips;
+	struct db_hash_context *ipdb;
+	uint32_t *pnn_list;
+	int ret, count, i, j;
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	ret = db_hash_init(mem_ctx, "ips", 101, DB_HASH_COMPLEX, &ipdb);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	count = list_of_active_nodes(nodemap, CTDB_UNKNOWN_PNN, mem_ctx,
+				     &pnn_list);
+	if (count <= 0) {
+		goto failed;
+	}
+
+	for (i=0; i<count; i++) {
+		ret = ctdb_ctrl_get_public_ips(mem_ctx, ctdb->ev, ctdb->client,
+					       pnn_list[i], TIMEOUT(), &ips);
+		if (ret != 0) {
+			goto failed;
+		}
+
+		for (j=0; j<ips->num; j++) {
+			struct ctdb_public_ip ip;
+
+			ip.pnn = ips->ip[j].pnn;
+			ip.addr = ips->ip[j].addr;
+
+			ret = db_hash_add(ipdb, (uint8_t *)&ip.addr,
+					  sizeof(ip.addr),
+					  (uint8_t *)&ip, sizeof(ip));
+			if (ret != 0) {
+				goto failed;
+			}
+		}
+
+		TALLOC_FREE(ips);
+	}
+
+	talloc_free(pnn_list);
+
+	ret = db_hash_traverse(ipdb, NULL, NULL, &count);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);
+	if (ips == NULL) {
+		goto failed;
+	}
+
+	ips->ip = talloc_array(ips, struct ctdb_public_ip, count);
+	if (ips->ip == NULL) {
+		goto failed;
+	}
+
+	ret = db_hash_traverse(ipdb, collect_ips, ips, &count);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	if (count != ips->num) {
+		goto failed;
+	}
+
+	talloc_free(ipdb);
+
+	*out = ips;
+	return 0;
+
+failed:
+	talloc_free(ipdb);
+	return 1;
+}
+
+static int control_ip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		      int argc, const char **argv)
+{
+	struct ctdb_public_ip_list *ips;
+	struct ctdb_public_ip_info **ipinfo;
+	int ret, i;
+	bool do_all = false;
+
+	if (argc > 1) {
+		usage("ip");
+	}
+
+	if (argc == 1) {
+		if (strcmp(argv[0], "all") == 0) {
+			do_all = true;
+		} else {
+			usage("ip");
+		}
+	}
+
+	if (do_all) {
+		ret = get_all_public_ips(ctdb, mem_ctx, &ips);
+	} else {
+		ret = ctdb_ctrl_get_public_ips(mem_ctx, ctdb->ev, ctdb->client,
+					       ctdb->cmd_pnn, TIMEOUT(), &ips);
+	}
+	if (ret != 0) {
+		return ret;
+	}
+
+	ipinfo = talloc_array(mem_ctx, struct ctdb_public_ip_info *, ips->num);
+	if (ipinfo == NULL) {
+		return 1;
+	}
+
+	for (i=0; i<ips->num; i++) {
+		uint32_t pnn;
+		if (do_all) {
+			pnn = ips->ip[i].pnn;
+		} else {
+			pnn = ctdb->cmd_pnn;
+		}
+		ret = ctdb_ctrl_get_public_ip_info(mem_ctx, ctdb->ev,
+						   ctdb->client, pnn,
+						   TIMEOUT(), &ips->ip[i].addr,
+						   &ipinfo[i]);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
+	print_ip(mem_ctx, ctdb, ips, ipinfo, do_all);
+	return 0;
+}
+
+static int control_ipinfo(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_public_ip_info *ipinfo;
+	ctdb_sock_addr addr;
+	int ret, i;
+
+	if (argc != 1) {
+		usage("ipinfo");
+	}
+
+	if (! parse_ip(argv[0], NULL, 0, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_public_ip_info(mem_ctx, ctdb->ev, ctdb->client,
+					   ctdb->cmd_pnn, TIMEOUT(), &addr,
+					   &ipinfo);
+	if (ret != 0) {
+		if (ret == -1) {
+			printf("Node %u does not know about IP %s\n",
+			       ctdb->cmd_pnn, argv[0]);
+		}
+		return ret;
+	}
+
+	printf("Public IP[%s] info on node %u\n",
+	       ctdb_sock_addr_to_string(mem_ctx, &ipinfo->ip.addr),
+					ctdb->cmd_pnn);
+
+	printf("IP:%s\nCurrentNode:%u\nNumInterfaces:%u\n",
+	       ctdb_sock_addr_to_string(mem_ctx, &ipinfo->ip.addr),
+	       ipinfo->ip.pnn, ipinfo->ifaces->num);
+
+	for (i=0; i<ipinfo->ifaces->num; i++) {
+		struct ctdb_iface *iface;
+
+		iface = &ipinfo->ifaces->iface[i];
+		iface->name[CTDB_IFACE_SIZE] = '\0';
+		printf("Interface[%u]: Name:%s Link:%s References:%u%s\n",
+		       i+1, iface->name,
+		       iface->link_state == 0 ? "down" : "up",
+		       iface->references,
+		       (i == ipinfo->active_idx) ? " (active)" : "");
+	}
+
+	return 0;
+}
+
+static int control_ifaces(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_iface_list *ifaces;
+	int ret, i;
+
+	if (argc != 0) {
+		usage("ifaces");
+	}
+
+	ret = ctdb_ctrl_get_ifaces(mem_ctx, ctdb->ev, ctdb->client,
+				   ctdb->cmd_pnn, TIMEOUT(), &ifaces);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (ifaces->num == 0) {
+		printf("No interfaces configured on node %u\n",
+		       ctdb->cmd_pnn);
+		return 0;
+	}
+
+	if (options.machinereadable) {
+		printf("%s%s%s%s%s%s%s\n", options.sep,
+		       "Name", options.sep,
+		       "LinkStatus", options.sep,
+		       "References", options.sep);
+	} else {
+		printf("Interfaces on node %u\n", ctdb->cmd_pnn);
+	}
+
+	for (i=0; i<ifaces->num; i++) {
+		if (options.machinereadable) {
+			printf("%s%s%s%u%s%u%s\n", options.sep,
+			       ifaces->iface[i].name, options.sep,
+			       ifaces->iface[i].link_state, options.sep,
+			       ifaces->iface[i].references, options.sep);
+		} else {
+			printf("name:%s link:%s references:%u\n",
+			       ifaces->iface[i].name,
+			       ifaces->iface[i].link_state ? "up" : "down",
+			       ifaces->iface[i].references);
+		}
+	}
+
+	return 0;
+}
+
+static int control_setifacelink(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	struct ctdb_iface_list *ifaces;
+	struct ctdb_iface *iface;
+	int ret, i;
+
+	if (argc != 2) {
+		usage("setifacelink");
+	}
+
+	if (strlen(argv[0]) > CTDB_IFACE_SIZE) {
+		fprintf(stderr, "Interface name '%s' too long\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_ifaces(mem_ctx, ctdb->ev, ctdb->client,
+				   ctdb->cmd_pnn, TIMEOUT(), &ifaces);
+	if (ret != 0) {
+		fprintf(stderr,
+			"Failed to get interface information from node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	iface = NULL;
+	for (i=0; i<ifaces->num; i++) {
+		if (strcmp(ifaces->iface[i].name, argv[0]) == 0) {
+			iface = &ifaces->iface[i];
+			break;
+		}
+	}
+
+	if (iface == NULL) {
+		printf("Interface %s not configured on node %u\n",
+		       argv[0], ctdb->cmd_pnn);
+		return 1;
+	}
+
+	if (strcmp(argv[1], "up") == 0) {
+		iface->link_state = 1;
+	} else if (strcmp(argv[1], "down") == 0) {
+		iface->link_state = 0;
+	} else {
+		usage("setifacelink");
+		return 1;
+	}
+
+	iface->references = 0;
+
+	ret = ctdb_ctrl_set_iface_link_state(mem_ctx, ctdb->ev, ctdb->client,
+					     ctdb->cmd_pnn, TIMEOUT(), iface);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_process_exists(TALLOC_CTX *mem_ctx,
+				  struct ctdb_context *ctdb,
+				  int argc, const char **argv)
+{
+	pid_t pid;
+	int ret, status;
+
+	if (argc != 1) {
+		usage("process-exists");
+	}
+
+	pid = atoi(argv[0]);
+	ret = ctdb_ctrl_process_exists(mem_ctx, ctdb->ev, ctdb->client,
+				       ctdb->cmd_pnn, TIMEOUT(), pid, &status);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (status == 0) {
+		printf("PID %u exists\n", pid);
+	} else {
+		printf("PID %u does not exist\n", pid);
+	}
+	return status;
+}
+
+static int control_getdbmap(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	struct ctdb_dbid_map *dbmap;
+	int ret, i;
+
+	if (argc != 0) {
+		usage("getdbmap");
+	}
+
+	ret = ctdb_ctrl_get_dbmap(mem_ctx, ctdb->ev, ctdb->client,
+				  ctdb->cmd_pnn, TIMEOUT(), &dbmap);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (options.machinereadable == 1) {
+		printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+		       options.sep,
+		       "ID", options.sep,
+		       "Name", options.sep,
+		       "Path", options.sep,
+		       "Persistent", options.sep,
+		       "Sticky", options.sep,
+		       "Unhealthy", options.sep,
+		       "Readonly", options.sep);
+	} else {
+		printf("Number of databases:%d\n", dbmap->num);
+	}
+
+	for (i=0; i<dbmap->num; i++) {
+		const char *name;
+		const char *path;
+		const char *health;
+		bool persistent;
+		bool readonly;
+		bool sticky;
+		uint32_t db_id;
+
+		db_id = dbmap->dbs[i].db_id;
+
+		ret = ctdb_ctrl_get_dbname(mem_ctx, ctdb->ev, ctdb->client,
+					   ctdb->cmd_pnn, TIMEOUT(), db_id,
+					   &name);
+		if (ret != 0) {
+			return ret;
+		}
+
+		ret = ctdb_ctrl_getdbpath(mem_ctx, ctdb->ev, ctdb->client,
+					  ctdb->cmd_pnn, TIMEOUT(), db_id,
+					  &path);
+		if (ret != 0) {
+			return ret;
+		}
+
+		ret = ctdb_ctrl_db_get_health(mem_ctx, ctdb->ev, ctdb->client,
+					      ctdb->cmd_pnn, TIMEOUT(), db_id,
+					      &health);
+		if (ret != 0) {
+			return ret;
+		}
+
+		persistent = dbmap->dbs[i].flags & CTDB_DB_FLAGS_PERSISTENT;
+		readonly = dbmap->dbs[i].flags & CTDB_DB_FLAGS_READONLY;
+		sticky = dbmap->dbs[i].flags & CTDB_DB_FLAGS_STICKY;
+
+		if (options.machinereadable == 1) {
+			printf("%s0x%08X%s%s%s%s%s%d%s%d%s%d%s%d%s\n",
+			       options.sep,
+			       db_id, options.sep,
+			       name, options.sep,
+			       path, options.sep,
+			       !! (persistent), options.sep,
+			       !! (sticky), options.sep,
+			       !! (health), options.sep,
+			       !! (readonly), options.sep);
+		} else {
+			printf("dbid:0x%08x name:%s path:%s%s%s%s%s\n",
+			       db_id, name, path,
+			       persistent ? " PERSISTENT" : "",
+			       sticky ? " STICKY" : "",
+			       readonly ? " READONLY" : "",
+			       health ? " UNHEALTHY" : "");
+		}
+
+		talloc_free(discard_const(name));
+		talloc_free(discard_const(path));
+		talloc_free(discard_const(health));
+	}
+
+	return 0;
+}
+
+static int control_getdbstatus(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	uint32_t db_id;
+	const char *db_name, *db_path, *db_health;
+	uint8_t db_flags;
+	int ret;
+
+	if (argc != 1) {
+		usage("getdbstatus");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	ret = ctdb_ctrl_getdbpath(mem_ctx, ctdb->ev, ctdb->client,
+				  ctdb->cmd_pnn, TIMEOUT(), db_id,
+				  &db_path);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_ctrl_db_get_health(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), db_id,
+				      &db_health);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("dbid: 0x%08x\nname: %s\npath: %s\n", db_id, db_name, db_path);
+	printf("PERSISTENT: %s\nSTICKY: %s\nREADONLY: %s\nHEALTH: %s\n",
+	       (db_flags & CTDB_DB_FLAGS_PERSISTENT ? "yes" : "no"),
+	       (db_flags & CTDB_DB_FLAGS_STICKY ? "yes" : "no"),
+	       (db_flags & CTDB_DB_FLAGS_READONLY ? "yes" : "no"),
+	       (db_health ? db_health : "OK"));
+	return 0;
+}
+
+struct dump_record_state {
+	uint32_t count;
+};
+
+#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x)))
+
+static void dump_tdb_data(const char *name, TDB_DATA val)
+{
+	int i;
+
+	fprintf(stdout, "%s(%zu) = \"", name, val.dsize);
+	for (i=0; i<val.dsize; i++) {
+		if (ISASCII(val.dptr[i])) {
+			fprintf(stdout, "%c", val.dptr[i]);
+		} else {
+			fprintf(stdout, "\\%02X", val.dptr[i]);
+		}
+	}
+	fprintf(stdout, "\"\n");
+}
+
+static void dump_ltdb_header(struct ctdb_ltdb_header *header)
+{
+	fprintf(stdout, "dmaster: %u\n", header->dmaster);
+	fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn);
+	fprintf(stdout, "flags: 0x%08x", header->flags);
+	if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) {
+		fprintf(stdout, " MIGRATED_WITH_DATA");
+	}
+	if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) {
+		fprintf(stdout, " VACUUM_MIGRATED");
+	}
+	if (header->flags & CTDB_REC_FLAG_AUTOMATIC) {
+		fprintf(stdout, " AUTOMATIC");
+	}
+	if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) {
+		fprintf(stdout, " RO_HAVE_DELEGATIONS");
+	}
+	if (header->flags & CTDB_REC_RO_HAVE_READONLY) {
+		fprintf(stdout, " RO_HAVE_READONLY");
+	}
+	if (header->flags & CTDB_REC_RO_REVOKING_READONLY) {
+		fprintf(stdout, " RO_REVOKING_READONLY");
+	}
+	if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) {
+		fprintf(stdout, " RO_REVOKE_COMPLETE");
+	}
+	fprintf(stdout, "\n");
+
+}
+
+static int dump_record(uint32_t reqid, struct ctdb_ltdb_header *header,
+		       TDB_DATA key, TDB_DATA data, void *private_data)
+{
+	struct dump_record_state *state =
+		(struct dump_record_state *)private_data;
+
+	state->count += 1;
+
+	dump_tdb_data("key", key);
+	dump_ltdb_header(header);
+	dump_tdb_data("data", data);
+	fprintf(stdout, "\n");
+
+	return 0;
+}
+
+struct traverse_state {
+	TALLOC_CTX *mem_ctx;
+	bool done;
+	ctdb_rec_parser_func_t func;
+	struct dump_record_state sub_state;
+};
+
+static void traverse_handler(uint64_t srvid, TDB_DATA data, void *private_data)
+{
+	struct traverse_state *state = (struct traverse_state *)private_data;
+	struct ctdb_rec_data *rec;
+	struct ctdb_ltdb_header header;
+	int ret;
+
+	ret = ctdb_rec_data_pull(data.dptr, data.dsize, state->mem_ctx, &rec);
+	if (ret != 0) {
+		return;
+	}
+
+	if (rec->key.dsize == 0 && rec->data.dsize == 0) {
+		talloc_free(rec);
+		/* end of traverse */
+		state->done = true;
+		return;
+	}
+
+	ret = ctdb_ltdb_header_extract(&rec->data, &header);
+	if (ret != 0) {
+		talloc_free(rec);
+		return;
+	}
+
+	if (rec->data.dsize == 0) {
+		return;
+	}
+
+	ret = state->func(rec->reqid, &header, rec->key, rec->data,
+			  &state->sub_state);
+	talloc_free(rec);
+	if (ret != 0) {
+		state->done = true;
+	}
+}
+
+static int control_catdb(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	struct ctdb_db_context *db;
+	const char *db_name;
+	uint32_t db_id;
+	uint8_t db_flags;
+	struct ctdb_traverse_start_ext traverse;
+	struct traverse_state state;
+	int ret;
+
+	if (argc != 1) {
+		usage("catdb");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	/* Valgrind fix */
+	ZERO_STRUCT(traverse);
+
+	traverse.db_id = db_id;
+	traverse.reqid = 0;
+	traverse.srvid = next_srvid(ctdb);
+	traverse.withemptyrecords = false;
+
+	state.mem_ctx = mem_ctx;
+	state.done = false;
+	state.func = dump_record;
+	state.sub_state.count = 0;
+
+	ret = ctdb_client_set_message_handler(ctdb->ev, ctdb->client,
+					      traverse.srvid,
+					      traverse_handler, &state);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_ctrl_traverse_start_ext(mem_ctx, ctdb->ev, ctdb->client,
+					   ctdb->cmd_pnn, TIMEOUT(),
+					   &traverse);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ctdb_client_wait(ctdb->ev, &state.done);
+
+	printf("Dumped %u records\n", state.sub_state.count);
+
+	ret = ctdb_client_remove_message_handler(ctdb->ev, ctdb->client,
+						 traverse.srvid, &state);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_cattdb(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct ctdb_db_context *db;
+	const char *db_name;
+	uint32_t db_id;
+	uint8_t db_flags;
+	struct dump_record_state state;
+	int ret;
+
+	if (argc != 1) {
+		usage("catdb");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	state.count = 0;
+	ret = ctdb_db_traverse(db, true, true, dump_record, &state);
+
+	printf("Dumped %u record(s)\n", state.count);
+
+	return ret;
+}
+
+static int control_getmonmode(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	int mode, ret;
+
+	if (argc != 0) {
+		usage("getmonmode");
+	}
+
+	ret = ctdb_ctrl_get_monmode(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), &mode);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("%s\n",
+	       (mode == CTDB_MONITORING_ENABLED) ? "ENABLED" : "DISABLED");
+	return 0;
+}
+
+static int control_getcapabilities(TALLOC_CTX *mem_ctx,
+				   struct ctdb_context *ctdb,
+				   int argc, const char **argv)
+{
+	uint32_t caps;
+	int ret;
+
+	if (argc != 0) {
+		usage("getcapabilities");
+	}
+
+	ret = ctdb_ctrl_get_capabilities(mem_ctx, ctdb->ev, ctdb->client,
+					 ctdb->cmd_pnn, TIMEOUT(), &caps);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (options.machinereadable == 1) {
+		printf("%s%s%s%s%s\n",
+		       options.sep,
+		       "RECMASTER", options.sep,
+		       "LMASTER", options.sep);
+		printf("%s%d%s%d%s\n", options.sep,
+		       !! (caps & CTDB_CAP_RECMASTER), options.sep,
+		       !! (caps & CTDB_CAP_LMASTER), options.sep);
+	} else {
+		printf("RECMASTER: %s\n",
+		       (caps & CTDB_CAP_RECMASTER) ? "YES" : "NO");
+		printf("LMASTER: %s\n",
+		       (caps & CTDB_CAP_LMASTER) ? "YES" : "NO");
+	}
+
+	return 0;
+}
+
+static int control_pnn(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		       int argc, const char **argv)
+{
+	printf("%u\n", ctdb_client_pnn(ctdb->client));
+	return 0;
+}
+
+static int control_lvs(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		       int argc, const char **argv)
+{
+	char *t, *lvs_helper = NULL;
+
+	if (argc != 1) {
+		usage("lvs");
+	}
+
+	t = getenv("CTDB_LVS_HELPER");
+	if (t != NULL) {
+		lvs_helper = talloc_strdup(mem_ctx, t);
+	} else {
+		lvs_helper = talloc_asprintf(mem_ctx, "%s/ctdb_lvs",
+					     CTDB_HELPER_BINDIR);
+	}
+
+	if (lvs_helper == NULL) {
+		fprintf(stderr, "Unable to set LVS helper\n");
+		return 1;
+	}
+
+	return run_helper("LVS helper", lvs_helper, argv[0]);
+}
+
+static int control_disable_monitor(TALLOC_CTX *mem_ctx,
+				   struct ctdb_context *ctdb,
+				   int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("disablemonitor");
+	}
+
+	ret = ctdb_ctrl_disable_monitor(mem_ctx, ctdb->ev, ctdb->client,
+					ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_enable_monitor(TALLOC_CTX *mem_ctx,
+				  struct ctdb_context *ctdb,
+				  int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("enablemonitor");
+	}
+
+	ret = ctdb_ctrl_enable_monitor(mem_ctx, ctdb->ev, ctdb->client,
+				       ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_setdebug(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	enum debug_level log_level;
+	int ret;
+	bool found;
+
+	if (argc != 1) {
+		usage("setdebug");
+	}
+
+	found = debug_level_parse(argv[0], &log_level);
+	if (! found) {
+		fprintf(stderr,
+			"Invalid debug level '%s'. Valid levels are:\n",
+			argv[0]);
+		fprintf(stderr, "\tERROR | WARNING | NOTICE | INFO | DEBUG\n");
+		return 1;
+	}
+
+	ret = ctdb_ctrl_setdebug(mem_ctx, ctdb->ev, ctdb->client,
+				 ctdb->cmd_pnn, TIMEOUT(), log_level);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_getdebug(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	enum debug_level loglevel;
+	const char *log_str;
+	int ret;
+
+	if (argc != 0) {
+		usage("getdebug");
+	}
+
+	ret = ctdb_ctrl_getdebug(mem_ctx, ctdb->ev, ctdb->client,
+				 ctdb->cmd_pnn, TIMEOUT(), &loglevel);
+	if (ret != 0) {
+		return ret;
+	}
+
+	log_str = debug_level_to_string(loglevel);
+	printf("%s\n", log_str);
+
+	return 0;
+}
+
+static int control_attach(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	uint8_t db_flags = 0;
+	int ret;
+
+	if (argc < 1 || argc > 2) {
+		usage("attach");
+	}
+
+	db_name = argv[0];
+	if (argc == 2) {
+		if (strcmp(argv[1], "persistent") == 0) {
+			db_flags = CTDB_DB_FLAGS_PERSISTENT;
+		} else if (strcmp(argv[1], "readonly") == 0) {
+			db_flags = CTDB_DB_FLAGS_READONLY;
+		} else if (strcmp(argv[1], "sticky") == 0) {
+			db_flags = CTDB_DB_FLAGS_STICKY;
+		} else {
+			usage("attach");
+		}
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, NULL);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_detach(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	uint32_t db_id;
+	uint8_t db_flags;
+	struct ctdb_node_map *nodemap;
+	int recmode;
+	int ret, ret2, i;
+
+	if (argc < 1) {
+		usage("detach");
+	}
+
+	ret = ctdb_ctrl_get_recmode(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), &recmode);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (recmode == CTDB_RECOVERY_ACTIVE) {
+		fprintf(stderr, "Database cannot be detached"
+				" when recovery is active\n");
+		return 1;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	for (i=0; i<nodemap->num; i++) {
+		uint32_t value;
+
+		if (nodemap->node[i].flags & NODE_FLAGS_DISCONNECTED) {
+			continue;
+		}
+		if (nodemap->node[i].flags & NODE_FLAGS_DELETED) {
+			continue;
+		}
+		if (nodemap->node[i].flags & NODE_FLAGS_INACTIVE) {
+			fprintf(stderr, "Database cannot be detached on"
+				" inactive (stopped or banned) node %u\n",
+				nodemap->node[i].pnn);
+			return 1;
+		}
+
+		ret = ctdb_ctrl_get_tunable(mem_ctx, ctdb->ev, ctdb->client,
+					    nodemap->node[i].pnn, TIMEOUT(),
+					    "AllowClientDBAttach", &value);
+		if (ret != 0) {
+			fprintf(stderr,
+				"Unable to get tunable AllowClientDBAttach"
+			        " from node %u\n", nodemap->node[i].pnn);
+			return ret;
+		}
+
+		if (value == 1) {
+			fprintf(stderr,
+				"Database access is still active on node %u."
+			        " Set AllowclientDBAttach=0 on all nodes.\n",
+				nodemap->node[i].pnn);
+			return 1;
+		}
+	}
+
+	ret2 = 0;
+	for (i=0; i<argc; i++) {
+		if (! db_exists(mem_ctx, ctdb, argv[i], &db_id, &db_name,
+				&db_flags)) {
+			continue;
+		}
+
+		if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+			fprintf(stderr,
+			        "Persistent database %s cannot be detached\n",
+				argv[0]);
+			return 1;
+		}
+
+		ret = ctdb_detach(mem_ctx, ctdb->ev, ctdb->client,
+				  TIMEOUT(), db_id);
+		if (ret != 0) {
+			fprintf(stderr, "Database %s detach failed\n", db_name);
+			ret2 = ret;
+		}
+	}
+
+	return ret2;
+}
+
+static int control_dumpmemory(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	const char *mem_str;
+	ssize_t n;
+	int ret;
+
+	ret = ctdb_ctrl_dump_memory(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(), &mem_str);
+	if (ret != 0) {
+		return ret;
+	}
+
+	n = write(1, mem_str, strlen(mem_str)+1);
+	if (n < 0 || n != strlen(mem_str)+1) {
+		fprintf(stderr, "Failed to write talloc summary\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static void dump_memory(uint64_t srvid, TDB_DATA data, void *private_data)
+{
+	bool *done = (bool *)private_data;
+	ssize_t n;
+
+	n = write(1, data.dptr, data.dsize);
+	if (n < 0 || n != data.dsize) {
+		fprintf(stderr, "Failed to write talloc summary\n");
+	}
+
+	*done = true;
+}
+
+static int control_rddumpmemory(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	struct ctdb_srvid_message msg = { 0 };
+	int ret;
+	bool done = false;
+
+	msg.pnn = ctdb->pnn;
+	msg.srvid = next_srvid(ctdb);
+
+	ret = ctdb_client_set_message_handler(ctdb->ev, ctdb->client,
+					      msg.srvid, dump_memory, &done);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_message_mem_dump(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, &msg);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ctdb_client_wait(ctdb->ev, &done);
+	return 0;
+}
+
+static int control_getpid(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	pid_t pid;
+	int ret;
+
+	ret = ctdb_ctrl_get_pid(mem_ctx, ctdb->ev, ctdb->client,
+				ctdb->cmd_pnn, TIMEOUT(), &pid);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("%u\n", pid);
+	return 0;
+}
+
+static int check_flags(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		       const char *desc, uint32_t flag, bool set_flag)
+{
+	struct ctdb_node_map *nodemap;
+	bool flag_is_set;
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	flag_is_set = nodemap->node[ctdb->cmd_pnn].flags & flag;
+	if (set_flag == flag_is_set) {
+		if (set_flag) {
+			fprintf(stderr, "Node %u is already %s\n",
+				ctdb->cmd_pnn, desc);
+		} else {
+			fprintf(stderr, "Node %u is not %s\n",
+				ctdb->cmd_pnn, desc);
+		}
+		return 0;
+	}
+
+	return 1;
+}
+
+static void wait_for_flags(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   uint32_t flag, bool set_flag)
+{
+	struct ctdb_node_map *nodemap;
+	bool flag_is_set;
+
+	while (1) {
+		nodemap = get_nodemap(ctdb, true);
+		if (nodemap == NULL) {
+			fprintf(stderr,
+				"Failed to get nodemap, trying again\n");
+		}
+
+		flag_is_set = nodemap->node[ctdb->cmd_pnn].flags & flag;
+		if (flag_is_set == set_flag) {
+			break;
+		}
+
+		sleep(1);
+	}
+}
+
+struct ipreallocate_state {
+	int status;
+	bool done;
+};
+
+static void ipreallocate_handler(uint64_t srvid, TDB_DATA data,
+				 void *private_data)
+{
+	struct ipreallocate_state *state =
+		(struct ipreallocate_state *)private_data;
+
+	if (data.dsize != sizeof(int)) {
+		/* Ignore packet */
+		return;
+	}
+
+	state->status = *(int *)data.dptr;
+	state->done = true;
+}
+
+static int ipreallocate(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb)
+{
+	struct ctdb_srvid_message msg = { 0 };
+	struct ipreallocate_state state;
+	int ret;
+
+	msg.pnn = ctdb->pnn;
+	msg.srvid = next_srvid(ctdb);
+
+	state.done = false;
+	ret = ctdb_client_set_message_handler(ctdb->ev, ctdb->client,
+					      msg.srvid,
+					      ipreallocate_handler, &state);
+	if (ret != 0) {
+		return ret;
+	}
+
+	while (true) {
+		ret = ctdb_message_takeover_run(mem_ctx, ctdb->ev,
+						ctdb->client,
+						CTDB_BROADCAST_CONNECTED,
+						&msg);
+		if (ret != 0) {
+			goto fail;
+		}
+
+		ret = ctdb_client_wait_timeout(ctdb->ev, &state.done,
+					       TIMEOUT());
+		if (ret != 0) {
+			continue;
+		}
+
+		if (state.status >= 0) {
+			ret = 0;
+		} else {
+			ret = state.status;
+		}
+		break;
+	}
+
+fail:
+	ctdb_client_remove_message_handler(ctdb->ev, ctdb->client,
+					   msg.srvid, &state);
+	return ret;
+}
+
+static int control_disable(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("disable");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "disabled",
+			  NODE_FLAGS_PERMANENTLY_DISABLED, true);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ret = ctdb_ctrl_modflags(mem_ctx, ctdb->ev, ctdb->client,
+				 ctdb->cmd_pnn, TIMEOUT(),
+				 NODE_FLAGS_PERMANENTLY_DISABLED, 0);
+	if (ret != 0) {
+		fprintf(stderr,
+			"Failed to set DISABLED flag on node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_PERMANENTLY_DISABLED, true);
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_enable(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("enable");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "disabled",
+			  NODE_FLAGS_PERMANENTLY_DISABLED, false);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ret = ctdb_ctrl_modflags(mem_ctx, ctdb->ev, ctdb->client,
+				 ctdb->cmd_pnn, TIMEOUT(),
+				 0, NODE_FLAGS_PERMANENTLY_DISABLED);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to reset DISABLED flag on node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_PERMANENTLY_DISABLED, false);
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_stop(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("stop");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "stopped",
+			  NODE_FLAGS_STOPPED, true);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ret = ctdb_ctrl_stop_node(mem_ctx, ctdb->ev, ctdb->client,
+				  ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		fprintf(stderr, "Failed to stop node %u\n", ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_STOPPED, true);
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_continue(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("continue");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "stopped",
+			  NODE_FLAGS_STOPPED, false);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ret = ctdb_ctrl_continue_node(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		fprintf(stderr, "Failed to continue stopped node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_STOPPED, false);
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_ban(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		       int argc, const char **argv)
+{
+	struct ctdb_ban_state ban_state;
+	int ret;
+
+	if (argc != 1) {
+		usage("ban");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "banned",
+			  NODE_FLAGS_BANNED, true);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ban_state.pnn = ctdb->cmd_pnn;
+	ban_state.time = strtoul(argv[0], NULL, 0);
+
+	if (ban_state.time == 0) {
+		fprintf(stderr, "Ban time cannot be zero\n");
+		return EINVAL;
+	}
+
+	ret = ctdb_ctrl_set_ban_state(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &ban_state);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to ban node %u\n", ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_BANNED, true);
+	return ipreallocate(mem_ctx, ctdb);
+
+}
+
+static int control_unban(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	struct ctdb_ban_state ban_state;
+	int ret;
+
+	if (argc != 0) {
+		usage("unban");
+	}
+
+	ret = check_flags(mem_ctx, ctdb, "banned",
+			  NODE_FLAGS_BANNED, false);
+	if (ret == 0) {
+		return 0;
+	}
+
+	ban_state.pnn = ctdb->cmd_pnn;
+	ban_state.time = 0;
+
+	ret = ctdb_ctrl_set_ban_state(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &ban_state);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to unban node %u\n", ctdb->cmd_pnn);
+		return ret;
+	}
+
+	wait_for_flags(mem_ctx, ctdb, NODE_FLAGS_BANNED, false);
+	return ipreallocate(mem_ctx, ctdb);
+
+}
+
+static int control_shutdown(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 0) {
+		usage("shutdown");
+	}
+
+	ret = ctdb_ctrl_shutdown(mem_ctx, ctdb->ev, ctdb->client,
+				 ctdb->cmd_pnn, TIMEOUT());
+	if (ret != 0) {
+		fprintf(stderr, "Unable to shutdown node %u\n", ctdb->cmd_pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int get_generation(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  uint32_t *generation)
+{
+	uint32_t recmaster;
+	int recmode;
+	struct ctdb_vnn_map *vnnmap;
+	int ret;
+
+again:
+	ret = ctdb_ctrl_get_recmaster(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &recmaster);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to find recovery master\n");
+		return ret;
+	}
+
+	ret = ctdb_ctrl_get_recmode(mem_ctx, ctdb->ev, ctdb->client,
+				    recmaster, TIMEOUT(), &recmode);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get recovery mode from node %u\n",
+			recmaster);
+		return ret;
+	}
+
+	if (recmode == CTDB_RECOVERY_ACTIVE) {
+		sleep(1);
+		goto again;
+	}
+
+	ret = ctdb_ctrl_getvnnmap(mem_ctx, ctdb->ev, ctdb->client,
+				  recmaster, TIMEOUT(), &vnnmap);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get generation from node %u\n",
+			recmaster);
+		return ret;
+	}
+
+	if (vnnmap->generation == 1) {
+		talloc_free(vnnmap);
+		sleep(1);
+		goto again;
+	}
+
+	*generation = vnnmap->generation;
+	talloc_free(vnnmap);
+	return 0;
+}
+
+
+static int control_recover(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	uint32_t generation, next_generation;
+	int ret;
+
+	if (argc != 0) {
+		usage("recover");
+	}
+
+	ret = get_generation(mem_ctx, ctdb, &generation);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = ctdb_ctrl_set_recmode(mem_ctx, ctdb->ev, ctdb->client,
+				    ctdb->cmd_pnn, TIMEOUT(),
+				    CTDB_RECOVERY_ACTIVE);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to set recovery mode active\n");
+		return ret;
+	}
+
+	while (1) {
+		ret = get_generation(mem_ctx, ctdb, &next_generation);
+		if (ret != 0) {
+			fprintf(stderr,
+				"Failed to confirm end of recovery\n");
+			return ret;
+		}
+
+		if (next_generation != generation) {
+			break;
+		}
+
+		sleep (1);
+	}
+
+	return 0;
+}
+
+static int control_ipreallocate(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	if (argc != 0) {
+		usage("ipreallocate");
+	}
+
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_isnotrecmaster(TALLOC_CTX *mem_ctx,
+				  struct ctdb_context *ctdb,
+				  int argc, const char **argv)
+{
+	uint32_t recmaster;
+	int ret;
+
+	if (argc != 0) {
+		usage("isnotrecmaster");
+	}
+
+	ret = ctdb_ctrl_get_recmaster(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->pnn, TIMEOUT(), &recmaster);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get recmaster\n");
+		return ret;
+	}
+
+	if (recmaster != ctdb->pnn) {
+		printf("this node is not the recmaster\n");
+		return 1;
+	}
+
+	printf("this node is the recmaster\n");
+	return 0;
+}
+
+static int control_gratarp(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	struct ctdb_addr_info addr_info;
+	int ret;
+
+	if (argc != 2) {
+		usage("gratarp");
+	}
+
+	if (! parse_ip(argv[0], NULL, 0, &addr_info.addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+	addr_info.iface = argv[1];
+
+	ret = ctdb_ctrl_send_gratuitous_arp(mem_ctx, ctdb->ev, ctdb->client,
+					    ctdb->cmd_pnn, TIMEOUT(),
+					    &addr_info);
+	if (ret != 0) {
+		fprintf(stderr, "Unable to send gratuitous arp from node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_tickle(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	ctdb_sock_addr src, dst;
+	int ret;
+
+	if (argc != 0 && argc != 2) {
+		usage("tickle");
+	}
+
+	if (argc == 0) {
+		struct ctdb_connection *clist;
+		int count;
+		int i, num_failed;
+
+		ret = ctdb_parse_connections(stdin, mem_ctx, &count, &clist);
+		if (ret != 0) {
+			return ret;
+		}
+
+		num_failed = 0;
+		for (i=0; i<count; i++) {
+			ret = ctdb_sys_send_tcp(&clist[i].src,
+						&clist[i].dst,
+						0, 0, 0);
+			if (ret != 0) {
+				num_failed += 1;
+			}
+		}
+
+		if (num_failed > 0) {
+			fprintf(stderr, "Failed to send %d tickles\n",
+				num_failed);
+			return 1;
+		}
+
+		return 0;
+	}
+
+
+	if (! parse_ip_port(argv[0], &src)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	if (! parse_ip_port(argv[1], &dst)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[1]);
+		return 1;
+	}
+
+	ret = ctdb_sys_send_tcp(&src, &dst, 0, 0, 0);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to send tickle ack\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_gettickles(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	struct ctdb_tickle_list *tickles;
+	unsigned port = 0;
+	int ret, i;
+
+	if (argc < 1 || argc > 2) {
+		usage("gettickles");
+	}
+
+	if (argc == 2) {
+		port = strtoul(argv[1], NULL, 10);
+	}
+
+	if (! parse_ip(argv[0], NULL, port, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_tcp_tickle_list(mem_ctx, ctdb->ev, ctdb->client,
+					    ctdb->cmd_pnn, TIMEOUT(), &addr,
+					    &tickles);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get list of connections\n");
+		return ret;
+	}
+
+	if (options.machinereadable) {
+		printf("%s%s%s%s%s%s%s%s%s\n",
+		       options.sep,
+		       "Source IP", options.sep,
+		       "Port", options.sep,
+		       "Destiation IP", options.sep,
+		       "Port", options.sep);
+		for (i=0; i<tickles->num; i++) {
+			printf("%s%s%s%u%s%s%s%u%s\n", options.sep,
+			       ctdb_sock_addr_to_string(
+				       mem_ctx, &tickles->conn[i].src),
+			       options.sep,
+			       ntohs(tickles->conn[i].src.ip.sin_port),
+			       options.sep,
+			       ctdb_sock_addr_to_string(
+				       mem_ctx, &tickles->conn[i].dst),
+			       options.sep,
+			       ntohs(tickles->conn[i].dst.ip.sin_port),
+			       options.sep);
+		}
+	} else {
+		printf("Connections for IP: %s\n",
+		       ctdb_sock_addr_to_string(mem_ctx, &tickles->addr));
+		printf("Num connections: %u\n", tickles->num);
+		for (i=0; i<tickles->num; i++) {
+			printf("SRC: %s:%u   DST: %s:%u\n",
+			       ctdb_sock_addr_to_string(
+				       mem_ctx, &tickles->conn[i].src),
+			       ntohs(tickles->conn[i].src.ip.sin_port),
+			       ctdb_sock_addr_to_string(
+				       mem_ctx, &tickles->conn[i].dst),
+			       ntohs(tickles->conn[i].dst.ip.sin_port));
+		}
+	}
+
+	return 0;
+}
+
+typedef void (*clist_request_func)(struct ctdb_req_control *request,
+				   struct ctdb_connection *conn);
+
+typedef int (*clist_reply_func)(struct ctdb_reply_control *reply);
+
+struct process_clist_state {
+	struct ctdb_connection *clist;
+	int count;
+	int num_failed, num_total;
+	clist_reply_func reply_func;
+};
+
+static void process_clist_done(struct tevent_req *subreq);
+
+static struct tevent_req *process_clist_send(
+					TALLOC_CTX *mem_ctx,
+					struct ctdb_context *ctdb,
+					struct ctdb_connection *clist,
+					int count,
+					clist_request_func request_func,
+					clist_reply_func reply_func)
+{
+	struct tevent_req *req, *subreq;
+	struct process_clist_state *state;
+	struct ctdb_req_control request;
+	int i;
+
+	req = tevent_req_create(mem_ctx, &state, struct process_clist_state);
+	if (req == NULL) {
+		return NULL;
+	}
+
+	state->clist = clist;
+	state->count = count;
+	state->reply_func = reply_func;
+
+	for (i=0; i<count; i++) {
+		request_func(&request, &clist[i]);
+		subreq = ctdb_client_control_send(state, ctdb->ev,
+						  ctdb->client, ctdb->cmd_pnn,
+						  TIMEOUT(), &request);
+		if (tevent_req_nomem(subreq, req)) {
+			return tevent_req_post(req, ctdb->ev);
+		}
+		tevent_req_set_callback(subreq, process_clist_done, req);
+	}
+
+	return req;
+}
+
+static void process_clist_done(struct tevent_req *subreq)
+{
+	struct tevent_req *req = tevent_req_callback_data(
+		subreq, struct tevent_req);
+	struct process_clist_state *state = tevent_req_data(
+		req, struct process_clist_state);
+	struct ctdb_reply_control *reply;
+	int ret;
+	bool status;
+
+	status = ctdb_client_control_recv(subreq, NULL, state, &reply);
+	TALLOC_FREE(subreq);
+	if (! status) {
+		state->num_failed += 1;
+		goto done;
+	}
+
+	ret = state->reply_func(reply);
+	if (ret != 0) {
+		state->num_failed += 1;
+		goto done;
+	}
+
+done:
+	state->num_total += 1;
+	if (state->num_total == state->count) {
+		tevent_req_done(req);
+	}
+}
+
+static int process_clist_recv(struct tevent_req *req)
+{
+	struct process_clist_state *state = tevent_req_data(
+		req, struct process_clist_state);
+
+	return state->num_failed;
+}
+
+static int control_addtickle(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	struct ctdb_connection conn;
+	int ret;
+
+	if (argc != 0 && argc != 2) {
+		usage("addtickle");
+	}
+
+	if (argc == 0) {
+		struct ctdb_connection *clist;
+		struct tevent_req *req;
+		int count;
+
+		ret = ctdb_parse_connections(stdin, mem_ctx, &count, &clist);
+		if (ret != 0) {
+			return ret;
+		}
+
+		req = process_clist_send(mem_ctx, ctdb, clist, count,
+				 ctdb_req_control_tcp_add_delayed_update,
+				 ctdb_reply_control_tcp_add_delayed_update);
+		if (req == NULL) {
+			return ENOMEM;
+		}
+
+		tevent_req_poll(req, ctdb->ev);
+
+		ret = process_clist_recv(req);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to add %d tickles\n", ret);
+			return 1;
+		}
+
+		return 0;
+	}
+
+	if (! parse_ip_port(argv[0], &conn.src)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+	if (! parse_ip_port(argv[1], &conn.dst)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[1]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_tcp_add_delayed_update(mem_ctx, ctdb->ev,
+					       ctdb->client, ctdb->cmd_pnn,
+					       TIMEOUT(), &conn);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to register connection\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_deltickle(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	struct ctdb_connection conn;
+	int ret;
+
+	if (argc != 0 && argc != 2) {
+		usage("deltickle");
+	}
+
+	if (argc == 0) {
+		struct ctdb_connection *clist;
+		struct tevent_req *req;
+		int count;
+
+		ret = ctdb_parse_connections(stdin, mem_ctx, &count, &clist);
+		if (ret != 0) {
+			return ret;
+		}
+
+		req = process_clist_send(mem_ctx, ctdb, clist, count,
+					 ctdb_req_control_tcp_remove,
+					 ctdb_reply_control_tcp_remove);
+		if (req == NULL) {
+			return ENOMEM;
+		}
+
+		tevent_req_poll(req, ctdb->ev);
+
+		ret = process_clist_recv(req);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to remove %d tickles\n", ret);
+			return 1;
+		}
+
+		return 0;
+	}
+
+	if (! parse_ip_port(argv[0], &conn.src)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+	if (! parse_ip_port(argv[1], &conn.dst)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[1]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_tcp_remove(mem_ctx, ctdb->ev, ctdb->client,
+				   ctdb->cmd_pnn, TIMEOUT(), &conn);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to unregister connection\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_check_srvids(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	uint64_t *srvid;
+	uint8_t *result;
+	int ret, i;
+
+	if (argc == 0) {
+		usage("check_srvids");
+	}
+
+	srvid = talloc_array(mem_ctx, uint64_t, argc);
+	if (srvid == NULL) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	for (i=0; i<argc; i++) {
+		srvid[i] = strtoull(argv[i], NULL, 0);
+	}
+
+	ret = ctdb_ctrl_check_srvids(mem_ctx, ctdb->ev, ctdb->client,
+				     ctdb->cmd_pnn, TIMEOUT(), srvid, argc,
+				     &result);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to check srvids on node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	for (i=0; i<argc; i++) {
+		printf("SRVID 0x%" PRIx64 " %s\n", srvid[i],
+		       (result[i] ? "exists" : "does not exist"));
+	}
+
+	return 0;
+}
+
+static int control_listnodes(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	struct ctdb_node_map *nodemap;
+	int i;
+
+	if (argc != 0) {
+		usage("listnodes");
+	}
+
+	nodemap = read_nodes_file(mem_ctx, CTDB_UNKNOWN_PNN);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].flags & NODE_FLAGS_DELETED) {
+			continue;
+		}
+
+		if (options.machinereadable) {
+			printf("%s%u%s%s%s\n", options.sep,
+			       nodemap->node[i].pnn, options.sep,
+			       ctdb_sock_addr_to_string(
+					mem_ctx, &nodemap->node[i].addr),
+			       options.sep);
+		} else {
+			printf("%s\n",
+			       ctdb_sock_addr_to_string(
+					mem_ctx, &nodemap->node[i].addr));
+		}
+	}
+
+	return 0;
+}
+
+static bool nodemap_identical(struct ctdb_node_map *nodemap1,
+			      struct ctdb_node_map *nodemap2)
+{
+	int i;
+
+	if (nodemap1->num != nodemap2->num) {
+		return false;
+	}
+
+	for (i=0; i<nodemap1->num; i++) {
+		struct ctdb_node_and_flags *n1, *n2;
+
+		n1 = &nodemap1->node[i];
+		n2 = &nodemap2->node[i];
+
+		if ((n1->pnn != n2->pnn) ||
+		    (n1->flags != n2->flags) ||
+		    ! ctdb_sock_addr_same_ip(&n1->addr, &n2->addr)) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static int check_node_file_changes(TALLOC_CTX *mem_ctx,
+				   struct ctdb_node_map *nm,
+				   struct ctdb_node_map *fnm,
+				   bool *reload)
+{
+	int i;
+	bool check_failed = false;
+
+	*reload = false;
+
+	for (i=0; i<nm->num; i++) {
+		if (i >= fnm->num) {
+			fprintf(stderr,
+				"Node %u (%s) missing from nodes file\n",
+				nm->node[i].pnn,
+				ctdb_sock_addr_to_string(
+					mem_ctx, &nm->node[i].addr));
+			check_failed = true;
+			continue;
+		}
+		if (nm->node[i].flags & NODE_FLAGS_DELETED &&
+		    fnm->node[i].flags & NODE_FLAGS_DELETED) {
+			/* Node remains deleted */
+			continue;
+		}
+
+		if (! (nm->node[i].flags & NODE_FLAGS_DELETED) &&
+		    ! (fnm->node[i].flags & NODE_FLAGS_DELETED)) {
+			/* Node not newly nor previously deleted */
+			if (! ctdb_same_ip(&nm->node[i].addr,
+					   &fnm->node[i].addr)) {
+				fprintf(stderr,
+					"Node %u has changed IP address"
+					" (was %s, now %s)\n",
+					nm->node[i].pnn,
+					ctdb_sock_addr_to_string(
+						mem_ctx, &nm->node[i].addr),
+					ctdb_sock_addr_to_string(
+						mem_ctx, &fnm->node[i].addr));
+				check_failed = true;
+			} else {
+				if (nm->node[i].flags & NODE_FLAGS_DISCONNECTED) {
+					fprintf(stderr,
+						"WARNING: Node %u is disconnected."
+						" You MUST fix this node manually!\n",
+						nm->node[i].pnn);
+				}
+			}
+			continue;
+		}
+
+		if (fnm->node[i].flags & NODE_FLAGS_DELETED) {
+			/* Node is being deleted */
+			printf("Node %u is DELETED\n", nm->node[i].pnn);
+			*reload = true;
+			if (! (nm->node[i].flags & NODE_FLAGS_DISCONNECTED)) {
+				fprintf(stderr,
+					"ERROR: Node %u is still connected\n",
+					nm->node[i].pnn);
+				check_failed = true;
+			}
+			continue;
+		}
+
+		if (nm->node[i].flags & NODE_FLAGS_DELETED) {
+			/* Node was previously deleted */
+			printf("Node %u is UNDELETED\n", nm->node[i].pnn);
+			*reload = true;
+		}
+	}
+
+	if (check_failed) {
+		fprintf(stderr,
+			"ERROR: Nodes will not be reloaded due to previous error\n");
+		return 1;
+	}
+
+	/* Leftover nodes in file are NEW */
+	for (; i < fnm->num; i++) {
+		printf("Node %u is NEW\n", fnm->node[i].pnn);
+		*reload = true;
+	}
+
+	return 0;
+}
+
+struct disable_recoveries_state {
+	uint32_t *pnn_list;
+	int node_count;
+	bool *reply;
+	int status;
+	bool done;
+};
+
+static void disable_recoveries_handler(uint64_t srvid, TDB_DATA data,
+				       void *private_data)
+{
+	struct disable_recoveries_state *state =
+		(struct disable_recoveries_state *)private_data;
+	int ret, i;
+
+	if (data.dsize != sizeof(int)) {
+		/* Ignore packet */
+		return;
+	}
+
+	/* ret will be a PNN (i.e. >=0) on success, or negative on error */
+	ret = *(int *)data.dptr;
+	if (ret < 0) {
+		state->status = ret;
+		state->done = true;
+		return;
+	}
+	for (i=0; i<state->node_count; i++) {
+		if (state->pnn_list[i] == ret) {
+			state->reply[i] = true;
+			break;
+		}
+	}
+
+	state->done = true;
+	for (i=0; i<state->node_count; i++) {
+		if (! state->reply[i]) {
+			state->done = false;
+			break;
+		}
+	}
+}
+
+static int disable_recoveries(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      uint32_t timeout, uint32_t *pnn_list, int count)
+{
+	struct ctdb_disable_message disable = { 0 };
+	struct disable_recoveries_state state;
+	int ret, i;
+
+	disable.pnn = ctdb->pnn;
+	disable.srvid = next_srvid(ctdb);
+	disable.timeout = timeout;
+
+	state.pnn_list = pnn_list;
+	state.node_count = count;
+	state.done = false;
+	state.status = 0;
+	state.reply = talloc_zero_array(mem_ctx, bool, count);
+	if (state.reply == NULL) {
+		return ENOMEM;
+	}
+
+	ret = ctdb_client_set_message_handler(ctdb->ev, ctdb->client,
+					      disable.srvid,
+					      disable_recoveries_handler,
+					      &state);
+	if (ret != 0) {
+		return ret;
+	}
+
+	for (i=0; i<count; i++) {
+		ret = ctdb_message_disable_recoveries(mem_ctx, ctdb->ev,
+						      ctdb->client,
+						      pnn_list[i],
+						      &disable);
+		if (ret != 0) {
+			goto fail;
+		}
+	}
+
+	ret = ctdb_client_wait_timeout(ctdb->ev, &state.done, TIMEOUT());
+	if (ret == ETIME) {
+		fprintf(stderr, "Timed out waiting to disable recoveries\n");
+	} else {
+		ret = (state.status >= 0 ? 0 : 1);
+	}
+
+fail:
+	ctdb_client_remove_message_handler(ctdb->ev, ctdb->client,
+					   disable.srvid, &state);
+	return ret;
+}
+
+static int control_reloadnodes(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	struct ctdb_node_map *nodemap = NULL;
+	struct ctdb_node_map *file_nodemap;
+	struct ctdb_node_map *remote_nodemap;
+	struct ctdb_req_control request;
+	struct ctdb_reply_control **reply;
+	bool reload;
+	int ret, i;
+	uint32_t *pnn_list;
+	int count;
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	file_nodemap = read_nodes_file(mem_ctx, ctdb->pnn);
+	if (file_nodemap == NULL) {
+		return 1;
+	}
+
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].flags & NODE_FLAGS_DISCONNECTED) {
+			continue;
+		}
+
+		ret = ctdb_ctrl_get_nodes_file(mem_ctx, ctdb->ev, ctdb->client,
+					       nodemap->node[i].pnn, TIMEOUT(),
+					       &remote_nodemap);
+		if (ret != 0) {
+			fprintf(stderr,
+				"ERROR: Failed to get nodes file from node %u\n",
+				nodemap->node[i].pnn);
+			return ret;
+		}
+
+		if (! nodemap_identical(file_nodemap, remote_nodemap)) {
+			fprintf(stderr,
+				"ERROR: Nodes file on node %u differs"
+				 " from current node (%u)\n",
+				 nodemap->node[i].pnn, ctdb->pnn);
+			return 1;
+		}
+	}
+
+	ret = check_node_file_changes(mem_ctx, nodemap, file_nodemap, &reload);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (! reload) {
+		fprintf(stderr, "No change in nodes file,"
+				" skipping unnecessary reload\n");
+		return 0;
+	}
+
+	count = list_of_connected_nodes(nodemap, CTDB_UNKNOWN_PNN,
+					mem_ctx, &pnn_list);
+	if (count <= 0) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	ret = disable_recoveries(mem_ctx, ctdb, 2*options.timelimit,
+				 pnn_list, count);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to disable recoveries\n");
+		return ret;
+	}
+
+	ctdb_req_control_reload_nodes_file(&request);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, &reply);
+	if (ret != 0) {
+		bool failed = false;
+
+		for (i=0; i<count; i++) {
+			ret = ctdb_reply_control_reload_nodes_file(reply[i]);
+			if (ret != 0) {
+				fprintf(stderr,
+					"Node %u failed to reload nodes\n",
+					pnn_list[i]);
+				failed = true;
+			}
+		}
+		if (failed) {
+			fprintf(stderr,
+				"You MUST fix failed nodes manually!\n");
+		}
+	}
+
+	ret = disable_recoveries(mem_ctx, ctdb, 0, pnn_list, count);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to enable recoveries\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int moveip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+		  ctdb_sock_addr *addr, uint32_t pnn)
+{
+	struct ctdb_public_ip_list *pubip_list;
+	struct ctdb_public_ip pubip;
+	struct ctdb_node_map *nodemap;
+	struct ctdb_req_control request;
+	uint32_t *pnn_list;
+	int ret, i, count;
+
+	ret = ctdb_message_disable_ip_check(mem_ctx, ctdb->ev, ctdb->client,
+					    CTDB_BROADCAST_CONNECTED,
+					    2*options.timelimit);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to disable IP check\n");
+		return ret;
+	}
+
+	ret = ctdb_ctrl_get_public_ips(mem_ctx, ctdb->ev, ctdb->client,
+				       pnn, TIMEOUT(), &pubip_list);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get Public IPs from node %u\n",
+			pnn);
+		return ret;
+	}
+
+	for (i=0; i<pubip_list->num; i++) {
+		if (ctdb_same_ip(addr, &pubip_list->ip[i].addr)) {
+			break;
+		}
+	}
+
+	if (i == pubip_list->num) {
+		fprintf(stderr, "Node %u CANNOT host IP address %s\n",
+			pnn, ctdb_sock_addr_to_string(mem_ctx, addr));
+		return 1;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	count = list_of_active_nodes(nodemap, pnn, mem_ctx, &pnn_list);
+	if (count <= 0) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	pubip.pnn = pnn;
+	pubip.addr = *addr;
+	ctdb_req_control_release_ip(&request, &pubip);
+
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to release IP on nodes\n");
+		return ret;
+	}
+
+	ret = ctdb_ctrl_takeover_ip(mem_ctx, ctdb->ev, ctdb->client,
+				    pnn, TIMEOUT(), &pubip);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to takeover IP on node %u\n", pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_moveip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	uint32_t pnn;
+	int ret, retries = 0;
+
+	if (argc != 2) {
+		usage("moveip");
+	}
+
+	if (! parse_ip(argv[0], NULL, 0, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	pnn = strtoul(argv[1], NULL, 10);
+	if (pnn == CTDB_UNKNOWN_PNN) {
+		fprintf(stderr, "Invalid PNN %s\n", argv[1]);
+		return 1;
+	}
+
+	while (retries < 5) {
+		ret = moveip(mem_ctx, ctdb, &addr, pnn);
+		if (ret == 0) {
+			break;
+		}
+
+		sleep(3);
+		retries++;
+	}
+
+	if (ret != 0) {
+		fprintf(stderr, "Failed to move IP %s to node %u\n",
+			argv[0], pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_rebalanceip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	struct ctdb_node_map *nodemap;
+	struct ctdb_public_ip pubip;
+	struct ctdb_req_control request;
+	uint32_t *pnn_list;
+	int ret, count;
+
+	if (argc != 1) {
+		usage("rebalanceip");
+	}
+
+	if (parse_ip(argv[0], NULL, 0, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_message_disable_ip_check(mem_ctx, ctdb->ev, ctdb->client,
+					    CTDB_BROADCAST_CONNECTED,
+					    2*options.timelimit);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to disable IP check\n");
+		return 1;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	count = list_of_active_nodes(nodemap, -1, mem_ctx, &pnn_list);
+	if (count <= 0) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	pubip.pnn = CTDB_UNKNOWN_PNN;
+	pubip.addr = addr;
+
+	ctdb_req_control_release_ip(&request, &pubip);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to release IP from nodes\n");
+		return 1;
+	}
+
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int rebalancenode(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 uint32_t pnn)
+{
+	int ret;
+
+	ret = ctdb_message_rebalance_node(mem_ctx, ctdb->ev, ctdb->client,
+					  CTDB_BROADCAST_CONNECTED, pnn);
+	if (ret != 0) {
+		fprintf(stderr,
+			"Failed to ask recovery master to distribute IPs\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_addip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	struct ctdb_public_ip_list *pubip_list;
+	struct ctdb_addr_info addr_info;
+	unsigned int mask;
+	int ret, i, retries = 0;
+
+	if (argc != 2) {
+		usage("addip");
+	}
+
+	if (! parse_ip_mask(argv[0], argv[1], &addr, &mask)) {
+		fprintf(stderr, "Invalid IP/Mask %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_public_ips(mem_ctx, ctdb->ev, ctdb->client,
+				       ctdb->cmd_pnn, TIMEOUT(), &pubip_list);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get Public IPs from node %u\n",
+			ctdb->cmd_pnn);
+		return 1;
+	}
+
+	for (i=0; i<pubip_list->num; i++) {
+		if (ctdb_same_ip(&addr, &pubip_list->ip[i].addr)) {
+			fprintf(stderr, "Node already knows about IP %s\n",
+				ctdb_sock_addr_to_string(mem_ctx, &addr));
+			return 0;
+		}
+	}
+
+	addr_info.addr = addr;
+	addr_info.mask = mask;
+	addr_info.iface = argv[1];
+
+	while (retries < 5) {
+		ret = ctdb_ctrl_add_public_ip(mem_ctx, ctdb->ev, ctdb->client,
+					      ctdb->cmd_pnn, TIMEOUT(),
+					      &addr_info);
+		if (ret == 0) {
+			break;
+		}
+
+		sleep(3);
+		retries++;
+	}
+
+	if (ret != 0) {
+		fprintf(stderr, "Failed to add public IP to node %u."
+				" Giving up\n", ctdb->cmd_pnn);
+		return ret;
+	}
+
+	ret = rebalancenode(mem_ctx, ctdb, ctdb->cmd_pnn);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_delip(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	struct ctdb_public_ip_list *pubip_list;
+	struct ctdb_addr_info addr_info;
+	int ret, i;
+
+	if (argc != 1) {
+		usage("delip");
+	}
+
+	if (! parse_ip(argv[0], NULL, 0, &addr)) {
+		fprintf(stderr, "Invalid IP address %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_public_ips(mem_ctx, ctdb->ev, ctdb->client,
+				       ctdb->cmd_pnn, TIMEOUT(), &pubip_list);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get Public IPs from node %u\n",
+			ctdb->cmd_pnn);
+		return 1;
+	}
+
+	for (i=0; i<pubip_list->num; i++) {
+		if (ctdb_same_ip(&addr, &pubip_list->ip[i].addr)) {
+			break;
+		}
+	}
+
+	if (i == pubip_list->num) {
+		fprintf(stderr, "Node does not know about IP address %s\n",
+			ctdb_sock_addr_to_string(mem_ctx, &addr));
+		return 0;
+	}
+
+	addr_info.addr = addr;
+	addr_info.mask = 0;
+	addr_info.iface = NULL;
+
+	ret = ctdb_ctrl_del_public_ip(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &addr_info);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to delete public IP from node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_eventscript(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 1) {
+		usage("eventscript");
+	}
+
+	if (strcmp(argv[0], "monitor") != 0) {
+		fprintf(stderr, "Only monitor event can be run\n");
+		return 1;
+	}
+
+	ret = ctdb_ctrl_run_eventscripts(mem_ctx, ctdb->ev, ctdb->client,
+					 ctdb->cmd_pnn, TIMEOUT(), argv[0]);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to run monitor event on node %u\n",
+			ctdb->cmd_pnn);
+		return ret;
+	}
+
+	return 0;
+}
+
+#define DB_VERSION	3
+#define MAX_DB_NAME	64
+#define MAX_REC_BUFFER_SIZE	(100*1000)
+
+struct db_header {
+	unsigned long version;
+	time_t timestamp;
+	unsigned long flags;
+	unsigned long nbuf;
+	unsigned long nrec;
+	char name[MAX_DB_NAME];
+};
+
+struct backup_state {
+	TALLOC_CTX *mem_ctx;
+	struct ctdb_rec_buffer *recbuf;
+	uint32_t db_id;
+	int fd;
+	unsigned int nbuf, nrec;
+};
+
+static int backup_handler(uint32_t reqid, struct ctdb_ltdb_header *header,
+			  TDB_DATA key, TDB_DATA data, void *private_data)
+{
+	struct backup_state *state = (struct backup_state *)private_data;
+	size_t len;
+	int ret;
+
+	if (state->recbuf == NULL) {
+		state->recbuf = ctdb_rec_buffer_init(state->mem_ctx,
+						     state->db_id);
+		if (state->recbuf == NULL) {
+			return ENOMEM;
+		}
+	}
+
+	ret = ctdb_rec_buffer_add(state->recbuf, state->recbuf, reqid,
+				  header, key, data);
+	if (ret != 0) {
+		return ret;
+	}
+
+	len = ctdb_rec_buffer_len(state->recbuf);
+	if (len < MAX_REC_BUFFER_SIZE) {
+		return 0;
+	}
+
+	ret = ctdb_rec_buffer_write(state->recbuf, state->fd);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to write records to backup file\n");
+		return ret;
+	}
+
+	state->nbuf += 1;
+	state->nrec += state->recbuf->count;
+	TALLOC_FREE(state->recbuf);
+
+	return 0;
+}
+
+static int control_backupdb(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	uint32_t db_id;
+	uint8_t db_flags;
+	struct backup_state state;
+	struct db_header db_hdr;
+	int fd, ret;
+
+	if (argc != 2) {
+		usage("backupdb");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	fd = open(argv[1], O_RDWR|O_CREAT, 0600);
+	if (fd == -1) {
+		ret = errno;
+		fprintf(stderr, "Failed to open file %s for writing\n",
+			argv[1]);
+		return ret;
+	}
+
+	/* Write empty header first */
+	ZERO_STRUCT(db_hdr);
+	ret = write(fd, &db_hdr, sizeof(struct db_header));
+	if (ret == -1) {
+		ret = errno;
+		close(fd);
+		fprintf(stderr, "Failed to write header to file %s\n", argv[1]);
+		return ret;
+	}
+
+	state.mem_ctx = mem_ctx;
+	state.recbuf = NULL;
+	state.fd = fd;
+	state.nbuf = 0;
+	state.nrec = 0;
+
+	ret = ctdb_db_traverse(db, true, false, backup_handler, &state);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to collect records from DB %s\n",
+			db_name);
+		close(fd);
+		return ret;
+	}
+
+	if (state.recbuf != NULL) {
+		ret = ctdb_rec_buffer_write(state.recbuf, state.fd);
+		if (ret != 0) {
+			fprintf(stderr,
+				"Failed to write records to backup file\n");
+			close(fd);
+			return ret;
+		}
+
+		state.nbuf += 1;
+		state.nrec += state.recbuf->count;
+		TALLOC_FREE(state.recbuf);
+	}
+
+	db_hdr.version = DB_VERSION;
+	db_hdr.timestamp = time(NULL);
+	db_hdr.flags = db_flags;
+	db_hdr.nbuf = state.nbuf;
+	db_hdr.nrec = state.nrec;
+	strncpy(db_hdr.name, db_name, MAX_DB_NAME-1);
+
+	lseek(fd, 0, SEEK_SET);
+	ret = write(fd, &db_hdr, sizeof(struct db_header));
+	if (ret == -1) {
+		ret = errno;
+		close(fd);
+		fprintf(stderr, "Failed to write header to file %s\n", argv[1]);
+		return ret;
+	}
+
+	close(fd);
+	printf("Database backed up to %s\n", argv[1]);
+	return 0;
+}
+
+static int control_restoredb(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	const char *db_name = NULL;
+	struct ctdb_db_context *db;
+	struct db_header db_hdr;
+	struct ctdb_node_map *nodemap;
+	struct ctdb_req_control request;
+	struct ctdb_reply_control **reply;
+	struct ctdb_transdb wipedb;
+	struct ctdb_pulldb_ext pulldb;
+	struct ctdb_rec_buffer *recbuf;
+	uint32_t generation;
+	uint32_t *pnn_list;
+	char timebuf[128];
+	int fd, i;
+	int count, ret;
+
+	if (argc < 1 || argc > 2) {
+		usage("restoredb");
+	}
+
+	fd = open(argv[0], O_RDONLY, 0600);
+	if (fd == -1) {
+		ret = errno;
+		fprintf(stderr, "Failed to open file %s for reading\n",
+			argv[0]);
+		return ret;
+	}
+
+	if (argc == 2) {
+		db_name = argv[1];
+	}
+
+	ret = read(fd, &db_hdr, sizeof(struct db_header));
+	if (ret == -1) {
+		ret = errno;
+		close(fd);
+		fprintf(stderr, "Failed to read db header from file %s\n",
+			argv[0]);
+		return ret;
+	}
+
+	if (db_hdr.version != DB_VERSION) {
+		fprintf(stderr,
+			"Wrong version of backup file, expected %u, got %lu\n",
+			DB_VERSION, db_hdr.version);
+		close(fd);
+		return EINVAL;
+	}
+
+	if (db_name == NULL) {
+		db_name = db_hdr.name;
+	}
+
+	strftime(timebuf, sizeof(timebuf)-1, "%Y/%m/%d %H:%M:%S",
+		 localtime(&db_hdr.timestamp));
+	printf("Restoring database %s from backup @ %s\n", db_name, timebuf);
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_hdr.flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_hdr.name);
+		return ret;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		fprintf(stderr, "Failed to get nodemap\n");
+		return ENOMEM;
+	}
+
+	ret = get_generation(mem_ctx, ctdb, &generation);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get current generation\n");
+		return ret;
+	}
+
+	count = list_of_active_nodes(nodemap, CTDB_UNKNOWN_PNN, mem_ctx,
+				     &pnn_list);
+	if (count <= 0) {
+		return ENOMEM;
+	}
+
+	wipedb.db_id = ctdb_db_id(db);
+	wipedb.tid = generation;
+
+	ctdb_req_control_db_freeze(&request, wipedb.db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev,
+					ctdb->client, pnn_list, count,
+					TIMEOUT(), &request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+
+	ctdb_req_control_db_transaction_start(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_wipe_database(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	pulldb.db_id = ctdb_db_id(db);
+	pulldb.lmaster = 0;
+	pulldb.srvid = SRVID_CTDB_PUSHDB;
+
+	ctdb_req_control_db_push_start(&request, &pulldb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	for (i=0; i<db_hdr.nbuf; i++) {
+		struct ctdb_req_message message;
+		TDB_DATA data;
+
+		ret = ctdb_rec_buffer_read(fd, mem_ctx, &recbuf);
+		if (ret != 0) {
+			goto failed;
+		}
+
+		data.dsize = ctdb_rec_buffer_len(recbuf);
+		data.dptr = talloc_size(mem_ctx, data.dsize);
+		if (data.dptr == NULL) {
+			goto failed;
+		}
+
+		ctdb_rec_buffer_push(recbuf, data.dptr);
+
+		message.srvid = pulldb.srvid;
+		message.data.data = data;
+
+		ret = ctdb_client_message_multi(mem_ctx, ctdb->ev,
+						ctdb->client,
+						pnn_list, count,
+						&message, NULL);
+		if (ret != 0) {
+			goto failed;
+		}
+
+		talloc_free(recbuf);
+		talloc_free(data.dptr);
+	}
+
+	ctdb_req_control_db_push_confirm(&request, pulldb.db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, &reply);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	for (i=0; i<count; i++) {
+		uint32_t num_records;
+
+		ret = ctdb_reply_control_db_push_confirm(reply[i],
+							 &num_records);
+		if (ret != 0) {
+			fprintf(stderr, "Invalid response from node %u\n",
+				pnn_list[i]);
+			goto failed;
+		}
+
+		if (num_records != db_hdr.nrec) {
+			fprintf(stderr, "Node %u received %u of %lu records\n",
+				pnn_list[i], num_records, db_hdr.nrec);
+			goto failed;
+		}
+	}
+
+	ctdb_req_control_db_set_healthy(&request, wipedb.db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_db_transaction_commit(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_db_thaw(&request, wipedb.db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev,
+					ctdb->client, pnn_list, count,
+					TIMEOUT(), &request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	printf("Database %s restored\n", db_name);
+	return 0;
+
+
+failed:
+	ctdb_ctrl_set_recmode(mem_ctx, ctdb->ev, ctdb->client,
+			      ctdb->pnn, TIMEOUT(), CTDB_RECOVERY_ACTIVE);
+	return ret;
+}
+
+struct dumpdbbackup_state {
+	ctdb_rec_parser_func_t parser;
+	struct dump_record_state sub_state;
+};
+
+static int dumpdbbackup_handler(uint32_t reqid,
+				struct ctdb_ltdb_header *header,
+				TDB_DATA key, TDB_DATA data,
+				void *private_data)
+{
+	struct dumpdbbackup_state *state =
+		(struct dumpdbbackup_state *)private_data;
+	struct ctdb_ltdb_header hdr;
+	int ret;
+
+	ret = ctdb_ltdb_header_extract(&data, &hdr);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return state->parser(reqid, &hdr, key, data, &state->sub_state);
+}
+
+static int control_dumpdbbackup(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	struct db_header db_hdr;
+	char timebuf[128];
+	struct dumpdbbackup_state state;
+	int fd, ret, i;
+
+	if (argc != 1) {
+		usage("dumpbackup");
+	}
+
+	fd = open(argv[0], O_RDONLY, 0600);
+	if (fd == -1) {
+		ret = errno;
+		fprintf(stderr, "Failed to open file %s for reading\n",
+			argv[0]);
+		return ret;
+	}
+
+	ret = read(fd, &db_hdr, sizeof(struct db_header));
+	if (ret == -1) {
+		ret = errno;
+		close(fd);
+		fprintf(stderr, "Failed to read db header from file %s\n",
+			argv[0]);
+		return ret;
+	}
+
+	if (db_hdr.version != DB_VERSION) {
+		fprintf(stderr,
+			"Wrong version of backup file, expected %u, got %lu\n",
+			DB_VERSION, db_hdr.version);
+		return EINVAL;
+	}
+
+	strftime(timebuf, sizeof(timebuf)-1, "%Y/%m/%d %H:%M:%S",
+		 localtime(&db_hdr.timestamp));
+	printf("Dumping database %s from backup @ %s\n",
+	       db_hdr.name, timebuf);
+
+	state.parser = dump_record;
+	state.sub_state.count = 0;
+
+	for (i=0; i<db_hdr.nbuf; i++) {
+		struct ctdb_rec_buffer *recbuf;
+
+		ret = ctdb_rec_buffer_read(fd, mem_ctx, &recbuf);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to read records\n");
+			return ret;
+		}
+
+		ret = ctdb_rec_buffer_traverse(recbuf, dumpdbbackup_handler,
+					       &state);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to dump records\n");
+			return ret;
+		}
+	}
+
+	printf("Dumped %u record(s)\n", state.sub_state.count);
+	return 0;
+}
+
+static int control_wipedb(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	uint32_t db_id;
+	uint8_t db_flags;
+	struct ctdb_node_map *nodemap;
+	struct ctdb_req_control request;
+	struct ctdb_transdb wipedb;
+	uint32_t generation;
+	uint32_t *pnn_list;
+	int count, ret;
+
+	if (argc != 1) {
+		usage("wipedb");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		fprintf(stderr, "Failed to get nodemap\n");
+		return ENOMEM;
+	}
+
+	ret = get_generation(mem_ctx, ctdb, &generation);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get current generation\n");
+		return ret;
+	}
+
+	count = list_of_active_nodes(nodemap, CTDB_UNKNOWN_PNN, mem_ctx,
+				     &pnn_list);
+	if (count <= 0) {
+		return ENOMEM;
+	}
+
+	ctdb_req_control_db_freeze(&request, db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev,
+					ctdb->client, pnn_list, count,
+					TIMEOUT(), &request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	wipedb.db_id = db_id;
+	wipedb.tid = generation;
+
+	ctdb_req_control_db_transaction_start(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_wipe_database(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_db_set_healthy(&request, db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_db_transaction_commit(&request, &wipedb);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list, count, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	ctdb_req_control_db_thaw(&request, db_id);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev,
+					ctdb->client, pnn_list, count,
+					TIMEOUT(), &request, NULL, NULL);
+	if (ret != 0) {
+		goto failed;
+	}
+
+	printf("Database %s wiped\n", db_name);
+	return 0;
+
+
+failed:
+	ctdb_ctrl_set_recmode(mem_ctx, ctdb->ev, ctdb->client,
+			      ctdb->pnn, TIMEOUT(), CTDB_RECOVERY_ACTIVE);
+	return ret;
+}
+
+static int control_recmaster(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	uint32_t recmaster;
+	int ret;
+
+	ret = ctdb_ctrl_get_recmaster(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), &recmaster);
+	if (ret != 0) {
+		return ret;
+	}
+
+	printf("%u\n", recmaster);
+	return 0;
+}
+
+static void print_scriptstatus_one(struct ctdb_script_list *slist,
+				   const char *event_str)
+{
+	int i;
+	int num_run = 0;
+
+	if (slist == NULL) {
+		if (! options.machinereadable) {
+			printf("%s cycle never run\n", event_str);
+		}
+		return;
+	}
+
+	for (i=0; i<slist->num_scripts; i++) {
+		if (slist->script[i].status != -ENOEXEC) {
+			num_run++;
+		}
+	}
+
+	if (! options.machinereadable) {
+		printf("%d scripts were executed last %s cycle\n",
+		       num_run, event_str);
+	}
+
+	for (i=0; i<slist->num_scripts; i++) {
+		const char *status = NULL;
+
+		switch (slist->script[i].status) {
+		case -ETIME:
+			status = "TIMEDOUT";
+			break;
+		case -ENOEXEC:
+			status = "DISABLED";
+			break;
+		case 0:
+			status = "OK";
+			break;
+		default:
+			if (slist->script[i].status > 0) {
+				status = "ERROR";
+			}
+			break;
+		}
+
+		if (options.machinereadable) {
+			printf("%s%s%s%s%s%i%s%s%s%lu.%06lu%s%lu.%06lu%s%s%s\n",
+			       options.sep,
+			       event_str, options.sep,
+			       slist->script[i].name, options.sep,
+			       slist->script[i].status, options.sep,
+			       status, options.sep,
+			       slist->script[i].start.tv_sec,
+			       slist->script[i].start.tv_usec, options.sep,
+			       slist->script[i].finished.tv_sec,
+			       slist->script[i].finished.tv_usec, options.sep,
+			       slist->script[i].output, options.sep);
+			continue;
+		}
+
+		if (status) {
+			printf("%-20s Status:%s    ",
+			       slist->script[i].name, status);
+		} else {
+			/* Some other error, eg from stat. */
+			printf("%-20s Status:CANNOT RUN (%s)",
+			       slist->script[i].name,
+			       strerror(-slist->script[i].status));
+		}
+
+		if (slist->script[i].status >= 0) {
+			printf("Duration:%.3lf ",
+			       timeval_delta(&slist->script[i].finished,
+					     &slist->script[i].start));
+		}
+		if (slist->script[i].status != -ENOEXEC) {
+			printf("%s", ctime(&slist->script[i].start.tv_sec));
+			if (slist->script[i].status != 0) {
+				printf("   OUTPUT:%s\n",
+				       slist->script[i].output);
+			}
+		} else {
+			printf("\n");
+		}
+	}
+}
+
+static void print_scriptstatus(struct ctdb_script_list **slist,
+			       int count, const char **event_str)
+{
+	int i;
+
+	if (options.machinereadable) {
+		printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+		       options.sep,
+		       "Type", options.sep,
+		       "Name", options.sep,
+		       "Code", options.sep,
+		       "Status", options.sep,
+		       "Start", options.sep,
+		       "End", options.sep,
+		       "Error Output", options.sep);
+	}
+
+	for (i=0; i<count; i++) {
+		print_scriptstatus_one(slist[i], event_str[i]);
+	}
+}
+
+static int control_scriptstatus(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	struct ctdb_script_list **slist;
+	const char *event_str;
+	enum ctdb_event event;
+	const char *all_events[] = {
+		"init", "setup", "startup", "monitor",
+		"takeip", "releaseip", "updateip", "ipreallocated" };
+	bool valid;
+	int ret, i, j;
+	int count, start, end, num;
+
+	if (argc > 1) {
+		usage("scriptstatus");
+	}
+
+	if (argc == 0) {
+		event_str = "monitor";
+	} else {
+		event_str = argv[0];
+	}
+
+	valid = false;
+	for (i=0; i<ARRAY_SIZE(all_events); i++) {
+		if (strcmp(event_str, all_events[i]) == 0) {
+			valid = true;
+			count = 1;
+			start = i;
+			end = i+1;
+			break;
+		}
+	}
+
+	if (strcmp(event_str, "all") == 0) {
+		valid = true;
+		count = ARRAY_SIZE(all_events);
+		start = 0;
+		end = count-1;
+	}
+
+	if (! valid) {
+		fprintf(stderr, "Unknown event name %s\n", argv[0]);
+		usage("scriptstatus");
+	}
+
+	slist = talloc_array(mem_ctx, struct ctdb_script_list *, count);
+	if (slist == NULL) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	num = 0;
+	for (i=start; i<end; i++) {
+		event = ctdb_event_from_string(all_events[i]);
+
+		ret = ctdb_ctrl_get_event_script_status(mem_ctx, ctdb->ev,
+							ctdb->client,
+							ctdb->cmd_pnn,
+							TIMEOUT(), event,
+							&slist[num]);
+		if (ret != 0) {
+			fprintf(stderr,
+				"failed to get script status for %s event\n",
+				all_events[i]);
+			return 1;
+		}
+
+		if (slist[num] == NULL) {
+			num++;
+			continue;
+		}
+
+		/* The ETIME status is ignored for certain events.
+		 * In that case the status is 0, but endtime is not set.
+		 */
+		for (j=0; j<slist[num]->num_scripts; j++) {
+			if (slist[num]->script[j].status == 0 &&
+			    timeval_is_zero(&slist[num]->script[j].finished)) {
+				slist[num]->script[j].status = -ETIME;
+			}
+		}
+
+		num++;
+	}
+
+	print_scriptstatus(slist, count, &all_events[start]);
+	return 0;
+}
+
+static int control_enablescript(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 1) {
+		usage("enablescript");
+	}
+
+	ret = ctdb_ctrl_enable_script(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), argv[0]);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to enable script %s\n", argv[0]);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_disablescript(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				 int argc, const char **argv)
+{
+	int ret;
+
+	if (argc != 1) {
+		usage("disablescript");
+	}
+
+	ret = ctdb_ctrl_disable_script(mem_ctx, ctdb->ev, ctdb->client,
+				       ctdb->cmd_pnn, TIMEOUT(), argv[0]);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to disable script %s\n", argv[0]);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_natgw(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			 int argc, const char **argv)
+{
+	char *t, *natgw_helper = NULL;
+
+	if (argc != 1) {
+		usage("natgw");
+	}
+
+	t = getenv("CTDB_NATGW_HELPER");
+	if (t != NULL) {
+		natgw_helper = talloc_strdup(mem_ctx, t);
+	} else {
+		natgw_helper = talloc_asprintf(mem_ctx, "%s/ctdb_natgw",
+					       CTDB_HELPER_BINDIR);
+	}
+
+	if (natgw_helper == NULL) {
+		fprintf(stderr, "Unable to set NAT gateway helper\n");
+		return 1;
+	}
+
+	return run_helper("NAT gateway helper", natgw_helper, argv[0]);
+}
+
+static int control_natgwlist(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	char *t, *natgw_helper = NULL;
+
+	if (argc != 0) {
+		usage("natgwlist");
+	}
+
+	t = getenv("CTDB_NATGW_HELPER");
+	if (t != NULL) {
+		natgw_helper = talloc_strdup(mem_ctx, t);
+	} else {
+		natgw_helper = talloc_asprintf(mem_ctx, "%s/ctdb_natgw",
+					       CTDB_HELPER_BINDIR);
+	}
+
+	if (natgw_helper == NULL) {
+		fprintf(stderr, "Unable to set NAT gateway helper\n");
+		return 1;
+	}
+
+	return run_helper("NAT gateway helper", natgw_helper, "natgwlist");
+}
+
+/*
+ * Find the PNN of the current node
+ * discover the pnn by loading the nodes file and try to bind
+ * to all addresses one at a time until the ip address is found.
+ */
+static bool find_node_xpnn(TALLOC_CTX *mem_ctx, uint32_t *pnn)
+{
+	struct ctdb_node_map *nodemap;
+	int i;
+
+	nodemap = read_nodes_file(mem_ctx, CTDB_UNKNOWN_PNN);
+	if (nodemap == NULL) {
+		return false;
+	}
+
+	for (i=0; i<nodemap->num; i++) {
+		if (nodemap->node[i].flags & NODE_FLAGS_DELETED) {
+			continue;
+		}
+		if (ctdb_sys_have_ip(&nodemap->node[i].addr)) {
+			if (pnn != NULL) {
+				*pnn = nodemap->node[i].pnn;
+			}
+			talloc_free(nodemap);
+			return true;
+		}
+	}
+
+	fprintf(stderr, "Failed to detect PNN of the current node.\n");
+	talloc_free(nodemap);
+	return false;
+}
+
+static int control_getreclock(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	const char *reclock;
+	int ret;
+
+	if (argc != 0) {
+		usage("getreclock");
+	}
+
+	ret = ctdb_ctrl_get_reclock_file(mem_ctx, ctdb->ev, ctdb->client,
+					 ctdb->cmd_pnn, TIMEOUT(), &reclock);
+	if (ret != 0) {
+		return ret;
+	}
+
+	if (reclock != NULL) {
+		printf("%s\n", reclock);
+	}
+
+	return 0;
+}
+
+static int control_setlmasterrole(TALLOC_CTX *mem_ctx,
+				  struct ctdb_context *ctdb,
+				  int argc, const char **argv)
+{
+	uint32_t lmasterrole;
+	int ret;
+
+	if (argc != 1) {
+		usage("setlmasterrole");
+	}
+
+	if (strcmp(argv[0], "on") == 0) {
+		lmasterrole = 1;
+	} else if (strcmp(argv[0], "off") == 0) {
+		lmasterrole = 0;
+	} else {
+		usage("setlmasterrole");
+	}
+
+	ret = ctdb_ctrl_set_lmasterrole(mem_ctx, ctdb->ev, ctdb->client,
+					ctdb->cmd_pnn, TIMEOUT(), lmasterrole);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_setrecmasterrole(TALLOC_CTX *mem_ctx,
+				    struct ctdb_context *ctdb,
+				    int argc, const char **argv)
+{
+	uint32_t recmasterrole;
+	int ret;
+
+	if (argc != 1) {
+		usage("setrecmasterrole");
+	}
+
+	if (strcmp(argv[0], "on") == 0) {
+		recmasterrole = 1;
+	} else if (strcmp(argv[0], "off") == 0) {
+		recmasterrole = 0;
+	} else {
+		usage("setrecmasterrole");
+	}
+
+	ret = ctdb_ctrl_set_recmasterrole(mem_ctx, ctdb->ev, ctdb->client,
+					  ctdb->cmd_pnn, TIMEOUT(),
+					  recmasterrole);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_setdbreadonly(TALLOC_CTX *mem_ctx,
+				 struct ctdb_context *ctdb,
+				 int argc, const char **argv)
+{
+	uint32_t db_id;
+	uint8_t db_flags;
+	int ret;
+
+	if (argc != 1) {
+		usage("setdbreadonly");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, NULL, &db_flags)) {
+		return 1;
+	}
+
+	if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+		fprintf(stderr, "Cannot set READONLY on persistent DB\n");
+		return 1;
+	}
+
+	ret = ctdb_ctrl_set_db_readonly(mem_ctx, ctdb->ev, ctdb->client,
+					ctdb->cmd_pnn, TIMEOUT(), db_id);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_setdbsticky(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	uint32_t db_id;
+	uint8_t db_flags;
+	int ret;
+
+	if (argc != 1) {
+		usage("setdbsticky");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, NULL, &db_flags)) {
+		return 1;
+	}
+
+	if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+		fprintf(stderr, "Cannot set STICKY on persistent DB\n");
+		return 1;
+	}
+
+	ret = ctdb_ctrl_set_db_sticky(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), db_id);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_pfetch(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_transaction_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key, data;
+	int ret;
+
+	if (argc < 2 || argc > 3) {
+		usage("pfetch");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (! (db_flags & CTDB_DB_FLAGS_PERSISTENT)) {
+		fprintf(stderr, "DB %s is not a persistent database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_transaction_start(mem_ctx, ctdb->ev, ctdb->client,
+				     TIMEOUT(), db, true, &h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to start transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	ret = ctdb_transaction_fetch_record(h, key, mem_ctx, &data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to read record for key %s\n",
+			argv[1]);
+		return ret;
+	}
+
+	printf("%.*s\n", (int)data.dsize, data.dptr);
+
+	ctdb_transaction_cancel(h);
+	return 0;
+}
+
+static int control_pstore(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_transaction_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key, data;
+	int ret;
+
+	if (argc != 3) {
+		usage("pstore");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (! (db_flags & CTDB_DB_FLAGS_PERSISTENT)) {
+		fprintf(stderr, "DB %s is not a persistent database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = str_to_data(argv[2], strlen(argv[2]), mem_ctx, &data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse value %s\n", argv[2]);
+		return ret;
+	}
+
+	ret = ctdb_transaction_start(mem_ctx, ctdb->ev, ctdb->client,
+				     TIMEOUT(), db, false, &h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to start transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	ret = ctdb_transaction_store_record(h, key, data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to store record for key %s\n",
+			argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_transaction_commit(h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to commit transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int control_pdelete(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_transaction_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key;
+	int ret;
+
+	if (argc != 2) {
+		usage("pdelete");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (! (db_flags & CTDB_DB_FLAGS_PERSISTENT)) {
+		fprintf(stderr, "DB %s is not a persistent database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_transaction_start(mem_ctx, ctdb->ev, ctdb->client,
+				     TIMEOUT(), db, false, &h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to start transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	ret = ctdb_transaction_delete_record(h, key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to delete record for key %s\n",
+			argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_transaction_commit(h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to commit transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ptrans_parse_string(TALLOC_CTX *mem_ctx, const char **ptr, TDB_DATA *data)
+{
+	const char *t;
+	size_t n;
+	int ret;
+
+	*data = tdb_null;
+
+	/* Skip whitespace */
+	n = strspn(*ptr, " \t");
+	t = *ptr + n;
+
+	if (t[0] == '"') {
+		/* Quoted ASCII string - no wide characters! */
+		t++;
+		n = strcspn(t, "\"");
+		if (t[n] == '"') {
+			if (n > 0) {
+				ret = str_to_data(t, n, mem_ctx, data);
+				if (ret != 0) {
+					return ret;
+				}
+			}
+			*ptr = t + n + 1;
+		} else {
+			fprintf(stderr, "Unmatched \" in input %s\n", *ptr);
+			return 1;
+		}
+	} else {
+		fprintf(stderr, "Unsupported input format in %s\n", *ptr);
+		return 1;
+	}
+
+	return 0;
+}
+
+#define MAX_LINE_SIZE	1024
+
+static bool ptrans_get_key_value(TALLOC_CTX *mem_ctx, FILE *file,
+				 TDB_DATA *key, TDB_DATA *value)
+{
+	char line [MAX_LINE_SIZE]; /* FIXME: make this more flexible? */
+	const char *ptr;
+	int ret;
+
+	ptr = fgets(line, MAX_LINE_SIZE, file);
+	if (ptr == NULL) {
+		return false;
+	}
+
+	/* Get key */
+	ret = ptrans_parse_string(mem_ctx, &ptr, key);
+	if (ret != 0 || ptr == NULL || key->dptr == NULL) {
+		/* Line Ignored but not EOF */
+		*key = tdb_null;
+		return true;
+	}
+
+	/* Get value */
+	ret = ptrans_parse_string(mem_ctx, &ptr, value);
+	if (ret != 0) {
+		/* Line Ignored but not EOF */
+		talloc_free(key->dptr);
+		*key = tdb_null;
+		return true;
+	}
+
+	return true;
+}
+
+static int control_ptrans(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_transaction_handle *h;
+	uint8_t db_flags;
+	FILE *file;
+	TDB_DATA key, value;
+	int ret;
+
+	if (argc < 1 || argc > 2) {
+		usage("ptrans");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (! (db_flags & CTDB_DB_FLAGS_PERSISTENT)) {
+		fprintf(stderr, "DB %s is not a persistent database\n",
+			db_name);
+		return 1;
+	}
+
+	if (argc == 2) {
+		file = fopen(argv[1], "r");
+		if (file == NULL) {
+			fprintf(stderr, "Failed to open file %s\n", argv[1]);
+			return 1;
+		}
+	} else {
+		file = stdin;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = ctdb_transaction_start(mem_ctx, ctdb->ev, ctdb->client,
+				     TIMEOUT(), db, false, &h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to start transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	while (ptrans_get_key_value(mem_ctx, file, &key, &value)) {
+		if (key.dsize != 0) {
+			ret = ctdb_transaction_store_record(h, key, value);
+			if (ret != 0) {
+				fprintf(stderr, "Failed to store record\n");
+				ctdb_transaction_cancel(h);
+				return 1;
+			}
+			talloc_free(key.dptr);
+			talloc_free(value.dptr);
+		}
+	}
+
+	ret = ctdb_transaction_commit(h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to commit transaction on db %s\n",
+			db_name);
+		return ret;
+	}
+
+	if (file != stdin) {
+		fclose(file);
+	}
+
+	return 0;
+}
+
+static int control_tfetch(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct tdb_context *tdb;
+	TDB_DATA key, data;
+	struct ctdb_ltdb_header header;
+	int ret;
+
+	if (argc < 2 || argc > 3) {
+		usage("tfetch");
+	}
+
+	tdb = tdb_open(argv[0], 0, 0, O_RDWR, 0);
+	if (tdb == NULL) {
+		fprintf(stderr, "Failed to open TDB file %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	data = tdb_fetch(tdb, key);
+	if (data.dptr == NULL) {
+		fprintf(stderr, "No record for key %s\n", argv[1]);
+		return 1;
+	}
+
+	if (data.dsize < sizeof(struct ctdb_ltdb_header)) {
+		fprintf(stderr, "Invalid record for key %s\n", argv[1]);
+		return 1;
+	}
+
+	tdb_close(tdb);
+
+	if (argc == 3) {
+		int fd;
+		ssize_t nwritten;
+
+		fd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0600);
+		if (fd == -1) {
+			fprintf(stderr, "Failed to open output file %s\n",
+				argv[2]);
+			goto fail;
+		}
+
+		nwritten = sys_write(fd, data.dptr, data.dsize);
+		if (nwritten != data.dsize) {
+			fprintf(stderr, "Failed to write record to file\n");
+			goto fail;
+		}
+
+		close(fd);
+	}
+fail:
+	ret = ctdb_ltdb_header_extract(&data, &header);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse header from data\n");
+		return 1;
+	}
+
+	dump_ltdb_header(&header);
+	dump_tdb_data("data", data);
+
+	return 0;
+}
+
+static int control_tstore(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			  int argc, const char **argv)
+{
+	struct tdb_context *tdb;
+	TDB_DATA key, data, value;
+	struct ctdb_ltdb_header header;
+	size_t offset;
+	int ret;
+
+	if (argc < 3 || argc > 5) {
+		usage("tstore");
+	}
+
+	tdb = tdb_open(argv[0], 0, 0, O_RDWR, 0);
+	if (tdb == NULL) {
+		fprintf(stderr, "Failed to open TDB file %s\n", argv[0]);
+		return 1;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = str_to_data(argv[2], strlen(argv[2]), mem_ctx, &value);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse value %s\n", argv[2]);
+		return ret;
+	}
+
+	ZERO_STRUCT(header);
+
+	if (argc > 3) {
+		header.rsn = (uint64_t)strtoull(argv[3], NULL, 0);
+	}
+	if (argc > 4) {
+		header.dmaster = (uint32_t)atol(argv[4]);
+	}
+	if (argc > 5) {
+		header.flags = (uint32_t)atol(argv[5]);
+	}
+
+	offset = ctdb_ltdb_header_len(&header);
+	data.dsize = offset + value.dsize;
+	data.dptr = talloc_size(mem_ctx, data.dsize);
+	if (data.dptr == NULL) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	ctdb_ltdb_header_push(&header, data.dptr);
+	memcpy(data.dptr + offset, value.dptr, value.dsize);
+
+	ret = tdb_store(tdb, key, data, TDB_REPLACE);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to write record %s to file %s\n",
+			argv[1], argv[0]);
+	}
+
+	tdb_close(tdb);
+
+	return ret;
+}
+
+static int control_readkey(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_record_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key, data;
+	bool readonly = false;
+	int ret;
+
+	if (argc < 2 || argc > 3) {
+		usage("readkey");
+	}
+
+	if (argc == 3) {
+		if (strcmp(argv[2], "readonly") == 0) {
+			readonly = true;
+		} else {
+			usage("readkey");
+		}
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+		fprintf(stderr, "DB %s is not a volatile database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_fetch_lock(mem_ctx, ctdb->ev, ctdb->client,
+			      db, key, readonly, &h, NULL, &data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to read record for key %s\n",
+			argv[1]);
+	} else {
+		printf("Data: size:%zu ptr:[%.*s]\n", data.dsize,
+		       (int)data.dsize, data.dptr);
+	}
+
+	talloc_free(h);
+	return ret;
+}
+
+static int control_writekey(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			    int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_record_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key, data;
+	int ret;
+
+	if (argc != 3) {
+		usage("writekey");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+		fprintf(stderr, "DB %s is not a volatile database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = str_to_data(argv[2], strlen(argv[2]), mem_ctx, &data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse value %s\n", argv[2]);
+		return ret;
+	}
+
+	ret = ctdb_fetch_lock(mem_ctx, ctdb->ev, ctdb->client,
+			      db, key, false, &h, NULL, NULL);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to lock record for key %s\n", argv[0]);
+		return ret;
+	}
+
+	ret = ctdb_store_record(h, data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to store record for key %s\n",
+			argv[1]);
+	}
+
+	talloc_free(h);
+	return ret;
+}
+
+static int control_deletekey(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	const char *db_name;
+	struct ctdb_db_context *db;
+	struct ctdb_record_handle *h;
+	uint8_t db_flags;
+	TDB_DATA key, data;
+	int ret;
+
+	if (argc != 2) {
+		usage("deletekey");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], NULL, &db_name, &db_flags)) {
+		return 1;
+	}
+
+	if (db_flags & CTDB_DB_FLAGS_PERSISTENT) {
+		fprintf(stderr, "DB %s is not a volatile database\n",
+			db_name);
+		return 1;
+	}
+
+	ret = ctdb_attach(ctdb->ev, ctdb->client, TIMEOUT(), db_name,
+			  db_flags, &db);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to attach to DB %s\n", db_name);
+		return ret;
+	}
+
+	ret = str_to_data(argv[1], strlen(argv[1]), mem_ctx, &key);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to parse key %s\n", argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_fetch_lock(mem_ctx, ctdb->ev, ctdb->client,
+			      db, key, false, &h, NULL, &data);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to fetch record for key %s\n",
+			argv[1]);
+		return ret;
+	}
+
+	ret = ctdb_delete_record(h);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to delete record for key %s\n",
+			argv[1]);
+	}
+
+	talloc_free(h);
+	return ret;
+}
+
+static int control_checktcpport(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				int argc, const char **argv)
+{
+	struct sockaddr_in sin;
+	unsigned int port;
+	int s, v;
+	int ret;
+
+	if (argc != 1) {
+		usage("chktcpport");
+	}
+
+	port = atoi(argv[0]);
+
+	s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (s == -1) {
+		fprintf(stderr, "Failed to open local socket\n");
+		return errno;
+	}
+
+	v = fcntl(s, F_GETFL, 0);
+	if (v == -1 || fcntl(s, F_SETFL, v | O_NONBLOCK)) {
+		fprintf(stderr, "Unable to set socket non-blocking\n");
+		return errno;
+	}
+
+	bzero(&sin, sizeof(sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(port);
+	ret = bind(s, (struct sockaddr *)&sin, sizeof(sin));
+	close(s);
+	if (ret == -1) {
+		fprintf(stderr, "Failed to bind to TCP port %u\n", port);
+		return errno;
+	}
+
+	return 0;
+}
+
+static int control_rebalancenode(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+				 int argc, const char **argv)
+{
+	if (argc != 0) {
+		usage("rebalancenode");
+	}
+
+	if (! rebalancenode(mem_ctx, ctdb, ctdb->cmd_pnn)) {
+		fprintf(stderr, "Failed to rebalance IPs on node %u\n",
+			ctdb->cmd_pnn);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int control_getdbseqnum(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			       int argc, const char **argv)
+{
+	uint32_t db_id;
+	const char *db_name;
+	uint64_t seqnum;
+	int ret;
+
+	if (argc != 1) {
+		usage("getdbseqnum");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, NULL)) {
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_db_seqnum(mem_ctx, ctdb->ev, ctdb->client,
+				      ctdb->cmd_pnn, TIMEOUT(), db_id,
+				      &seqnum);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get sequence number for DB %s\n",
+			db_name);
+		return ret;
+	}
+
+	printf("0x%"PRIx64"\n", seqnum);
+	return 0;
+}
+
+static int control_nodestatus(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			      int argc, const char **argv)
+{
+	const char *nodestring = NULL;
+	struct ctdb_node_map *nodemap;
+	int ret, i;
+
+	if (argc > 1) {
+		usage("nodestatus");
+	}
+
+	if (argc == 1) {
+		nodestring = argv[0];
+	}
+
+	if (! parse_nodestring(mem_ctx, ctdb, nodestring, &nodemap)) {
+		return 1;
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	if (options.machinereadable) {
+		print_nodemap_machine(mem_ctx, ctdb, nodemap, ctdb->cmd_pnn);
+	} else {
+		print_nodemap(mem_ctx, ctdb, nodemap, ctdb->cmd_pnn);
+	}
+
+	ret = 0;
+	for (i=0; i<nodemap->num; i++) {
+		ret |= nodemap->node[i].flags;
+	}
+
+	return ret;
+}
+
+const struct {
+	const char *name;
+	uint32_t offset;
+} db_stats_fields[] = {
+#define DBSTATISTICS_FIELD(n) { #n, offsetof(struct ctdb_db_statistics, n) }
+	DBSTATISTICS_FIELD(db_ro_delegations),
+	DBSTATISTICS_FIELD(db_ro_revokes),
+	DBSTATISTICS_FIELD(locks.num_calls),
+	DBSTATISTICS_FIELD(locks.num_current),
+	DBSTATISTICS_FIELD(locks.num_pending),
+	DBSTATISTICS_FIELD(locks.num_failed),
+	DBSTATISTICS_FIELD(db_ro_delegations),
+};
+
+static void print_dbstatistics(const char *db_name,
+			       struct ctdb_db_statistics *s)
+{
+	int i;
+	const char *prefix = NULL;
+	int preflen = 0;
+
+	printf("DB Statistics %s\n", db_name);
+
+	for (i=0; i<ARRAY_SIZE(db_stats_fields); i++) {
+		if (strchr(db_stats_fields[i].name, '.') != NULL) {
+			preflen = strcspn(db_stats_fields[i].name, ".") + 1;
+			if (! prefix ||
+			    strncmp(prefix, db_stats_fields[i].name, preflen) != 0) {
+				prefix = db_stats_fields[i].name;
+				printf(" %*.*s\n", preflen-1, preflen-1,
+				       db_stats_fields[i].name);
+			}
+		} else {
+			preflen = 0;
+		}
+		printf(" %*s%-22s%*s%10u\n", preflen ? 4 : 0, "",
+		       db_stats_fields[i].name+preflen, preflen ? 0 : 4, "",
+		       *(uint32_t *)(db_stats_fields[i].offset+(uint8_t *)s));
+	}
+
+	printf(" hop_count_buckets:");
+	for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+		printf(" %d", s->hop_count_bucket[i]);
+	}
+	printf("\n");
+
+	printf(" lock_buckets:");
+	for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+		printf(" %d", s->locks.buckets[i]);
+	}
+	printf("\n");
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "locks_latency      MIN/AVG/MAX",
+	       s->locks.latency.min, LATENCY_AVG(s->locks.latency),
+	       s->locks.latency.max, s->locks.latency.num);
+
+	printf(" %-30s     %.6f/%.6f/%.6f sec out of %d\n",
+	       "vacuum_latency     MIN/AVG/MAX",
+	       s->vacuum.latency.min, LATENCY_AVG(s->vacuum.latency),
+	       s->vacuum.latency.max, s->vacuum.latency.num);
+
+	printf(" Num Hot Keys:     %d\n", s->num_hot_keys);
+	for (i=0; i<s->num_hot_keys; i++) {
+		int j;
+		printf("     Count:%d Key:", s->hot_keys[i].count);
+		for (j=0; j<s->hot_keys[i].key.dsize; j++) {
+			printf("%02x", s->hot_keys[i].key.dptr[j] & 0xff);
+		}
+		printf("\n");
+	}
+}
+
+static int control_dbstatistics(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			        int argc, const char **argv)
+{
+	uint32_t db_id;
+	const char *db_name;
+	struct ctdb_db_statistics *dbstats;
+	int ret;
+
+	if (argc != 1) {
+		usage("dbstatistics");
+	}
+
+	if (! db_exists(mem_ctx, ctdb, argv[0], &db_id, &db_name, NULL)) {
+		return 1;
+	}
+
+	ret = ctdb_ctrl_get_db_statistics(mem_ctx, ctdb->ev, ctdb->client,
+					  ctdb->cmd_pnn, TIMEOUT(), db_id,
+					  &dbstats);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to get statistics for DB %s\n",
+			db_name);
+		return ret;
+	}
+
+	print_dbstatistics(db_name, dbstats);
+	return 0;
+}
+
+struct disable_takeover_runs_state {
+	uint32_t *pnn_list;
+	int node_count;
+	bool *reply;
+	int status;
+	bool done;
+};
+
+static void disable_takeover_run_handler(uint64_t srvid, TDB_DATA data,
+					 void *private_data)
+{
+	struct disable_takeover_runs_state *state =
+		(struct disable_takeover_runs_state *)private_data;
+	int ret, i;
+
+	if (data.dsize != sizeof(int)) {
+		/* Ignore packet */
+		return;
+	}
+
+	/* ret will be a PNN (i.e. >=0) on success, or negative on error */
+	ret = *(int *)data.dptr;
+	if (ret < 0) {
+		state->status = ret;
+		state->done = true;
+		return;
+	}
+	for (i=0; i<state->node_count; i++) {
+		if (state->pnn_list[i] == ret) {
+			state->reply[i] = true;
+			break;
+		}
+	}
+
+	state->done = true;
+	for (i=0; i<state->node_count; i++) {
+		if (! state->reply[i]) {
+			state->done = false;
+			break;
+		}
+	}
+}
+
+static int disable_takeover_runs(TALLOC_CTX *mem_ctx,
+				 struct ctdb_context *ctdb, uint32_t timeout,
+				 uint32_t *pnn_list, int count)
+{
+	struct ctdb_disable_message disable = { 0 };
+	struct disable_takeover_runs_state state;
+	int ret, i;
+
+	disable.pnn = ctdb->pnn;
+	disable.srvid = next_srvid(ctdb);
+	disable.timeout = timeout;
+
+	state.pnn_list = pnn_list;
+	state.node_count = count;
+	state.done = false;
+	state.status = 0;
+	state.reply = talloc_zero_array(mem_ctx, bool, count);
+	if (state.reply == NULL) {
+		return ENOMEM;
+	}
+
+	ret = ctdb_client_set_message_handler(ctdb->ev, ctdb->client,
+					      disable.srvid,
+					      disable_takeover_run_handler,
+					      &state);
+	if (ret != 0) {
+		return ret;
+	}
+
+	for (i=0; i<count; i++) {
+		ret = ctdb_message_disable_takeover_runs(mem_ctx, ctdb->ev,
+							 ctdb->client,
+							 pnn_list[i],
+							 &disable);
+		if (ret != 0) {
+			goto fail;
+		}
+	}
+
+	ret = ctdb_client_wait_timeout(ctdb->ev, &state.done, TIMEOUT());
+	if (ret == ETIME) {
+		fprintf(stderr, "Timed out waiting to disable takeover runs\n");
+	} else {
+		ret = (state.status >= 0 ? 0 : 1);
+	}
+
+fail:
+	ctdb_client_remove_message_handler(ctdb->ev, ctdb->client,
+					   disable.srvid, &state);
+	return ret;
+}
+
+static int control_reloadips(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			     int argc, const char **argv)
+{
+	const char *nodestring = NULL;
+	struct ctdb_node_map *nodemap, *nodemap2;
+	struct ctdb_req_control request;
+	uint32_t *pnn_list, *pnn_list2;
+	int ret, count, count2;
+
+	if (argc > 1) {
+		usage("reloadips");
+	}
+
+	if (argc == 1) {
+		nodestring = argv[0];
+	}
+
+	nodemap = get_nodemap(ctdb, false);
+	if (nodemap == NULL) {
+		return 1;
+	}
+
+	if (! parse_nodestring(mem_ctx, ctdb, nodestring, &nodemap2)) {
+		return 1;
+	}
+
+	count = list_of_connected_nodes(nodemap, CTDB_UNKNOWN_PNN,
+					mem_ctx, &pnn_list);
+	if (count <= 0) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	count2 = list_of_active_nodes(nodemap2, CTDB_UNKNOWN_PNN,
+				      mem_ctx, &pnn_list2);
+	if (count2 <= 0) {
+		fprintf(stderr, "Memory allocation error\n");
+		return 1;
+	}
+
+	/* Disable takeover runs on all connected nodes.  A reply
+	 * indicating success is needed from each node so all nodes
+	 * will need to be active.
+	 *
+	 * A check could be added to not allow reloading of IPs when
+	 * there are disconnected nodes.  However, this should
+	 * probably be left up to the administrator.
+	 */
+	ret = disable_takeover_runs(mem_ctx, ctdb, 2*options.timelimit,
+				    pnn_list, count);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to disable takeover runs\n");
+		return ret;
+	}
+
+	/* Now tell all the desired nodes to reload their public IPs.
+	 * Keep trying this until it succeeds.  This assumes all
+	 * failures are transient, which might not be true...
+	 */
+	ctdb_req_control_reload_public_ips(&request);
+	ret = ctdb_client_control_multi(mem_ctx, ctdb->ev, ctdb->client,
+					pnn_list2, count2, TIMEOUT(),
+					&request, NULL, NULL);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to reload IPs on some nodes.\n");
+	}
+
+	/* It isn't strictly necessary to wait until takeover runs are
+	 * re-enabled but doing so can't hurt.
+	 */
+	ret = disable_takeover_runs(mem_ctx, ctdb, 0, pnn_list, count);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to enable takeover runs\n");
+		return ret;
+	}
+
+	return ipreallocate(mem_ctx, ctdb);
+}
+
+static int control_ipiface(TALLOC_CTX *mem_ctx, struct ctdb_context *ctdb,
+			   int argc, const char **argv)
+{
+	ctdb_sock_addr addr;
+	char *iface;
+
+	if (argc != 1) {
+		usage("ipiface");
+	}
+
+	if (! parse_ip(argv[0], NULL, 0, &addr)) {
+		fprintf(stderr, "Failed to Parse IP %s\n", argv[0]);
+		return 1;
+	}
+
+	iface = ctdb_sys_find_ifname(&addr);
+	if (iface == NULL) {
+		fprintf(stderr, "Failed to find interface for IP %s\n",
+			argv[0]);
+		return 1;
+	}
+	free(iface);
+
+	return 0;
+}
+
+
+static const struct ctdb_cmd {
+	const char *name;
+	int (*fn)(TALLOC_CTX *, struct ctdb_context *, int, const char **);
+	bool without_daemon; /* can be run without daemon running ? */
+	bool remote; /* can be run on remote nodes */
+	const char *msg;
+	const char *args;
+} ctdb_commands[] = {
+	{ "version", control_version, true, false,
+		"show version of ctdb", NULL },
+	{ "status", control_status, false, true,
+		"show node status", NULL },
+	{ "uptime", control_uptime, false, true,
+		"show node uptime", NULL },
+	{ "ping", control_ping, false, true,
+		"ping all nodes", NULL },
+	{ "runstate", control_runstate, false, true,
+		"get/check runstate of a node",
+		"[setup|first_recovery|startup|running]" },
+	{ "getvar", control_getvar, false, true,
+		"get a tunable variable", "<name>" },
+	{ "setvar", control_setvar, false, true,
+		"set a tunable variable", "<name> <value>" },
+	{ "listvars", control_listvars, false, true,
+		"list tunable variables", NULL },
+	{ "statistics", control_statistics, false, true,
+		"show ctdb statistics", NULL },
+	{ "statisticsreset", control_statistics_reset, false, true,
+		"reset ctdb statistics", NULL },
+	{ "stats", control_stats, false, true,
+		"show rolling statistics", "[count]" },
+	{ "ip", control_ip, false, true,
+		"show public ips", "[all]" },
+	{ "ipinfo", control_ipinfo, false, true,
+		"show public ip details", "<ip>" },
+	{ "ifaces", control_ifaces, false, true,
+		"show interfaces", NULL },
+	{ "setifacelink", control_setifacelink, false, true,
+		"set interface link status", "<iface> up|down" },
+	{ "process-exists", control_process_exists, false, true,
+		"check if a process exists on a node",  "<pid>" },
+	{ "getdbmap", control_getdbmap, false, true,
+		"show attached databases", NULL },
+	{ "getdbstatus", control_getdbstatus, false, true,
+		"show database status", "<dbname|dbid>" },
+	{ "catdb", control_catdb, false, false,
+		"dump cluster-wide ctdb database", "<dbname|dbid>" },
+	{ "cattdb", control_cattdb, false, false,
+		"dump local ctdb database", "<dbname|dbid>" },
+	{ "getmonmode", control_getmonmode, false, true,
+		"show monitoring mode", NULL },
+	{ "getcapabilities", control_getcapabilities, false, true,
+		"show node capabilities", NULL },
+	{ "pnn", control_pnn, false, false,
+		"show the pnn of the currnet node", NULL },
+	{ "lvs", control_lvs, false, false,
+		"show lvs configuration", "master|list|status" },
+	{ "disablemonitor", control_disable_monitor, false, true,
+		"disable monitoring", NULL },
+	{ "enablemonitor", control_enable_monitor, false, true,
+		"enable monitoring", NULL },
+	{ "setdebug", control_setdebug, false, true,
+		"set debug level", "ERROR|WARNING|NOTICE|INFO|DEBUG" },
+	{ "getdebug", control_getdebug, false, true,
+		"get debug level", NULL },
+	{ "attach", control_attach, false, false,
+		"attach a database", "<dbname> [persistent]" },
+	{ "detach", control_detach, false, false,
+		"detach database(s)", "<dbname|dbid> ..." },
+	{ "dumpmemory", control_dumpmemory, false, true,
+		"dump ctdbd memory map", NULL },
+	{ "rddumpmemory", control_rddumpmemory, false, true,
+		"dump recoverd memory map", NULL },
+	{ "getpid", control_getpid, false, true,
+		"get ctdbd process ID", NULL },
+	{ "disable", control_disable, false, true,
+		"disable a node", NULL },
+	{ "enable", control_enable, false, true,
+		"enable a node", NULL },
+	{ "stop", control_stop, false, true,
+		"stop a node", NULL },
+	{ "continue", control_continue, false, true,
+		"continue a stopped node", NULL },
+	{ "ban", control_ban, false, true,
+		"ban a node", "<bantime>"},
+	{ "unban", control_unban, false, true,
+		"unban a node", NULL },
+	{ "shutdown", control_shutdown, false, true,
+		"shutdown ctdb daemon", NULL },
+	{ "recover", control_recover, false, true,
+		"force recovery", NULL },
+	{ "sync", control_ipreallocate, false, true,
+		"run ip reallocation (deprecated)", NULL },
+	{ "ipreallocate", control_ipreallocate, false, true,
+		"run ip reallocation", NULL },
+	{ "isnotrecmaster", control_isnotrecmaster, false, false,
+		"check if local node is the recmaster", NULL },
+	{ "gratarp", control_gratarp, false, true,
+		"send a gratuitous arp", "<ip> <interface>" },
+	{ "tickle", control_tickle, false, false,
+		"send a tcp tickle ack", "<srcip:port> <dstip:port>" },
+	{ "gettickles", control_gettickles, false, true,
+		"get the list of tickles", "<ip> [<port>]" },
+	{ "addtickle", control_addtickle, false, true,
+		"add a tickle", "<ip>:<port> <ip>:<port>" },
+	{ "deltickle", control_deltickle, false, true,
+		"delete a tickle", "<ip>:<port> <ip>:<port>" },
+	{ "check_srvids", control_check_srvids, false, true,
+		"check if srvid is registered", "<id> [<id> ...]" },
+	{ "listnodes", control_listnodes, true, true,
+		"list nodes in the cluster", NULL },
+	{ "reloadnodes", control_reloadnodes, false, false,
+		"reload the nodes file all nodes", NULL },
+	{ "moveip", control_moveip, false, false,
+		"move an ip address to another node", "<ip> <node>" },
+	{ "rebalanceip", control_rebalanceip, false, false,
+		"move an ip address optimally to another node", "<ip>" },
+	{ "addip", control_addip, false, true,
+		"add an ip address to a node", "<ip/mask> <iface>" },
+	{ "delip", control_delip, false, true,
+		"delete an ip address from a node", "<ip>" },
+	{ "eventscript", control_eventscript, false, true,
+		"run an event", "monitor" },
+	{ "backupdb", control_backupdb, false, false,
+		"backup a database into a file", "<dbname|dbid> <file>" },
+	{ "restoredb", control_restoredb, false, false,
+		"restore a database from a file", "<file> [dbname]" },
+	{ "dumpdbbackup", control_dumpdbbackup, true, false,
+		"dump database from a backup file", "<file>" },
+	{ "wipedb", control_wipedb, false, false,
+		"wipe the contents of a database.", "<dbname|dbid>"},
+	{ "recmaster", control_recmaster, false, true,
+		"show the pnn for the recovery master", NULL },
+	{ "scriptstatus", control_scriptstatus, false, true,
+		"show event script status",
+		"[init|setup|startup|monitor|takeip|releaseip|ipreallocated]" },
+	{ "enablescript", control_enablescript, false, true,
+		"enable an eventscript", "<script>"},
+	{ "disablescript", control_disablescript, false, true,
+		"disable an eventscript", "<script>"},
+	{ "natgw", control_natgw, false, false,
+		"show natgw configuration", "master|list|status" },
+	{ "natgwlist", control_natgwlist, false, false,
+		"show the nodes belonging to this natgw configuration", NULL },
+	{ "getreclock", control_getreclock, false, true,
+		"get recovery lock file", NULL },
+	{ "setlmasterrole", control_setlmasterrole, false, true,
+		"set LMASTER role", "on|off" },
+	{ "setrecmasterrole", control_setrecmasterrole, false, true,
+		"set RECMASTER role", "on|off"},
+	{ "setdbreadonly", control_setdbreadonly, false, true,
+		"enable readonly records", "<dbname|dbid>" },
+	{ "setdbsticky", control_setdbsticky, false, true,
+		"enable sticky records", "<dbname|dbid>"},
+	{ "pfetch", control_pfetch, false, false,
+		"fetch record from persistent database", "<dbname|dbid> <key> [<file>]" },
+	{ "pstore", control_pstore, false, false,
+		"write record to persistent database", "<dbname|dbid> <key> <value>" },
+	{ "pdelete", control_pdelete, false, false,
+		"delete record from persistent database", "<dbname|dbid> <key>" },
+	{ "ptrans", control_ptrans, false, false,
+		"update a persistent database (from file or stdin)", "<dbname|dbid> [<file>]" },
+	{ "tfetch", control_tfetch, false, true,
+		"fetch a record", "<tdb-file> <key> [<file>]" },
+	{ "tstore", control_tstore, false, true,
+		"store a record", "<tdb-file> <key> <data> [<rsn> <dmaster> <flags>]" },
+	{ "readkey", control_readkey, false, false,
+		"read value of a database key", "<dbname|dbid> <key> [readonly]" },
+	{ "writekey", control_writekey, false, false,
+		"write value for a database key", "<dbname|dbid> <key> <value>" },
+	{ "deletekey", control_deletekey, false, false,
+		"delete a database key", "<dbname|dbid> <key>" },
+	{ "checktcpport", control_checktcpport, true, false,
+		"check if a service is bound to a specific tcp port or not", "<port>" },
+	{ "rebalancenode", control_rebalancenode, false, true,
+		"mark nodes as forced IP rebalancing targets", NULL },
+	{ "getdbseqnum", control_getdbseqnum, false, false,
+		"get database sequence number", "<dbname|dbid>" },
+	{ "nodestatus", control_nodestatus, false, true,
+		"show and return node status", "[all|<pnn-list>]" },
+	{ "dbstatistics", control_dbstatistics, false, true,
+		"show database statistics", "<dbname|dbid>" },
+	{ "reloadips", control_reloadips, false, false,
+		"reload the public addresses file", "[all|<pnn-list>]" },
+	{ "ipiface", control_ipiface, true, false,
+		"Find the interface an ip address is hosted on", "<ip>" },
+};
+
+static const struct ctdb_cmd *match_command(const char *command)
+{
+	const struct ctdb_cmd *cmd;
+	int i;
+
+	for (i=0; i<ARRAY_SIZE(ctdb_commands); i++) {
+		cmd = &ctdb_commands[i];
+		if (strlen(command) == strlen(cmd->name) &&
+		    strncmp(command, cmd->name, strlen(command)) == 0) {
+			return cmd;
+		}
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Show usage message
+ */
+static void usage_full(void)
+{
+	int i;
+
+	poptPrintHelp(pc, stdout, 0);
+	printf("\nCommands:\n");
+	for (i=0; i<ARRAY_SIZE(ctdb_commands); i++) {
+		printf("  %-15s %-27s  %s\n",
+		       ctdb_commands[i].name,
+		       ctdb_commands[i].args ? ctdb_commands[i].args : "",
+		       ctdb_commands[i].msg);
+	}
+}
+
+static void usage(const char *command)
+{
+	const struct ctdb_cmd *cmd;
+
+	if (command == NULL) {
+		usage_full();
+		exit(1);
+	}
+
+	cmd = match_command(command);
+	if (cmd == NULL) {
+		usage_full();
+	} else {
+		poptPrintUsage(pc, stdout, 0);
+		printf("\nCommands:\n");
+		printf("  %-15s %-27s  %s\n",
+		       cmd->name, cmd->args ? cmd->args : "", cmd->msg);
+	}
+
+	exit(1);
+}
+
+struct poptOption cmdline_options[] = {
+	POPT_AUTOHELP
+	{ "socket", 's', POPT_ARG_STRING, &options.socket, 0,
+		"CTDB socket path", "filename" },
+	{ "debug", 'd', POPT_ARG_STRING, &options.debuglevelstr, 0,
+		"debug level"},
+	{ "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0,
+		"timelimit (in seconds)" },
+	{ "node", 'n', POPT_ARG_INT, &options.pnn, 0,
+		"node specification - integer" },
+	{ NULL, 'Y', POPT_ARG_NONE, &options.machinereadable, 0,
+		"enable machine readable output", NULL },
+	{ "separator", 'x', POPT_ARG_STRING, &options.sep, 0,
+		"specify separator for machine readable output", "CHAR" },
+	{ NULL, 'X', POPT_ARG_NONE, &options.machineparsable, 0,
+		"enable machine parsable output with separator |", NULL },
+	{ "verbose", 'v', POPT_ARG_NONE, &options.verbose, 0,
+		"enable verbose output", NULL },
+	{ "maxruntime", 'T', POPT_ARG_INT, &options.maxruntime, 0,
+		"die if runtime exceeds this limit (in seconds)" },
+	POPT_TABLEEND
+};
+
+static int process_command(const struct ctdb_cmd *cmd, int argc,
+			   const char **argv)
+{
+	TALLOC_CTX *tmp_ctx;
+	struct ctdb_context *ctdb;
+	int ret;
+	bool status;
+	uint64_t srvid_offset;
+
+	tmp_ctx = talloc_new(NULL);
+	if (tmp_ctx == NULL) {
+		fprintf(stderr, "Memory allocation error\n");
+		goto fail;
+	}
+
+	if (cmd->without_daemon) {
+		if (options.pnn != -1) {
+			fprintf(stderr,
+				"Cannot specify node for command %s\n",
+				cmd->name);
+			goto fail;
+		}
+
+		return cmd->fn(tmp_ctx, NULL, argc-1, argv+1);
+	}
+
+	ctdb = talloc_zero(tmp_ctx, struct ctdb_context);
+	if (ctdb == NULL) {
+		fprintf(stderr, "Memory allocation error\n");
+		goto fail;
+	}
+
+	ctdb->ev = tevent_context_init(ctdb);
+	if (ctdb->ev == NULL) {
+		fprintf(stderr, "Failed to initialize tevent\n");
+		goto fail;
+	}
+
+	ret = ctdb_client_init(ctdb, ctdb->ev, options.socket, &ctdb->client);
+	if (ret != 0) {
+		fprintf(stderr, "Failed to connect to CTDB daemon (%s)\n",
+			options.socket);
+
+		if (!find_node_xpnn(ctdb, NULL)) {
+			fprintf(stderr, "Is this node part of CTDB cluster?\n");
+		}
+		goto fail;
+	}
+
+	ctdb->pnn = ctdb_client_pnn(ctdb->client);
+	srvid_offset = getpid() & 0xFFFF;
+	ctdb->srvid = SRVID_CTDB_TOOL | (srvid_offset << 16);
+
+	if (options.pnn != -1) {
+		status = verify_pnn(ctdb, options.pnn);
+		if (! status) {
+			goto fail;
+		}
+
+		ctdb->cmd_pnn = options.pnn;
+	} else {
+		ctdb->cmd_pnn = ctdb->pnn;
+	}
+
+	if (! cmd->remote && ctdb->pnn != ctdb->cmd_pnn) {
+		fprintf(stderr, "Node cannot be specified for command %s\n",
+			cmd->name);
+		goto fail;
+	}
+
+	ret = cmd->fn(tmp_ctx, ctdb, argc-1, argv+1);
+	talloc_free(tmp_ctx);
+	return ret;
+
+fail:
+	talloc_free(tmp_ctx);
+	return 1;
+}
+
+static void signal_handler(int sig)
+{
+	fprintf(stderr, "Maximum runtime exceeded - exiting\n");
+}
+
+static void alarm_handler(int sig)
+{
+	/* Kill any child processes */
+	signal(SIGTERM, signal_handler);
+	kill(0, SIGTERM);
+
+	_exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+	int opt;
+	const char **extra_argv;
+	int extra_argc;
+	const struct ctdb_cmd *cmd;
+	const char *ctdb_socket;
+	enum debug_level loglevel;
+	int ret;
+
+	setlinebuf(stdout);
+
+	/* Set default options */
+	options.socket = CTDB_SOCKET;
+	options.debuglevelstr = NULL;
+	options.timelimit = 10;
+	options.sep = "|";
+	options.maxruntime = 0;
+	options.pnn = -1;
+
+	ctdb_socket = getenv("CTDB_SOCKET");
+	if (ctdb_socket != NULL) {
+		options.socket = ctdb_socket;
+	}
+
+	pc = poptGetContext(argv[0], argc, argv, cmdline_options,
+			    POPT_CONTEXT_KEEP_FIRST);
+	while ((opt = poptGetNextOpt(pc)) != -1) {
+		fprintf(stderr, "Invalid option %s: %s\n",
+			poptBadOption(pc, 0), poptStrerror(opt));
+		exit(1);
+	}
+
+	if (options.maxruntime == 0) {
+		const char *ctdb_timeout;
+
+		ctdb_timeout = getenv("CTDB_TIMEOUT");
+		if (ctdb_timeout != NULL) {
+			options.maxruntime = strtoul(ctdb_timeout, NULL, 0);
+		} else {
+			options.maxruntime = 120;
+		}
+	}
+	if (options.maxruntime <= 120) {
+		/* default timeout is 120 seconds */
+		options.maxruntime = 120;
+	}
+
+	if (options.machineparsable) {
+		options.machinereadable = 1;
+	}
+
+	/* setup the remaining options for the commands */
+	extra_argc = 0;
+	extra_argv = poptGetArgs(pc);
+	if (extra_argv) {
+		extra_argv++;
+		while (extra_argv[extra_argc]) extra_argc++;
+	}
+
+	if (extra_argc < 1) {
+		usage(NULL);
+	}
+
+	cmd = match_command(extra_argv[0]);
+	if (cmd == NULL) {
+		fprintf(stderr, "Unknown command '%s'\n", extra_argv[0]);
+		exit(1);
+	}
+
+	/* Enable logging */
+	setup_logging("ctdb", DEBUG_STDERR);
+	if (debug_level_parse(options.debuglevelstr, &loglevel)) {
+		DEBUGLEVEL = loglevel;
+	} else {
+		DEBUGLEVEL = DEBUG_ERR;
+	}
+
+	signal(SIGALRM, alarm_handler);
+	alarm(options.maxruntime);
+
+	ret = process_command(cmd, extra_argc, extra_argv);
+	if (ret == -1) {
+		ret = 1;
+	}
+
+	(void)poptFreeContext(pc);
+
+	return ret;
+}
diff --git a/ctdb/wscript b/ctdb/wscript
index d15c56edcc1..d7174dd8766 100644
--- a/ctdb/wscript
+++ b/ctdb/wscript
@@ -421,6 +421,13 @@ def build(bld):
                      install_path='${SBINDIR}',
                      manpages='ctdbd.1')
 
+    bld.SAMBA_BINARY('ctdb',
+                     source='tools/ctdb.c',
+                     deps='''ctdb-client2 ctdb-protocol ctdb-util ctdb-system
+                             samba-util popt''',
+                     install_path='${BINDIR}',
+                     manpages='ctdb.1')
+
     bld.SAMBA_BINARY('ctdb_killtcp',
                      source='tools/ctdb_killtcp.c',
                      deps='''ctdb-protocol ctdb-util ctdb-system