diff --git a/ctdb/tests/src/fake_ctdbd.c b/ctdb/tests/src/fake_ctdbd.c
new file mode 100644
index 00000000000..e198a5dc441
--- /dev/null
+++ b/ctdb/tests/src/fake_ctdbd.c
@@ -0,0 +1,2303 @@
+/*
+ Fake CTDB server for testing
+
+ Copyright (C) Amitay Isaacs 2016
+
+ 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 .
+*/
+
+#include "replace.h"
+#include "system/network.h"
+#include "system/time.h"
+
+#include
+#include
+#include
+#include
+
+#include "lib/util/dlinklist.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+
+#include "protocol/protocol.h"
+#include "protocol/protocol_api.h"
+
+#include "common/comm.h"
+#include "common/system.h"
+#include "common/logging.h"
+
+
+#define CTDB_PORT 4379
+
+/* A fake flag that is only supported by some functions */
+#define NODE_FLAGS_FAKE_TIMEOUT 0x80000000
+
+struct node {
+ ctdb_sock_addr addr;
+ uint32_t pnn;
+ uint32_t flags;
+ uint32_t capabilities;
+ bool recovery_disabled;
+ void *recovery_substate;
+};
+
+struct node_map {
+ uint32_t num_nodes;
+ struct node *node;
+ uint32_t pnn;
+ uint32_t recmaster;
+};
+
+struct interface {
+ const char *name;
+ bool link_up;
+ uint32_t references;
+};
+
+struct interface_map {
+ int num;
+ struct interface *iface;
+};
+
+struct vnn_map {
+ uint32_t recmode;
+ uint32_t generation;
+ uint32_t size;
+ uint32_t *map;
+};
+
+struct srvid_register_state {
+ struct srvid_register_state *prev, *next;
+ struct ctdbd_context *ctdb;
+ uint64_t srvid;
+};
+
+struct ctdbd_context {
+ struct node_map *node_map;
+ struct interface_map *iface_map;
+ struct vnn_map *vnn_map;
+ struct srvid_register_state *rstate;
+ int num_clients;
+ struct timeval start_time;
+ struct timeval recovery_start_time;
+ struct timeval recovery_end_time;
+ bool takeover_disabled;
+};
+
+
+/*
+ * async wrapper around accept(2)
+ */
+
+struct accept_state {
+ int listen_fd;
+ struct tevent_fd *fde;
+ int client_fd;
+};
+
+static void accept_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data);
+
+static struct tevent_req *accept_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int listen_fd)
+{
+ struct tevent_req *req;
+ struct accept_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct accept_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->listen_fd = listen_fd;
+
+ state->fde = tevent_add_fd(ev, state, listen_fd, TEVENT_FD_READ,
+ accept_handler, req);
+ if (tevent_req_nomem(state->fde, req)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void accept_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct accept_state *state = tevent_req_data(
+ req, struct accept_state);
+ struct sockaddr addr;
+ socklen_t addrlen = sizeof(addr);
+ int ret;
+
+ TALLOC_FREE(state->fde);
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ret = accept(state->listen_fd, &addr, &addrlen);
+ if (ret == -1) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ state->client_fd = ret;
+ tevent_req_done(req);
+}
+
+static int accept_recv(struct tevent_req *req, int *perr)
+{
+ struct accept_state *state = tevent_req_data(
+ req, struct accept_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ return -1;
+ }
+
+ return state->client_fd;
+}
+
+/*
+ * Parse routines
+ */
+
+static struct node_map *nodemap_init(TALLOC_CTX *mem_ctx)
+{
+ struct node_map *node_map;
+
+ node_map = talloc_zero(mem_ctx, struct node_map);
+ if (node_map == NULL) {
+ return NULL;
+ }
+
+ node_map->pnn = CTDB_UNKNOWN_PNN;
+ node_map->recmaster = CTDB_UNKNOWN_PNN;
+
+ return node_map;
+}
+
+/* Read a nodemap from stdin. Each line looks like:
+ * [RECMASTER] [CURRENT] [CAPABILITIES]
+ * EOF or a blank line terminates input.
+ *
+ * By default, capablities for each node are
+ * CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER. These 2
+ * capabilities can be faked off by adding, for example,
+ * -CTDB_CAP_RECMASTER.
+ */
+
+static bool nodemap_parse(struct node_map *node_map)
+{
+ char line[1024];
+
+ while ((fgets(line, sizeof(line), stdin) != NULL)) {
+ uint32_t pnn, flags, capabilities;
+ char *tok, *t;
+ char *ip;
+ ctdb_sock_addr saddr;
+ struct node *node;
+
+ if (line[0] == '\n') {
+ break;
+ }
+
+ /* Get rid of pesky newline */
+ if ((t = strchr(line, '\n')) != NULL) {
+ *t = '\0';
+ }
+
+ /* Get PNN */
+ tok = strtok(line, " \t");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing PNN\n", line);
+ continue;
+ }
+ pnn = (uint32_t)strtoul(tok, NULL, 0);
+
+ /* Get IP */
+ tok = strtok(NULL, " \t");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing IP\n", line);
+ continue;
+ }
+ if (!parse_ip(tok, NULL, CTDB_PORT, &saddr)) {
+ fprintf(stderr, "bad line (%s) - invalid IP\n", line);
+ continue;
+ }
+ ip = talloc_strdup(node_map, tok);
+ if (ip == NULL) {
+ goto fail;
+ }
+
+ /* Get flags */
+ tok = strtok(NULL, " \t");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing flags\n",
+ line);
+ continue;
+ }
+ flags = (uint32_t)strtoul(tok, NULL, 0);
+ /* Handle deleted nodes */
+ if (flags & NODE_FLAGS_DELETED) {
+ talloc_free(ip);
+ ip = talloc_strdup(node_map, "0.0.0.0");
+ if (ip == NULL) {
+ goto fail;
+ }
+ }
+ capabilities = CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER;
+
+ tok = strtok(NULL, " \t");
+ while (tok != NULL) {
+ if (strcmp(tok, "CURRENT") == 0) {
+ node_map->pnn = pnn;
+ } else if (strcmp(tok, "RECMASTER") == 0) {
+ node_map->recmaster = pnn;
+ } else if (strcmp(tok, "-CTDB_CAP_RECMASTER") == 0) {
+ capabilities &= ~CTDB_CAP_RECMASTER;
+ } else if (strcmp(tok, "-CTDB_CAP_LMASTER") == 0) {
+ capabilities &= ~CTDB_CAP_LMASTER;
+ } else if (strcmp(tok, "TIMEOUT") == 0) {
+ /* This can be done with just a flag
+ * value but it is probably clearer
+ * and less error-prone to fake this
+ * with an explicit token */
+ flags |= NODE_FLAGS_FAKE_TIMEOUT;
+ }
+ tok = strtok(NULL, " \t");
+ }
+
+ node_map->node = talloc_realloc(node_map, node_map->node,
+ struct node,
+ node_map->num_nodes + 1);
+ if (node_map->node == NULL) {
+ goto fail;
+ }
+ node = &node_map->node[node_map->num_nodes];
+
+ parse_ip(ip, NULL, CTDB_PORT, &node->addr);
+ node->pnn = pnn;
+ node->flags = flags;
+ node->capabilities = capabilities;
+ node->recovery_disabled = false;
+ node->recovery_substate = NULL;
+
+ node_map->num_nodes += 1;
+ }
+
+ DEBUG(DEBUG_INFO, ("Parsing nodemap done\n"));
+ return true;
+
+fail:
+ DEBUG(DEBUG_INFO, ("Parsing nodemap failed\n"));
+ return false;
+
+}
+
+/* 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, CTDB_PORT, &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 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[PATH_MAX];
+ const char *nodes_list;
+
+ /* read the nodes file */
+ sprintf(nodepath, "CTDB_NODES_%u", pnn);
+ nodes_list = getenv(nodepath);
+ if (nodes_list == NULL) {
+ nodes_list = getenv("CTDB_NODES");
+ if (nodes_list == NULL) {
+ DEBUG(DEBUG_INFO, ("Nodes file not defined\n"));
+ return NULL;
+ }
+ }
+
+ nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list);
+ if (nodemap == NULL) {
+ DEBUG(DEBUG_INFO, ("Failed to read nodes file \"%s\"\n",
+ nodes_list));
+ return NULL;
+ }
+
+ return nodemap;
+}
+
+static struct interface_map *interfaces_init(TALLOC_CTX *mem_ctx)
+{
+ struct interface_map *iface_map;
+
+ iface_map = talloc_zero(mem_ctx, struct interface_map);
+ if (iface_map == NULL) {
+ return NULL;
+ }
+
+ return iface_map;
+}
+
+/* Read interfaces information. Same format as "ctdb ifaces -Y"
+ * output:
+ * :Name:LinkStatus:References:
+ * :eth2:1:4294967294
+ * :eth1:1:4294967292
+ */
+
+static bool interfaces_parse(struct interface_map *iface_map)
+{
+ char line[1024];
+
+ while ((fgets(line, sizeof(line), stdin) != NULL)) {
+ uint16_t link_state;
+ uint32_t references;
+ char *tok, *t, *name;
+ struct interface *iface;
+
+ if (line[0] == '\n') {
+ break;
+ }
+
+ /* Get rid of pesky newline */
+ if ((t = strchr(line, '\n')) != NULL) {
+ *t = '\0';
+ }
+
+ if (strcmp(line, ":Name:LinkStatus:References:") == 0) {
+ continue;
+ }
+
+ /* Leading colon... */
+ // tok = strtok(line, ":");
+
+ /* name */
+ tok = strtok(line, ":");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing name\n", line);
+ continue;
+ }
+ name = tok;
+
+ /* link_state */
+ tok = strtok(NULL, ":");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing link state\n",
+ line);
+ continue;
+ }
+ link_state = (uint16_t)strtoul(tok, NULL, 0);
+
+ /* references... */
+ tok = strtok(NULL, ":");
+ if (tok == NULL) {
+ fprintf(stderr, "bad line (%s) - missing references\n",
+ line);
+ continue;
+ }
+ references = (uint32_t)strtoul(tok, NULL, 0);
+
+ iface_map->iface = talloc_realloc(iface_map, iface_map->iface,
+ struct interface,
+ iface_map->num + 1);
+ if (iface_map->iface == NULL) {
+ goto fail;
+ }
+
+ iface = &iface_map->iface[iface_map->num];
+
+ iface->name = talloc_strdup(iface_map, name);
+ if (iface->name == NULL) {
+ goto fail;
+ }
+ iface->link_up = link_state;
+ iface->references = references;
+
+ iface_map->num += 1;
+ }
+
+ DEBUG(DEBUG_INFO, ("Parsing interfaces done\n"));
+ return true;
+
+fail:
+ fprintf(stderr, "Parsing interfaces failed\n");
+ return false;
+}
+
+static struct vnn_map *vnnmap_init(TALLOC_CTX *mem_ctx)
+{
+ struct vnn_map *vnn_map;
+
+ vnn_map = talloc_zero(mem_ctx, struct vnn_map);
+ if (vnn_map == NULL) {
+ fprintf(stderr, "Memory error\n");
+ return NULL;
+ }
+ vnn_map->recmode = CTDB_RECOVERY_ACTIVE;
+ vnn_map->generation = INVALID_GENERATION;
+
+ return vnn_map;
+}
+
+/* Read vnn map.
+ * output:
+ *
+ *
+ *
+ * ...
+ */
+
+static bool vnnmap_parse(struct vnn_map *vnn_map)
+{
+ char line[1024];
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ uint32_t n;
+ char *t;
+
+ if (line[0] == '\n') {
+ break;
+ }
+
+ /* Get rid of pesky newline */
+ if ((t = strchr(line, '\n')) != NULL) {
+ *t = '\0';
+ }
+
+ n = (uint32_t) strtol(line, NULL, 0);
+
+ /* generation */
+ if (vnn_map->generation == INVALID_GENERATION) {
+ vnn_map->generation = n;
+ continue;
+ }
+
+ vnn_map->map = talloc_realloc(vnn_map, vnn_map->map, uint32_t,
+ vnn_map->size + 1);
+ if (vnn_map->map == NULL) {
+ fprintf(stderr, "Memory error\n");
+ goto fail;
+ }
+
+ vnn_map->map[vnn_map->size] = n;
+ vnn_map->size += 1;
+ }
+
+ DEBUG(DEBUG_INFO, ("Parsing vnnmap done\n"));
+ return true;
+
+fail:
+ fprintf(stderr, "Parsing vnnmap failed\n");
+ return false;
+}
+
+/*
+ * CTDB context setup
+ */
+
+static uint32_t new_generation(uint32_t old_generation)
+{
+ uint32_t generation;
+
+ while (1) {
+ generation = random();
+ if (generation != INVALID_GENERATION &&
+ generation != old_generation) {
+ break;
+ }
+ }
+
+ return generation;
+}
+
+static struct ctdbd_context *ctdbd_setup(TALLOC_CTX *mem_ctx)
+{
+ struct ctdbd_context *ctdb;
+ char line[1024];
+ bool status;
+
+ ctdb = talloc_zero(mem_ctx, struct ctdbd_context);
+ if (ctdb == NULL) {
+ return NULL;
+ }
+
+ ctdb->node_map = nodemap_init(ctdb);
+ if (ctdb->node_map == NULL) {
+ goto fail;
+ }
+
+ ctdb->iface_map = interfaces_init(ctdb);
+ if (ctdb->iface_map == NULL) {
+ goto fail;
+ }
+
+ ctdb->vnn_map = vnnmap_init(ctdb);
+ if (ctdb->vnn_map == NULL) {
+ goto fail;
+ }
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ char *t;
+
+ if ((t = strchr(line, '\n')) != NULL) {
+ *t = '\0';
+ }
+
+ if (strcmp(line, "NODEMAP") == 0) {
+ status = nodemap_parse(ctdb->node_map);
+ } else if (strcmp(line, "IFACES") == 0) {
+ status = interfaces_parse(ctdb->iface_map);
+ } else if (strcmp(line, "VNNMAP") == 0) {
+ status = vnnmap_parse(ctdb->vnn_map);
+ } else {
+ fprintf(stderr, "Unknown line %s\n", line);
+ status = false;
+ }
+
+ if (! status) {
+ goto fail;
+ }
+ }
+
+ ctdb->start_time = tevent_timeval_current();
+ ctdb->recovery_start_time = tevent_timeval_current();
+ ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL;
+ if (ctdb->vnn_map->generation == INVALID_GENERATION) {
+ ctdb->vnn_map->generation =
+ new_generation(ctdb->vnn_map->generation);
+ }
+ ctdb->recovery_end_time = tevent_timeval_current();
+
+ return ctdb;
+
+fail:
+ TALLOC_FREE(ctdb);
+ return NULL;
+}
+
+static bool ctdbd_verify(struct ctdbd_context *ctdb)
+{
+ struct node *node;
+ int i;
+
+ if (ctdb->node_map->num_nodes == 0) {
+ return true;
+ }
+
+ /* Make sure all the nodes are in order */
+ for (i=0; inode_map->num_nodes; i++) {
+ node = &ctdb->node_map->node[i];
+ if (node->pnn != i) {
+ fprintf(stderr, "Expected node %u, found %u\n",
+ i, node->pnn);
+ return false;
+ }
+ }
+
+ node = &ctdb->node_map->node[ctdb->node_map->pnn];
+ if (node->flags & NODE_FLAGS_DISCONNECTED) {
+ DEBUG(DEBUG_INFO, ("Node disconnected, exiting\n"));
+ exit(0);
+ }
+
+ return true;
+}
+
+/*
+ * Doing a recovery
+ */
+
+struct recover_state {
+ struct tevent_context *ev;
+ struct ctdbd_context *ctdb;
+};
+
+static int recover_check(struct tevent_req *req);
+static void recover_wait_done(struct tevent_req *subreq);
+static void recover_done(struct tevent_req *subreq);
+
+static struct tevent_req *recover_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_context *ctdb)
+{
+ struct tevent_req *req;
+ struct recover_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct recover_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctdb = ctdb;
+
+ ret = recover_check(req);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static int recover_check(struct tevent_req *req)
+{
+ struct recover_state *state = tevent_req_data(
+ req, struct recover_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct tevent_req *subreq;
+ bool recovery_disabled;
+ int i;
+
+ recovery_disabled = false;
+ for (i=0; inode_map->num_nodes; i++) {
+ if (ctdb->node_map->node[i].recovery_disabled) {
+ recovery_disabled = true;
+ break;
+ }
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (subreq == NULL) {
+ return ENOMEM;
+ }
+
+ if (recovery_disabled) {
+ tevent_req_set_callback(subreq, recover_wait_done, req);
+ } else {
+ ctdb->recovery_start_time = tevent_timeval_current();
+ tevent_req_set_callback(subreq, recover_done, req);
+ }
+
+ return 0;
+}
+
+static void recover_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ret = recover_check(req);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static void recover_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct recover_state *state = tevent_req_data(
+ req, struct recover_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL;
+ ctdb->recovery_end_time = tevent_timeval_current();
+ ctdb->vnn_map->generation = new_generation(ctdb->vnn_map->generation);
+
+ tevent_req_done(req);
+}
+
+static bool recover_recv(struct tevent_req *req, int *perr)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Routines for ctdb_req_header
+ */
+
+static void header_fix_pnn(struct ctdb_req_header *header,
+ struct ctdbd_context *ctdb)
+{
+ if (header->srcnode == CTDB_CURRENT_NODE) {
+ header->srcnode = ctdb->node_map->pnn;
+ }
+
+ if (header->destnode == CTDB_CURRENT_NODE) {
+ header->destnode = ctdb->node_map->pnn;
+ }
+}
+
+static struct ctdb_req_header header_reply_control(
+ struct ctdb_req_header *header,
+ struct ctdbd_context *ctdb)
+{
+ struct ctdb_req_header reply_header;
+
+ reply_header = (struct ctdb_req_header) {
+ .ctdb_magic = CTDB_MAGIC,
+ .ctdb_version = CTDB_PROTOCOL,
+ .generation = ctdb->vnn_map->generation,
+ .operation = CTDB_REPLY_CONTROL,
+ .destnode = header->srcnode,
+ .srcnode = header->destnode,
+ .reqid = header->reqid,
+ };
+
+ return reply_header;
+}
+
+static struct ctdb_req_header header_reply_message(
+ struct ctdb_req_header *header,
+ struct ctdbd_context *ctdb)
+{
+ struct ctdb_req_header reply_header;
+
+ reply_header = (struct ctdb_req_header) {
+ .ctdb_magic = CTDB_MAGIC,
+ .ctdb_version = CTDB_PROTOCOL,
+ .generation = ctdb->vnn_map->generation,
+ .operation = CTDB_REQ_MESSAGE,
+ .destnode = header->srcnode,
+ .srcnode = header->destnode,
+ .reqid = 0,
+ };
+
+ return reply_header;
+}
+
+/*
+ * Client state
+ */
+
+struct client_state {
+ struct tevent_context *ev;
+ int fd;
+ struct ctdbd_context *ctdb;
+ int pnn;
+ struct comm_context *comm;
+ struct srvid_register_state *rstate;
+ int status;
+};
+
+/*
+ * Send replies to controls and messages
+ */
+
+static void client_reply_done(struct tevent_req *subreq);
+
+static void client_send_message(struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_message_data *message)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct tevent_req *subreq;
+ struct ctdb_req_header reply_header;
+ uint8_t *buf;
+ size_t datalen, buflen;
+ int ret;
+
+ reply_header = header_reply_message(header, ctdb);
+
+ datalen = ctdb_req_message_data_len(&reply_header, message);
+ ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ctdb_req_message_data_push(&reply_header, message,
+ buf, &buflen);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(DEBUG_INFO, ("message srvid = 0x%"PRIx64"\n", message->srvid));
+
+ subreq = comm_write_send(state, state->ev, state->comm, buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, client_reply_done, req);
+
+ talloc_steal(subreq, buf);
+}
+
+static void client_send_control(struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_reply_control *reply)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct tevent_req *subreq;
+ struct ctdb_req_header reply_header;
+ uint8_t *buf;
+ size_t datalen, buflen;
+ int ret;
+
+ reply_header = header_reply_control(header, ctdb);
+
+ datalen = ctdb_reply_control_len(&reply_header, reply);
+ ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ctdb_reply_control_push(&reply_header, reply, buf, &buflen);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DEBUG(DEBUG_INFO, ("reply opcode = %u\n", reply->rdata.opcode));
+
+ subreq = comm_write_send(state, state->ev, state->comm, buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, client_reply_done, req);
+
+ talloc_steal(subreq, buf);
+}
+
+static void client_reply_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+ bool status;
+
+ status = comm_write_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ }
+}
+
+/*
+ * Handling protocol - controls
+ */
+
+static void control_process_exists(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = kill(request->rdata.data.pid, 0);
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_ping(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = ctdb->num_clients;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_getvnnmap(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct ctdb_vnn_map *vnnmap;
+
+ reply.rdata.opcode = request->opcode;
+
+ vnnmap = talloc_zero(mem_ctx, struct ctdb_vnn_map);
+ if (vnnmap == NULL) {
+ reply.status = ENOMEM;
+ reply.errmsg = "Memory error";
+ } else {
+ vnnmap->generation = ctdb->vnn_map->generation;
+ vnnmap->size = ctdb->vnn_map->size;
+ vnnmap->map = ctdb->vnn_map->map;
+
+ reply.rdata.data.vnnmap = vnnmap;
+ reply.status = 0;
+ reply.errmsg = NULL;
+ }
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_recmode(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = ctdb->vnn_map->recmode;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+struct set_recmode_state {
+ struct tevent_req *req;
+ struct ctdbd_context *ctdb;
+ struct ctdb_req_header header;
+ struct ctdb_reply_control reply;
+};
+
+static void set_recmode_callback(struct tevent_req *subreq)
+{
+ struct set_recmode_state *substate = tevent_req_callback_data(
+ subreq, struct set_recmode_state);
+ bool status;
+ int ret;
+
+ status = recover_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ substate->reply.status = ret;
+ substate->reply.errmsg = "recovery failed";
+ } else {
+ substate->reply.status = 0;
+ substate->reply.errmsg = NULL;
+ }
+
+ client_send_control(substate->req, &substate->header, &substate->reply);
+ talloc_free(substate);
+}
+
+static void control_set_recmode(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct tevent_req *subreq;
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct set_recmode_state *substate;
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+
+ if (request->rdata.data.recmode == CTDB_RECOVERY_NORMAL) {
+ reply.status = -1;
+ reply.errmsg = "Client cannot set recmode to NORMAL";
+ goto fail;
+ }
+
+ substate = talloc_zero(ctdb, struct set_recmode_state);
+ if (substate == NULL) {
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ goto fail;
+ }
+
+ substate->req = req;
+ substate->ctdb = ctdb;
+ substate->header = *header;
+ substate->reply.rdata.opcode = request->opcode;
+
+ subreq = recover_send(substate, state->ev, state->ctdb);
+ if (subreq == NULL) {
+ talloc_free(substate);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, set_recmode_callback, substate);
+
+ ctdb->vnn_map->recmode = CTDB_RECOVERY_ACTIVE;
+ return;
+
+fail:
+ client_send_control(req, header, &reply);
+
+}
+
+static int srvid_register_state_destructor(struct srvid_register_state *rstate)
+{
+ DLIST_REMOVE(rstate->ctdb->rstate, rstate);
+ return 0;
+}
+
+static void control_register_srvid(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct srvid_register_state *rstate;
+
+ reply.rdata.opcode = request->opcode;
+
+ rstate = talloc_zero(ctdb, struct srvid_register_state);
+ if (rstate == NULL) {
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ goto fail;
+ }
+ rstate->ctdb = ctdb;
+ rstate->srvid = request->srvid;
+
+ talloc_set_destructor(rstate, srvid_register_state_destructor);
+
+ DLIST_ADD_END(ctdb->rstate, rstate);
+
+ DEBUG(DEBUG_INFO, ("Register srvid 0x%"PRIx64"\n", rstate->srvid));
+
+ reply.status = 0;
+ reply.errmsg = NULL;
+
+fail:
+ client_send_control(req, header, &reply);
+}
+
+static void control_deregister_srvid(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct srvid_register_state *rstate = NULL;
+
+ reply.rdata.opcode = request->opcode;
+
+ for (rstate = ctdb->rstate; rstate != NULL; rstate = rstate->next) {
+ if (rstate->srvid == request->srvid) {
+ break;
+ }
+ }
+
+ if (rstate == NULL) {
+ reply.status = -1;
+ reply.errmsg = "srvid not registered";
+ goto fail;
+ }
+
+ DEBUG(DEBUG_INFO, ("Deregister srvid 0x%"PRIx64"\n", rstate->srvid));
+ talloc_free(rstate);
+
+ reply.status = 0;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ TALLOC_FREE(rstate);
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_pid(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = getpid();
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_recmaster(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = ctdb->node_map->recmaster;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_pnn(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = header->destnode;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_shutdown(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *hdr,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+
+ state->status = 99;
+}
+
+static void control_uptime(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct ctdb_uptime *uptime;;
+
+ reply.rdata.opcode = request->opcode;
+
+ uptime = talloc_zero(mem_ctx, struct ctdb_uptime);
+ if (uptime == NULL) {
+ goto fail;
+ }
+
+ uptime->current_time = tevent_timeval_current();
+ uptime->ctdbd_start_time = ctdb->start_time;
+ uptime->last_recovery_started = ctdb->recovery_start_time;
+ uptime->last_recovery_finished = ctdb->recovery_end_time;
+
+ reply.rdata.data.uptime = uptime;
+ reply.status = 0;
+ reply.errmsg = NULL;
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ client_send_control(req, header, &reply);
+}
+
+static void control_reload_nodes_file(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct ctdb_node_map *nodemap;
+ struct node_map *node_map = ctdb->node_map;
+ int i;
+
+ reply.rdata.opcode = request->opcode;
+
+ nodemap = read_nodes_file(mem_ctx, header->destnode);
+ if (nodemap == NULL) {
+ goto fail;
+ }
+
+ for (i=0; inum; i++) {
+ struct node *node;
+
+ if (i < node_map->num_nodes &&
+ ctdb_sock_addr_same(&nodemap->node[i].addr,
+ &node_map->node[i].addr)) {
+ continue;
+ }
+
+ if (nodemap->node[i].flags & NODE_FLAGS_DELETED) {
+ node = &node_map->node[i];
+
+ node->flags |= NODE_FLAGS_DELETED;
+ parse_ip("0.0.0.0", NULL, 0, &node->addr);
+
+ continue;
+ }
+
+ if (i < node_map->num_nodes &&
+ node_map->node[i].flags & NODE_FLAGS_DELETED) {
+ node = &node_map->node[i];
+
+ node->flags &= ~NODE_FLAGS_DELETED;
+ node->addr = nodemap->node[i].addr;
+
+ continue;
+ }
+
+ node_map->node = talloc_realloc(node_map, node_map->node,
+ struct node,
+ node_map->num_nodes+1);
+ if (node_map->node == NULL) {
+ goto fail;
+ }
+ node = &node_map->node[node_map->num_nodes];
+
+ node->addr = nodemap->node[i].addr;
+ node->pnn = nodemap->node[i].pnn;
+ node->flags = 0;
+ node->capabilities = CTDB_CAP_DEFAULT;
+ node->recovery_disabled = false;
+ node->recovery_substate = NULL;
+
+ node_map->num_nodes += 1;
+ }
+
+ talloc_free(nodemap);
+
+ reply.status = 0;
+ reply.errmsg = NULL;
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_capabilities(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct node *node;
+ uint32_t caps = 0;
+
+ reply.rdata.opcode = request->opcode;
+
+ node = &ctdb->node_map->node[header->destnode];
+ caps = node->capabilities;
+
+ if (node->flags & NODE_FLAGS_FAKE_TIMEOUT) {
+ /* Don't send reply */
+ return;
+ }
+
+ reply.rdata.data.caps = caps;
+ reply.status = 0;
+ reply.errmsg = NULL;
+
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_nodemap(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct ctdb_node_map *nodemap;
+ struct node *node;
+ int i;
+
+ reply.rdata.opcode = request->opcode;
+
+ nodemap = talloc_zero(mem_ctx, struct ctdb_node_map);
+ if (nodemap == NULL) {
+ goto fail;
+ }
+
+ nodemap->num = ctdb->node_map->num_nodes;
+ nodemap->node = talloc_array(nodemap, struct ctdb_node_and_flags,
+ nodemap->num);
+ if (nodemap->node == NULL) {
+ goto fail;
+ }
+
+ for (i=0; inum; i++) {
+ node = &ctdb->node_map->node[i];
+ nodemap->node[i] = (struct ctdb_node_and_flags) {
+ .pnn = node->pnn,
+ .flags = node->flags,
+ .addr = node->addr,
+ };
+ }
+
+ reply.rdata.data.nodemap = nodemap;
+ reply.status = 0;
+ reply.errmsg = NULL;
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_ifaces(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_reply_control reply;
+ struct ctdb_iface_list *iface_list;
+ struct interface *iface;
+ int i;
+
+ reply.rdata.opcode = request->opcode;
+
+ iface_list = talloc_zero(mem_ctx, struct ctdb_iface_list);
+ if (iface_list == NULL) {
+ goto fail;
+ }
+
+ iface_list->num = ctdb->iface_map->num;
+ iface_list->iface = talloc_array(iface_list, struct ctdb_iface,
+ iface_list->num);
+ if (iface_list->iface == NULL) {
+ goto fail;
+ }
+
+ for (i=0; inum; i++) {
+ iface = &ctdb->iface_map->iface[i];
+ iface_list->iface[i] = (struct ctdb_iface) {
+ .link_state = iface->link_up,
+ .references = iface->references,
+ };
+ strncpy(iface_list->iface[i].name, iface->name,
+ CTDB_IFACE_SIZE+2);
+ }
+
+ reply.rdata.data.iface_list = iface_list;
+ reply.status = 0;
+ reply.errmsg = NULL;
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ reply.status = -1;
+ reply.errmsg = "Memory error";
+ client_send_control(req, header, &reply);
+}
+
+static void control_get_nodes_file(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct ctdb_reply_control reply;
+ struct ctdb_node_map *nodemap;
+
+ reply.rdata.opcode = request->opcode;
+
+ nodemap = read_nodes_file(mem_ctx, header->destnode);
+ if (nodemap == NULL) {
+ goto fail;
+ }
+
+ reply.rdata.data.nodemap = nodemap;
+ reply.status = 0;
+ reply.errmsg = NULL;
+ client_send_control(req, header, &reply);
+ return;
+
+fail:
+ reply.status = -1;
+ reply.errmsg = "Failed to read nodes file";
+ client_send_control(req, header, &reply);
+}
+
+static void control_error(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_control *request)
+{
+ struct ctdb_reply_control reply;
+
+ reply.rdata.opcode = request->opcode;
+ reply.status = -1;
+ reply.errmsg = "Not implemented";
+
+ client_send_control(req, header, &reply);
+}
+
+/*
+ * Handling protocol - messages
+ */
+
+struct disable_recoveries_state {
+ struct node *node;
+};
+
+static void disable_recoveries_callback(struct tevent_req *subreq)
+{
+ struct disable_recoveries_state *substate = tevent_req_callback_data(
+ subreq, struct disable_recoveries_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n"));
+ }
+
+ substate->node->recovery_disabled = false;
+ TALLOC_FREE(substate->node->recovery_substate);
+}
+
+static void message_disable_recoveries(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ctdb_req_header *header,
+ struct ctdb_req_message *request)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct tevent_req *subreq;
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct disable_recoveries_state *substate;
+ struct ctdb_disable_message *disable = request->data.disable;
+ struct ctdb_req_message_data reply;
+ struct node *node;
+ int ret = -1;
+ TDB_DATA data;
+
+ node = &ctdb->node_map->node[header->destnode];
+
+ if (disable->timeout == 0) {
+ TALLOC_FREE(node->recovery_substate);
+ node->recovery_disabled = false;
+ DEBUG(DEBUG_INFO, ("Enabled recoveries on node %u\n",
+ header->destnode));
+ goto done;
+ }
+
+ substate = talloc_zero(ctdb->node_map,
+ struct disable_recoveries_state);
+ if (substate == NULL) {
+ goto fail;
+ }
+
+ substate->node = node;
+
+ subreq = tevent_wakeup_send(substate, state->ev,
+ tevent_timeval_current_ofs(
+ disable->timeout, 0));
+ if (subreq == NULL) {
+ talloc_free(substate);
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, disable_recoveries_callback, substate);
+
+ DEBUG(DEBUG_INFO, ("Disabled recoveries for %d seconds on node %u\n",
+ disable->timeout, header->destnode));
+ node->recovery_substate = substate;
+ node->recovery_disabled = true;
+
+done:
+ ret = header->destnode;
+
+fail:
+ reply.srvid = disable->srvid;
+ data.dptr = (uint8_t *)&ret;
+ data.dsize = sizeof(int);
+ reply.data = data;
+
+ client_send_message(req, header, &reply);
+}
+
+/*
+ * Handle a single client
+ */
+
+static void client_read_handler(uint8_t *buf, size_t buflen,
+ void *private_data);
+static void client_dead_handler(void *private_data);
+static void client_process_packet(struct tevent_req *req,
+ uint8_t *buf, size_t buflen);
+static void client_process_message(struct tevent_req *req,
+ uint8_t *buf, size_t buflen);
+static void client_process_control(struct tevent_req *req,
+ uint8_t *buf, size_t buflen);
+static void client_reply_done(struct tevent_req *subreq);
+
+static struct tevent_req *client_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd, struct ctdbd_context *ctdb,
+ int pnn)
+{
+ struct tevent_req *req;
+ struct client_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct client_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+ state->ctdb = ctdb;
+ state->pnn = pnn;
+
+ ret = comm_setup(state, ev, fd, client_read_handler, req,
+ client_dead_handler, req, &state->comm);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(DEBUG_INFO, ("New client fd=%d\n", fd));
+
+ return req;
+}
+
+static void client_read_handler(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ struct ctdb_req_header header;
+ int ret, i;
+
+ ret = ctdb_req_header_pull(buf, buflen, &header);
+ if (ret != 0) {
+ return;
+ }
+
+ if (buflen != header.length) {
+ return;
+ }
+
+ ret = ctdb_req_header_verify(&header, 0);
+ if (ret != 0) {
+ return;
+ }
+
+ header_fix_pnn(&header, ctdb);
+
+ if (header.destnode == CTDB_BROADCAST_ALL) {
+ for (i=0; inode_map->num_nodes; i++) {
+ header.destnode = i;
+
+ ctdb_req_header_push(&header, buf);
+ client_process_packet(req, buf, buflen);
+ }
+ return;
+ }
+
+ if (header.destnode == CTDB_BROADCAST_CONNECTED) {
+ for (i=0; inode_map->num_nodes; i++) {
+ if (ctdb->node_map->node[i].flags &
+ NODE_FLAGS_DISCONNECTED) {
+ continue;
+ }
+
+ header.destnode = i;
+
+ ctdb_req_header_push(&header, buf);
+ client_process_packet(req, buf, buflen);
+ }
+ return;
+ }
+
+ if (header.destnode > ctdb->node_map->num_nodes) {
+ fprintf(stderr, "Invalid destination pnn 0x%x\n",
+ header.destnode);
+ return;
+ }
+
+
+ if (ctdb->node_map->node[header.destnode].flags & NODE_FLAGS_DISCONNECTED) {
+ fprintf(stderr, "Packet for disconnected node pnn %u\n",
+ header.destnode);
+ return;
+ }
+
+ ctdb_req_header_push(&header, buf);
+ client_process_packet(req, buf, buflen);
+}
+
+static void client_dead_handler(void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+
+ tevent_req_done(req);
+}
+
+static void client_process_packet(struct tevent_req *req,
+ uint8_t *buf, size_t buflen)
+{
+ struct ctdb_req_header header;
+ int ret;
+
+ ret = ctdb_req_header_pull(buf, buflen, &header);
+ if (ret != 0) {
+ return;
+ }
+
+ switch (header.operation) {
+ case CTDB_REQ_MESSAGE:
+ client_process_message(req, buf, buflen);
+ break;
+
+ case CTDB_REQ_CONTROL:
+ client_process_control(req, buf, buflen);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void client_process_message(struct tevent_req *req,
+ uint8_t *buf, size_t buflen)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ TALLOC_CTX *mem_ctx;
+ struct ctdb_req_header header;
+ struct ctdb_req_message request;
+ uint64_t srvid;
+ int ret;
+
+ mem_ctx = talloc_new(state);
+ if (tevent_req_nomem(mem_ctx, req)) {
+ return;
+ }
+
+ ret = ctdb_req_message_pull(buf, buflen, &header, mem_ctx, &request);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ header_fix_pnn(&header, ctdb);
+
+ srvid = request.srvid;
+ DEBUG(DEBUG_INFO, ("request srvid = 0x%"PRIx64"\n", srvid));
+
+ if (srvid == CTDB_SRVID_DISABLE_RECOVERIES) {
+ message_disable_recoveries(mem_ctx, req, &header, &request);
+ }
+
+ /* check srvid */
+ talloc_free(mem_ctx);
+}
+
+static void client_process_control(struct tevent_req *req,
+ uint8_t *buf, size_t buflen)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ TALLOC_CTX *mem_ctx;
+ struct ctdb_req_header header;
+ struct ctdb_req_control request;
+ int ret;
+
+ mem_ctx = talloc_new(state);
+ if (tevent_req_nomem(mem_ctx, req)) {
+ return;
+ }
+
+ ret = ctdb_req_control_pull(buf, buflen, &header, mem_ctx, &request);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ header_fix_pnn(&header, ctdb);
+
+ DEBUG(DEBUG_INFO, ("request opcode = %u\n", request.opcode));
+
+ switch (request.opcode) {
+ case CTDB_CONTROL_PROCESS_EXISTS:
+ control_process_exists(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_PING:
+ control_ping(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GETVNNMAP:
+ control_getvnnmap(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_RECMODE:
+ control_get_recmode(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_SET_RECMODE:
+ control_set_recmode(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_REGISTER_SRVID:
+ control_register_srvid(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_SRVID:
+ control_deregister_srvid(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_PID:
+ control_get_pid(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_RECMASTER:
+ control_get_recmaster(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_PNN:
+ control_get_pnn(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_SHUTDOWN:
+ control_shutdown(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_UPTIME:
+ control_uptime(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_RELOAD_NODES_FILE:
+ control_reload_nodes_file(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_CAPABILITIES:
+ control_get_capabilities(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_NODEMAP:
+ control_get_nodemap(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_IFACES:
+ control_get_ifaces(mem_ctx, req, &header, &request);
+ break;
+
+ case CTDB_CONTROL_GET_NODES_FILE:
+ control_get_nodes_file(mem_ctx, req, &header, &request);
+ break;
+
+ default:
+ if (! (request.flags & CTDB_CTRL_FLAG_NOREPLY)) {
+ control_error(mem_ctx, req, &header, &request);
+ }
+ break;
+ }
+
+ talloc_free(mem_ctx);
+}
+
+static int client_recv(struct tevent_req *req, int *perr)
+{
+ struct client_state *state = tevent_req_data(
+ req, struct client_state);
+ int err;
+
+ DEBUG(DEBUG_INFO, ("Client done fd=%d\n", state->fd));
+ close(state->fd);
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ return -1;
+ }
+
+ return state->status;
+}
+
+/*
+ * Fake CTDB server
+ */
+
+struct server_state {
+ struct tevent_context *ev;
+ struct ctdbd_context *ctdb;
+ int fd;
+};
+
+static void server_new_client(struct tevent_req *subreq);
+static void server_client_done(struct tevent_req *subreq);
+
+static struct tevent_req *server_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_context *ctdb,
+ int fd)
+{
+ struct tevent_req *req, *subreq;
+ struct server_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct server_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->ctdb = ctdb;
+ state->fd = fd;
+
+ subreq = accept_send(state, ev, fd);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, server_new_client, req);
+
+ return req;
+}
+
+static void server_new_client(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct server_state *state = tevent_req_data(
+ req, struct server_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ int client_fd, ret;
+
+ client_fd = accept_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (client_fd == -1) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = client_send(state, state->ev, client_fd,
+ ctdb, ctdb->node_map->pnn);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, server_client_done, req);
+
+ ctdb->num_clients += 1;
+
+ subreq = accept_send(state, state->ev, state->fd);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, server_new_client, req);
+}
+
+static void server_client_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct server_state *state = tevent_req_data(
+ req, struct server_state);
+ struct ctdbd_context *ctdb = state->ctdb;
+ int ret, status;
+
+ status = client_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (status < 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ctdb->num_clients -= 1;
+
+ if (status == 99) {
+ /* Special status, to shutdown server */
+ DEBUG(DEBUG_INFO, ("Shutting down server\n"));
+ tevent_req_done(req);
+ }
+}
+
+static bool server_recv(struct tevent_req *req, int *perr)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Main functions
+ */
+
+static int socket_init(const char *sockpath)
+{
+ struct sockaddr_un addr;
+ int ret, fd;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, sockpath);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ fprintf(stderr, "socket failed - %s\n", sockpath);
+ return -1;
+ }
+
+ ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret != 0) {
+ fprintf(stderr, "bind failed - %s\n", sockpath);
+ goto fail;
+ }
+
+ ret = listen(fd, 10);
+ if (ret != 0) {
+ fprintf(stderr, "listen failed\n");
+ goto fail;
+ }
+
+ DEBUG(DEBUG_INFO, ("Socket init done\n"));
+
+ return fd;
+
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+ return -1;
+}
+
+static struct options {
+ const char *sockpath;
+ const char *pidfile;
+ const char *debuglevel;
+} options;
+
+static struct poptOption cmdline_options[] = {
+ { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0,
+ "Unix domain socket path", "filename" },
+ { "pidfile", 'p', POPT_ARG_STRING, &options.pidfile, 0,
+ "pid file", "filename" } ,
+ { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0,
+ "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } ,
+};
+
+static void cleanup(void)
+{
+ unlink(options.sockpath);
+ unlink(options.pidfile);
+}
+
+static void signal_handler(int sig)
+{
+ cleanup();
+ exit(0);
+}
+
+static void start_server(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdbd_context *ctdb, int fd, int pfd)
+{
+ struct tevent_req *req;
+ int ret = 0;
+ ssize_t len;
+
+ atexit(cleanup);
+ signal(SIGTERM, signal_handler);
+
+ req = server_send(mem_ctx, ev, ctdb, fd);
+ if (req == NULL) {
+ fprintf(stderr, "Memory error\n");
+ }
+
+ len = write(pfd, &ret, sizeof(ret));
+ if (len != sizeof(ret)) {
+ fprintf(stderr, "Failed to send message to parent\n");
+ exit(1);
+ }
+ close(pfd);
+
+ tevent_req_poll(req, ev);
+
+ server_recv(req, &ret);
+ if (ret != 0) {
+ exit(1);
+ }
+}
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct ctdbd_context *ctdb;
+ struct tevent_context *ev;
+ enum debug_level debug_level;
+ poptContext pc;
+ int opt, fd, ret, pfd[2];
+ ssize_t len;
+ pid_t pid;
+ FILE *fp;
+
+ pc = poptGetContext(argv[0], argc, argv, cmdline_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0));
+ exit(1);
+ }
+
+ if (options.sockpath == NULL) {
+ fprintf(stderr, "Please specify socket path\n");
+ poptPrintHelp(pc, stdout, 0);
+ exit(1);
+ }
+
+ if (options.pidfile == NULL) {
+ fprintf(stderr, "Please specify pid file\n");
+ poptPrintHelp(pc, stdout, 0);
+ exit(1);
+ }
+
+ if (options.debuglevel == NULL) {
+ DEBUGLEVEL = debug_level_to_int(DEBUG_ERR);
+ } else {
+ if (debug_level_parse(options.debuglevel, &debug_level)) {
+ DEBUGLEVEL = debug_level_to_int(debug_level);
+ } else {
+ fprintf(stderr, "Invalid debug level\n");
+ poptPrintHelp(pc, stdout, 0);
+ exit(1);
+ }
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory error\n");
+ exit(1);
+ }
+
+ ctdb = ctdbd_setup(mem_ctx);
+ if (ctdb == NULL) {
+ exit(1);
+ }
+
+ if (! ctdbd_verify(ctdb)) {
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory error\n");
+ exit(1);
+ }
+
+ fd = socket_init(options.sockpath);
+ if (fd == -1) {
+ exit(1);
+ }
+
+ ret = pipe(pfd);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to create pipe\n");
+ cleanup();
+ exit(1);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ fprintf(stderr, "Failed to fork\n");
+ cleanup();
+ exit(1);
+ }
+
+ if (pid == 0) {
+ /* Child */
+ close(pfd[0]);
+ start_server(mem_ctx, ev, ctdb, fd, pfd[1]);
+ exit(1);
+ }
+
+ /* Parent */
+ close(pfd[1]);
+
+ len = read(pfd[0], &ret, sizeof(ret));
+ close(pfd[0]);
+ if (len != sizeof(ret)) {
+ fprintf(stderr, "len = %zi\n", len);
+ fprintf(stderr, "Failed to get message from child\n");
+ kill(pid, SIGTERM);
+ exit(1);
+ }
+
+ fp = fopen(options.pidfile, "w");
+ if (fp == NULL) {
+ fprintf(stderr, "Failed to open pid file %s\n",
+ options.pidfile);
+ kill(pid, SIGTERM);
+ exit(1);
+ }
+ fprintf(fp, "%d\n", pid);
+ fclose(fp);
+
+ return 0;
+}
diff --git a/ctdb/wscript b/ctdb/wscript
index 4df216cfd1c..3492a6a4a4b 100755
--- a/ctdb/wscript
+++ b/ctdb/wscript
@@ -712,6 +712,12 @@ def build(bld):
includes='include',
install_path='${CTDB_TEST_LIBDIR}')
+ bld.SAMBA_BINARY('fake_ctdbd',
+ source='tests/src/fake_ctdbd.c',
+ deps='''ctdb-util ctdb-protocol ctdb-system
+ samba-util tevent-unix-util popt''',
+ install_path='${CTDB_TEST_LIBDIR}')
+
if bld.env.HAVE_INFINIBAND:
bld.SAMBA_BINARY('ibwrapper_test',
source='ib/ibwrapper_test.c',