diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /ctdb/tests/src/fake_ctdbd.c | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ctdb/tests/src/fake_ctdbd.c')
-rw-r--r-- | ctdb/tests/src/fake_ctdbd.c | 4781 |
1 files changed, 4781 insertions, 0 deletions
diff --git a/ctdb/tests/src/fake_ctdbd.c b/ctdb/tests/src/fake_ctdbd.c new file mode 100644 index 0000000..a04ad85 --- /dev/null +++ b/ctdb/tests/src/fake_ctdbd.c @@ -0,0 +1,4781 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/time.h" +#include "system/filesys.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> +#include <tdb.h> + +#include "lib/util/dlinklist.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/async_req/async_sock.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" +#include "protocol/protocol_private.h" + +#include "common/comm.h" +#include "common/logging.h" +#include "common/tunable.h" +#include "common/srvid.h" +#include "common/system.h" + +#include "ipalloc_read_known_ips.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 database { + struct database *prev, *next; + const char *name; + const char *path; + struct tdb_context *tdb; + uint32_t id; + uint8_t flags; + uint64_t seq_num; +}; + +struct database_map { + struct database *db; + const char *dbdir; +}; + +struct fake_control_failure { + struct fake_control_failure *prev, *next; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; +}; + +struct ctdb_client { + struct ctdb_client *prev, *next; + struct ctdbd_context *ctdb; + pid_t pid; + void *state; +}; + +struct ctdbd_context { + struct node_map *node_map; + struct interface_map *iface_map; + struct vnn_map *vnn_map; + struct database_map *db_map; + struct srvid_context *srv; + int num_clients; + struct timeval start_time; + struct timeval recovery_start_time; + struct timeval recovery_end_time; + bool takeover_disabled; + int log_level; + enum ctdb_runstate runstate; + struct ctdb_tunable_list tun_list; + char *reclock; + struct ctdb_public_ip_list *known_ips; + struct fake_control_failure *control_failures; + struct ctdb_client *client_list; +}; + +/* + * 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: + * <PNN> <FLAGS> [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; + int ret; + + 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; + } + ret = ctdb_sock_addr_from_string(tok, &saddr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&saddr, CTDB_PORT); + 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]; + + ret = ctdb_sock_addr_from_string(ip, &node->addr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&node->addr, CTDB_PORT); + node->pnn = pnn; + node->flags = flags; + node->capabilities = capabilities; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + if (node_map->num_nodes == 0) { + goto fail; + } + + 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; + int ret; + + ret = ctdb_sock_addr_from_string(nstr, &addr, false); + if (ret != 0) { + fprintf(stderr, "Invalid IP address %s\n", nstr); + return false; + } + ctdb_sock_addr_set_port(&addr, CTDB_PORT); + + 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 nodes_list[PATH_MAX]; + const char *ctdb_base; + int num; + + ctdb_base = getenv("CTDB_BASE"); + if (ctdb_base == NULL) { + D_ERR("CTDB_BASE is not set\n"); + return NULL; + } + + /* read optional node-specific nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), + "%s/nodes.%d", ctdb_base, pnn); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + /* Fake a load failure for an empty nodemap */ + if (nodemap->num == 0) { + talloc_free(nodemap); + + D_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; + } + + return nodemap; + } + + /* read normal nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), "%s/nodes", ctdb_base); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + return nodemap; + } + + DBG_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; +} + +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; + } + + if (iface_map->num == 0) { + goto fail; + } + + 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: + * <GENERATION> + * <LMASTER0> + * <LMASTER1> + * ... + */ + +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; + } + + if (vnn_map->size == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing vnnmap done\n")); + return true; + +fail: + fprintf(stderr, "Parsing vnnmap failed\n"); + return false; +} + +static bool reclock_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->reclock = talloc_strdup(ctdb, line); + if (ctdb->reclock == NULL) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + DEBUG(DEBUG_INFO, ("Parsing reclock done\n")); + return true; + +fail: + fprintf(stderr, "Parsing reclock failed\n"); + return false; +} + +static struct database_map *dbmap_init(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct database_map *db_map; + + db_map = talloc_zero(mem_ctx, struct database_map); + if (db_map == NULL) { + return NULL; + } + + db_map->dbdir = talloc_strdup(db_map, dbdir); + if (db_map->dbdir == NULL) { + talloc_free(db_map); + return NULL; + } + + return db_map; +} + +/* Read a database map from stdin. Each line looks like: + * <ID> <NAME> [FLAGS] [SEQ_NUM] + * EOF or a blank line terminates input. + * + * By default, flags and seq_num are 0 + */ + +static bool dbmap_parse(struct database_map *db_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t id; + uint8_t flags = 0; + uint32_t seq_num = 0; + char *tok, *t; + char *name; + struct database *db; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get ID */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing ID\n", line); + continue; + } + id = (uint32_t)strtoul(tok, NULL, 0); + + /* Get NAME */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing NAME\n", line); + continue; + } + name = talloc_strdup(db_map, tok); + if (name == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "PERSISTENT") == 0) { + flags |= CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(tok, "STICKY") == 0) { + flags |= CTDB_DB_FLAGS_STICKY; + } else if (strcmp(tok, "READONLY") == 0) { + flags |= CTDB_DB_FLAGS_READONLY; + } else if (strcmp(tok, "REPLICATED") == 0) { + flags |= CTDB_DB_FLAGS_REPLICATED; + } else if (tok[0] >= '0'&& tok[0] <= '9') { + uint8_t nv = CTDB_DB_FLAGS_PERSISTENT | + CTDB_DB_FLAGS_REPLICATED; + + if ((flags & nv) == 0) { + fprintf(stderr, + "seq_num for volatile db\n"); + goto fail; + } + seq_num = (uint64_t)strtoull(tok, NULL, 0); + } + + tok = strtok(NULL, " \t"); + } + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + goto fail; + } + + db->id = id; + db->name = talloc_steal(db, name); + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + talloc_free(db); + goto fail; + } + db->flags = flags; + db->seq_num = seq_num; + + DLIST_ADD_END(db_map->db, db); + } + + if (db_map->db == NULL) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing dbmap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing dbmap failed\n")); + return false; + +} + +static struct database *database_find(struct database_map *db_map, + uint32_t db_id) +{ + struct database *db; + + for (db = db_map->db; db != NULL; db = db->next) { + if (db->id == db_id) { + return db; + } + } + + return NULL; +} + +static int database_count(struct database_map *db_map) +{ + struct database *db; + int count = 0; + + for (db = db_map->db; db != NULL; db = db->next) { + count += 1; + } + + return count; +} + +static int database_flags(uint8_t db_flags) +{ + int tdb_flags = 0; + + if (db_flags & CTDB_DB_FLAGS_PERSISTENT) { + tdb_flags = TDB_DEFAULT; + } else { + /* volatile and replicated use the same flags */ + tdb_flags = TDB_NOSYNC | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH; + } + + tdb_flags |= TDB_DISALLOW_NESTING; + + return tdb_flags; +} + +static struct database *database_new(struct database_map *db_map, + const char *name, uint8_t flags) +{ + struct database *db; + TDB_DATA key; + int tdb_flags; + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + return NULL; + } + + db->name = talloc_strdup(db, name); + if (db->name == NULL) { + goto fail; + } + + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + goto fail; + } + + key.dsize = strlen(db->name) + 1; + key.dptr = discard_const(db->name); + + db->id = tdb_jenkins_hash(&key); + db->flags = flags; + + tdb_flags = database_flags(flags); + + db->tdb = tdb_open(db->path, 8192, tdb_flags, O_CREAT|O_RDWR, 0644); + if (db->tdb == NULL) { + DBG_ERR("tdb_open\n"); + goto fail; + } + + DLIST_ADD_END(db_map->db, db); + return db; + +fail: + DBG_ERR("Memory error\n"); + talloc_free(db); + return NULL; + +} + +static int ltdb_store(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, TDB_DATA data) +{ + int ret; + bool db_volatile = true; + bool keep = false; + + if (db->tdb == NULL) { + return EINVAL; + } + + if ((db->flags & CTDB_DB_FLAGS_PERSISTENT) || + (db->flags & CTDB_DB_FLAGS_REPLICATED)) { + db_volatile = false; + } + + if (data.dsize > 0) { + keep = true; + } else { + if (db_volatile && header->rsn == 0) { + keep = true; + } + } + + if (keep) { + TDB_DATA rec[2]; + + rec[0].dsize = ctdb_ltdb_header_len(header); + rec[0].dptr = (uint8_t *)header; + + rec[1].dsize = data.dsize; + rec[1].dptr = data.dptr; + + ret = tdb_storev(db->tdb, key, rec, 2, TDB_REPLACE); + } else { + if (header->rsn > 0) { + ret = tdb_delete(db->tdb, key); + } else { + ret = 0; + } + } + + return ret; +} + +static int ltdb_fetch(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, + TALLOC_CTX *mem_ctx, TDB_DATA *data) +{ + TDB_DATA rec; + size_t np; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + rec = tdb_fetch(db->tdb, key); + ret = ctdb_ltdb_header_pull(rec.dptr, rec.dsize, header, &np); + if (ret != 0) { + if (rec.dptr != NULL) { + free(rec.dptr); + } + + *header = (struct ctdb_ltdb_header) { + .rsn = 0, + .dmaster = 0, + .flags = 0, + }; + + ret = ltdb_store(db, key, header, tdb_null); + if (ret != 0) { + return ret; + } + + *data = tdb_null; + return 0; + } + + data->dsize = rec.dsize - ctdb_ltdb_header_len(header); + data->dptr = talloc_memdup(mem_ctx, + rec.dptr + ctdb_ltdb_header_len(header), + data->dsize); + + free(rec.dptr); + + if (data->dptr == NULL) { + return ENOMEM; + } + + return 0; +} + +static int database_seqnum(struct database *db, uint64_t *seqnum) +{ + const char *keyname = CTDB_DB_SEQNUM_KEY; + TDB_DATA key, data; + struct ctdb_ltdb_header header; + size_t np; + int ret; + + if (db->tdb == NULL) { + *seqnum = db->seq_num; + return 0; + } + + key.dptr = discard_const(keyname); + key.dsize = strlen(keyname) + 1; + + ret = ltdb_fetch(db, key, &header, db, &data); + if (ret != 0) { + return ret; + } + + if (data.dsize == 0) { + *seqnum = 0; + return 0; + } + + ret = ctdb_uint64_pull(data.dptr, data.dsize, seqnum, &np); + talloc_free(data.dptr); + if (ret != 0) { + *seqnum = 0; + } + + return ret; +} + +static int ltdb_transaction_update(uint32_t reqid, + struct ctdb_ltdb_header *no_header, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct database *db = (struct database *)private_data; + TALLOC_CTX *tmp_ctx = talloc_new(db); + struct ctdb_ltdb_header header = { 0 }, oldheader; + TDB_DATA olddata; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = ctdb_ltdb_header_extract(&data, &header); + if (ret != 0) { + return ret; + } + + ret = ltdb_fetch(db, key, &oldheader, tmp_ctx, &olddata); + if (ret != 0) { + return ret; + } + + if (olddata.dsize > 0) { + if (oldheader.rsn > header.rsn || + (oldheader.rsn == header.rsn && + olddata.dsize != data.dsize)) { + return -1; + } + } + + talloc_free(tmp_ctx); + + ret = ltdb_store(db, key, &header, data); + return ret; +} + +static int ltdb_transaction(struct database *db, + struct ctdb_rec_buffer *recbuf) +{ + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = tdb_transaction_start(db->tdb); + if (ret == -1) { + return ret; + } + + ret = ctdb_rec_buffer_traverse(recbuf, ltdb_transaction_update, db); + if (ret != 0) { + tdb_transaction_cancel(db->tdb); + } + + ret = tdb_transaction_commit(db->tdb); + return ret; +} + +static bool public_ips_parse(struct ctdbd_context *ctdb, + uint32_t numnodes) +{ + bool status; + + if (numnodes == 0) { + D_ERR("Must initialise nodemap before public IPs\n"); + return false; + } + + ctdb->known_ips = ipalloc_read_known_ips(ctdb, numnodes, false); + + status = (ctdb->known_ips != NULL && ctdb->known_ips->num != 0); + + if (status) { + D_INFO("Parsing public IPs done\n"); + } else { + D_INFO("Parsing public IPs failed\n"); + } + + return status; +} + +/* Read information about controls to fail. Format is: + * <opcode> <pnn> {ERROR|TIMEOUT} <comment> + */ +static bool control_failures_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + char *tok, *t; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; + struct fake_control_failure *failure = NULL; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get opcode */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing opcode\n", line); + continue; + } + opcode = (enum ctdb_controls)strtoul(tok, NULL, 0); + + /* Get PNN */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get error */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing errno\n", line); + continue; + } + error = talloc_strdup(ctdb, tok); + if (error == NULL) { + goto fail; + } + if (strcmp(error, "ERROR") != 0 && + strcmp(error, "TIMEOUT") != 0) { + D_ERR("bad line (%s) " + "- error must be \"ERROR\" or \"TIMEOUT\"\n", + line); + goto fail; + } + + /* Get comment */ + tok = strtok(NULL, "\n"); /* rest of line */ + if (tok == NULL) { + D_ERR("bad line (%s) - missing comment\n", line); + continue; + } + comment = talloc_strdup(ctdb, tok); + if (comment == NULL) { + goto fail; + } + + failure = talloc_zero(ctdb, struct fake_control_failure); + if (failure == NULL) { + goto fail; + } + + failure->opcode = opcode; + failure->pnn = pnn; + failure->error = error; + failure->comment = comment; + + DLIST_ADD(ctdb->control_failures, failure); + } + + if (ctdb->control_failures == NULL) { + goto fail; + } + + D_INFO("Parsing fake control failures done\n"); + return true; + +fail: + D_INFO("Parsing fake control failures failed\n"); + return false; +} + +static bool runstate_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->runstate = ctdb_runstate_from_string(line); + if (ctdb->runstate == CTDB_RUNSTATE_UNKNOWN) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + D_INFO("Parsing runstate done\n"); + return true; + +fail: + D_ERR("Parsing runstate failed\n"); + return false; +} + +/* + * Manage clients + */ + +static int ctdb_client_destructor(struct ctdb_client *client) +{ + DLIST_REMOVE(client->ctdb->client_list, client); + return 0; +} + +static int client_add(struct ctdbd_context *ctdb, pid_t client_pid, + void *client_state) +{ + struct ctdb_client *client; + + client = talloc_zero(client_state, struct ctdb_client); + if (client == NULL) { + return ENOMEM; + } + + client->ctdb = ctdb; + client->pid = client_pid; + client->state = client_state; + + DLIST_ADD(ctdb->client_list, client); + talloc_set_destructor(client, ctdb_client_destructor); + return 0; +} + +static void *client_find(struct ctdbd_context *ctdb, pid_t client_pid) +{ + struct ctdb_client *client; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == client_pid) { + return client->state; + } + } + + return NULL; +} + +/* + * 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, + const char *dbdir) +{ + struct ctdbd_context *ctdb; + char line[1024]; + bool status; + int ret; + + 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; + } + + ctdb->db_map = dbmap_init(ctdb, dbdir); + if (ctdb->db_map == NULL) { + goto fail; + } + + ret = srvid_init(ctdb, &ctdb->srv); + if (ret != 0) { + goto fail; + } + + ctdb->runstate = CTDB_RUNSTATE_RUNNING; + + 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 if (strcmp(line, "DBMAP") == 0) { + status = dbmap_parse(ctdb->db_map); + } else if (strcmp(line, "PUBLICIPS") == 0) { + status = public_ips_parse(ctdb, + ctdb->node_map->num_nodes); + } else if (strcmp(line, "RECLOCK") == 0) { + status = reclock_parse(ctdb); + } else if (strcmp(line, "CONTROLFAILS") == 0) { + status = control_failures_parse(ctdb); + } else if (strcmp(line, "RUNSTATE") == 0) { + status = runstate_parse(ctdb); + } 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(); + + ctdb->log_level = DEBUG_ERR; + + ctdb_tunable_set_defaults(&ctdb->tun_list); + + return ctdb; + +fail: + TALLOC_FREE(ctdb); + return NULL; +} + +static bool ctdbd_verify(struct ctdbd_context *ctdb) +{ + struct node *node; + unsigned int i; + + if (ctdb->node_map->num_nodes == 0) { + return true; + } + + /* Make sure all the nodes are in order */ + for (i=0; i<ctdb->node_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; + unsigned int i; + + recovery_disabled = false; + for (i=0; i<ctdb->node_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_call( + 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_CALL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +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; + pid_t pid; + struct comm_context *comm; + struct srvid_register_state *rstate; + int status; +}; + +/* + * Send replies to call, controls and messages + */ + +static void client_reply_done(struct tevent_req *subreq); + +static void client_send_call(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_call *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_call(header, ctdb); + + datalen = ctdb_reply_call_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_call_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + 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_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 client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct client_state *cstate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + cstate = client_find(ctdb, request->rdata.data.pid); + if (cstate == NULL) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else { + 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_getdbpath(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_path = + talloc_strdup(mem_ctx, db->path); + if (reply.rdata.data.db_path == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + 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_debug(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.rdata.data.loglevel = (uint32_t)ctdb->log_level; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_set_debug(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; + + ctdb->log_level = (int)request->rdata.data.loglevel; + + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_dbmap(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_dbid_map *dbmap; + struct database *db; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + dbmap = talloc_zero(mem_ctx, struct ctdb_dbid_map); + if (dbmap == NULL) { + goto fail; + } + + dbmap->num = database_count(ctdb->db_map); + dbmap->dbs = talloc_zero_array(dbmap, struct ctdb_dbid, dbmap->num); + if (dbmap->dbs == NULL) { + goto fail; + } + + db = ctdb->db_map->db; + for (i = 0; i < dbmap->num; i++) { + dbmap->dbs[i] = (struct ctdb_dbid) { + .db_id = db->id, + .flags = db->flags, + }; + + db = db->next; + } + + reply.rdata.data.dbmap = dbmap; + 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_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 void control_db_attach(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 database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, 0); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void srvid_handler_done(struct tevent_req *subreq); + +static void srvid_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + struct client_state *state = talloc_get_type_abort( + private_data, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header request_header; + struct ctdb_req_message_data message; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + request_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = state->pnn, + .srcnode = ctdb->node_map->recmaster, + .reqid = 0, + }; + + message = (struct ctdb_req_message_data) { + .srvid = srvid, + .data = data, + }; + + datalen = ctdb_req_message_data_len(&request_header, &message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + return; + } + + ret = ctdb_req_message_data_push(&request_header, + &message, + buf, + &buflen); + if (ret != 0) { + talloc_free(buf); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (subreq == NULL) { + talloc_free(buf); + return; + } + tevent_req_set_callback(subreq, srvid_handler_done, state); + + talloc_steal(subreq, buf); +} + +static void srvid_handler_done(struct tevent_req *subreq) +{ + struct client_state *state = tevent_req_callback_data( + subreq, struct client_state); + int ret; + bool ok; + + ok = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + DEBUG(DEBUG_ERR, + ("Failed to dispatch message to client pid=%u, ret=%d\n", + state->pid, + ret)); + } +} + +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; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_register(ctdb->srv, state, request->srvid, + srvid_handler, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Register srvid 0x%"PRIx64"\n", request->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; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_deregister(ctdb->srv, request->srvid, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "srvid not registered"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Deregister srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_get_dbname(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_name = talloc_strdup(mem_ctx, db->name); + if (reply.rdata.data.db_name == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + 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_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_set_tunable(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; + bool ret, obsolete; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_set_value(&ctdb->tun_list, + request->rdata.data.tunable->name, + request->rdata.data.tunable->value, + &obsolete); + if (! ret) { + reply.status = -1; + } else if (obsolete) { + reply.status = 1; + } else { + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_get_tunable(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; + uint32_t value; + bool ret; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_get_value(&ctdb->tun_list, + request->rdata.data.tun_var, &value); + if (! ret) { + reply.status = -1; + } else { + reply.rdata.data.tun_value = value; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_list_tunables(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_var_list *var_list; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + var_list = ctdb_tunable_names(mem_ctx); + if (var_list == NULL) { + reply.status = -1; + } else { + reply.rdata.data.tun_var_list = var_list; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_modify_flags(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_node_flag_change *change = request->rdata.data.flag_change; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if ((change->old_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) || + (change->new_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO, + ("MODIFY_FLAGS control not for PERMANENTLY_DISABLED\n")); + reply.status = EINVAL; + reply.errmsg = "Failed to MODIFY_FLAGS"; + client_send_control(req, header, &reply); + return; + } + + /* There's all sorts of broadcast weirdness here. Only change + * the specified node, not the destination node of the + * control. */ + node = &ctdb->node_map->node[change->pnn]; + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO,("Disabling node %d\n", header->destnode)); + node->flags |= NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0) { + DEBUG(DEBUG_INFO,("Enabling node %d\n", header->destnode)); + node->flags &= ~NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + DEBUG(DEBUG_INFO, ("Flags unchanged for node %d\n", header->destnode)); + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_all_tunables(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.rdata.data.tun_list = &ctdb->tun_list; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_db_attach_persistent(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 database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_PERSISTENT); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +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; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + for (i=0; i<nodemap->num; 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) { + int ret; + + node = &node_map->node[i]; + + node->flags |= NODE_FLAGS_DELETED; + ret = ctdb_sock_addr_from_string("0.0.0.0", &node->addr, + false); + if (ret != 0) { + /* Can't happen, but Coverity... */ + goto fail; + } + + 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_release_ip(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_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn != header->destnode) { + if (header->destnode == ip->pnn) { + D_ERR("error: RELEASE_IP %s - to TAKE_IP node %d\n", + ctdb_sock_addr_to_string(mem_ctx, + &ip->addr, false), + ip->pnn); + reply.status = -1; + reply.errmsg = "RELEASE_IP to TAKE_IP node"; + client_send_control(req, header, &reply); + return; + } + + D_INFO("RELEASE_IP %s - to node %d - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } else { + D_NOTICE("RELEASE_IP %s - to node %d\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_takeover_ip(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_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn == header->destnode) { + D_INFO("TAKEOVER_IP %s - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + } else { + D_NOTICE("TAKEOVER_IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_public_ips(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_public_ip_list *ips = NULL; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + /* No IPs defined so create a dummy empty struct and ship it */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + goto ok; + } + + ips = &ctdb->known_ips[header->destnode]; + + if (request->flags & CTDB_PUBLIC_IP_FLAGS_ONLY_AVAILABLE) { + /* If runstate is not RUNNING or a node is then return + * no available IPs. Don't worry about interface + * states here - we're not faking down to that level. + */ + uint32_t flags = ctdb->node_map->node[header->destnode].flags; + if (ctdb->runstate != CTDB_RUNSTATE_RUNNING || + ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0)) { + /* No available IPs: return dummy empty struct */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + } + +ok: + reply.rdata.data.pubip_list = ips; + reply.status = 0; + reply.errmsg = NULL; + +done: + 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; + unsigned 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; i<nodemap->num; 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_reclock_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; + + reply.rdata.opcode = request->opcode; + + if (ctdb->reclock != NULL) { + reply.rdata.data.reclock_file = + talloc_strdup(mem_ctx, ctdb->reclock); + if (reply.rdata.data.reclock_file == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } else { + reply.rdata.data.reclock_file = NULL; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_stop_node(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; + + DEBUG(DEBUG_INFO, ("Stopping node\n")); + ctdb->node_map->node[header->destnode].flags |= NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_continue_node(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; + + DEBUG(DEBUG_INFO, ("Continue node\n")); + ctdb->node_map->node[header->destnode].flags &= ~NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void set_ban_state_callback(struct tevent_req *subreq) +{ + struct node *node = tevent_req_callback_data( + subreq, struct node); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + node->flags &= ~NODE_FLAGS_BANNED; +} + +static void control_set_ban_state(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 ctdb_ban_state *ban = request->rdata.data.ban_state; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if (ban->pnn != header->destnode) { + DEBUG(DEBUG_INFO, + ("SET_BAN_STATE control for PNN %d rejected\n", + ban->pnn)); + reply.status = EINVAL; + goto fail; + } + + node = &ctdb->node_map->node[header->destnode]; + + if (ban->time == 0) { + DEBUG(DEBUG_INFO,("Unbanning this node\n")); + node->flags &= ~NODE_FLAGS_BANNED; + goto done; + } + + subreq = tevent_wakeup_send(ctdb->node_map, state->ev, + tevent_timeval_current_ofs( + ban->time, 0)); + if (subreq == NULL) { + reply.status = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, set_ban_state_callback, node); + + DEBUG(DEBUG_INFO, ("Banning this node for %d seconds\n", ban->time)); + node->flags |= NODE_FLAGS_BANNED; + ctdb->vnn_map->generation = INVALID_GENERATION; + +done: + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; + +fail: + reply.errmsg = "Failed to ban node"; +} + +static void control_trans3_commit(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 database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.recbuf->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + if (! (db->flags & + (CTDB_DB_FLAGS_PERSISTENT|CTDB_DB_FLAGS_REPLICATED))) { + reply.status = -1; + reply.errmsg = "Transactions on volatile database"; + client_send_control(req, header, &reply); + return; + } + + ret = ltdb_transaction(db, request->rdata.data.recbuf); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Transaction failed"; + client_send_control(req, header, &reply); + return; + } + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_db_seqnum(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 database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + uint64_t seqnum; + + ret = database_seqnum(db, &seqnum); + if (ret == 0) { + reply.rdata.data.seqnum = seqnum; + reply.status = 0; + reply.errmsg = NULL; + } else { + reply.status = ret; + reply.errmsg = "Failed to get seqnum"; + } + } + + client_send_control(req, header, &reply); +} + +static void control_db_get_health(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.reason = NULL; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static struct ctdb_iface_list *get_ctdb_iface_list(TALLOC_CTX *mem_ctx, + struct ctdbd_context *ctdb) +{ + struct ctdb_iface_list *iface_list; + struct interface *iface; + unsigned int i; + + iface_list = talloc_zero(mem_ctx, struct ctdb_iface_list); + if (iface_list == NULL) { + goto done; + } + + 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) { + TALLOC_FREE(iface_list); + goto done; + } + + for (i=0; i<iface_list->num; i++) { + iface = &ctdb->iface_map->iface[i]; + iface_list->iface[i] = (struct ctdb_iface) { + .link_state = iface->link_up, + .references = iface->references, + }; + strlcpy(iface_list->iface[i].name, iface->name, + sizeof(iface_list->iface[i].name)); + } + +done: + return iface_list; +} + +static void control_get_public_ip_info(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; + ctdb_sock_addr *addr = request->rdata.data.addr; + struct ctdb_public_ip_list *known = NULL; + struct ctdb_public_ip_info *info = NULL; + unsigned i; + + reply.rdata.opcode = request->opcode; + + info = talloc_zero(mem_ctx, struct ctdb_public_ip_info); + if (info == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.rdata.data.ipinfo = info; + + if (ctdb->known_ips != NULL) { + known = &ctdb->known_ips[header->destnode]; + } else { + /* No IPs defined so create a dummy empty struct and + * fall through. The given IP won't be matched + * below... + */ + known = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (known == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + + for (i = 0; i < known->num; i++) { + if (ctdb_sock_addr_same_ip(&known->ip[i].addr, + addr)) { + break; + } + } + + if (i == known->num) { + D_ERR("GET_PUBLIC_IP_INFO: not known public IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, addr, false)); + reply.status = -1; + reply.errmsg = "Unknown address"; + goto done; + } + + info->ip = known->ip[i]; + + /* The fake PUBLICIPS stanza and resulting known_ips data + * don't know anything about interfaces, so completely fake + * this. + */ + info->active_idx = 0; + + info->ifaces = get_ctdb_iface_list(mem_ctx, ctdb); + if (info->ifaces == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + 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; + + reply.rdata.opcode = request->opcode; + + iface_list = get_ctdb_iface_list(mem_ctx, ctdb); + if (iface_list == NULL) { + goto fail; + } + + 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_set_iface_link_state(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 *in_iface; + struct interface *iface = NULL; + bool link_up = false; + int i; + + reply.rdata.opcode = request->opcode; + + in_iface = request->rdata.data.iface; + + if (in_iface->name[CTDB_IFACE_SIZE] != '\0') { + reply.errmsg = "interface name not terminated"; + goto fail; + } + + switch (in_iface->link_state) { + case 0: + link_up = false; + break; + + case 1: + link_up = true; + break; + + default: + reply.errmsg = "invalid link state"; + goto fail; + } + + if (in_iface->references != 0) { + reply.errmsg = "references should be 0"; + goto fail; + } + + for (i=0; i<ctdb->iface_map->num; i++) { + if (strcmp(ctdb->iface_map->iface[i].name, + in_iface->name) == 0) { + iface = &ctdb->iface_map->iface[i]; + break; + } + } + + if (iface == NULL) { + reply.errmsg = "interface not found"; + goto fail; + } + + iface->link_up = link_up; + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + client_send_control(req, header, &reply); +} + +static void control_set_db_readonly(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set READONLY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_READONLY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +struct traverse_start_ext_state { + struct tevent_req *req; + struct ctdb_req_header *header; + uint32_t reqid; + uint64_t srvid; + bool withemptyrecords; + int status; +}; + +static int traverse_start_ext_handler(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct traverse_start_ext_state *state = + (struct traverse_start_ext_state *)private_data; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + size_t np; + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + return 0; + } + + if ((data.dsize == sizeof(struct ctdb_ltdb_header)) && + (!state->withemptyrecords)) { + return 0; + } + + rec = (struct ctdb_rec_data) { + .reqid = state->reqid, + .header = NULL, + .key = key, + .data = data, + }; + + message.srvid = state->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + message.data.dptr = talloc_size(state->req, message.data.dsize); + if (message.data.dptr == NULL) { + state->status = ENOMEM; + return 1; + } + + ctdb_rec_data_push(&rec, message.data.dptr, &np); + client_send_message(state->req, state->header, &message); + + talloc_free(message.data.dptr); + + return 0; +} + +static void control_traverse_start_ext(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 database *db; + struct ctdb_traverse_start_ext *ext; + struct traverse_start_ext_state t_state; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + uint8_t buffer[32]; + size_t np; + int ret; + + reply.rdata.opcode = request->opcode; + + ext = request->rdata.data.traverse_start_ext; + + db = database_find(ctdb->db_map, ext->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + t_state = (struct traverse_start_ext_state) { + .req = req, + .header = header, + .reqid = ext->reqid, + .srvid = ext->srvid, + .withemptyrecords = ext->withemptyrecords, + }; + + ret = tdb_traverse_read(db->tdb, traverse_start_ext_handler, &t_state); + DEBUG(DEBUG_INFO, ("traversed %d records\n", ret)); + if (t_state.status != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); + } + + reply.status = 0; + client_send_control(req, header, &reply); + + rec = (struct ctdb_rec_data) { + .reqid = ext->reqid, + .header = NULL, + .key = tdb_null, + .data = tdb_null, + }; + + message.srvid = ext->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + ctdb_rec_data_push(&rec, buffer, &np); + message.data.dptr = buffer; + client_send_message(req, header, &message); +} + +static void control_set_db_sticky(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set STICKY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_STICKY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_ipreallocated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + /* Always succeed */ + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_runstate(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.rdata.data.runstate = ctdb->runstate; + reply.status = 0; + reply.errmsg = NULL; + + 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_db_open_flags(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 database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.tdb_flags = database_flags(db->flags); + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_db_attach_replicated(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 database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_REPLICATED); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_check_pid_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_client *client; + struct client_state *cstate; + struct ctdb_reply_control reply; + bool pid_found, srvid_found; + int ret; + + reply.rdata.opcode = request->opcode; + + pid_found = false; + srvid_found = false; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == request->rdata.data.pid_srvid->pid) { + pid_found = true; + cstate = (struct client_state *)client->state; + ret = srvid_exists(ctdb->srv, + request->rdata.data.pid_srvid->srvid, + cstate); + if (ret == 0) { + srvid_found = true; + ret = kill(cstate->pid, 0); + if (ret != 0) { + reply.status = ret; + reply.errmsg = strerror(errno); + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + } + } + + if (! pid_found) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else if (! srvid_found) { + reply.status = -1; + reply.errmsg = "No client for PID and SRVID"; + } + + client_send_control(req, header, &reply); +} + +static void control_disable_node(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; + + DEBUG(DEBUG_INFO, ("Disabling node\n")); + ctdb->node_map->node[header->destnode].flags |= + NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_enable_node(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; + + DEBUG(DEBUG_INFO, ("Enable node\n")); + ctdb->node_map->node[header->destnode].flags &= + ~NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static bool fake_control_failure(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 fake_control_failure *f = NULL; + + D_DEBUG("Checking fake control failure for control %u on node %u\n", + request->opcode, header->destnode); + for (f = ctdb->control_failures; f != NULL; f = f->next) { + if (f->opcode == request->opcode && + (f->pnn == header->destnode || + f->pnn == CTDB_UNKNOWN_PNN)) { + + reply.rdata.opcode = request->opcode; + if (strcmp(f->error, "TIMEOUT") == 0) { + /* Causes no reply */ + D_ERR("Control %u fake timeout on node %u\n", + request->opcode, header->destnode); + return true; + } else if (strcmp(f->error, "ERROR") == 0) { + D_ERR("Control %u fake error on node %u\n", + request->opcode, header->destnode); + reply.status = -1; + reply.errmsg = f->comment; + client_send_control(req, header, &reply); + return true; + } + } + } + + return false; +} + +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; + + D_DEBUG("Control %u not implemented\n", request->opcode); + + 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); +} + +static void message_takeover_run(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 ctdbd_context *ctdb = state->ctdb; + struct ctdb_srvid_message *srvid = request->data.msg; + struct ctdb_req_message_data reply; + int ret = -1; + TDB_DATA data; + + if (header->destnode != ctdb->node_map->recmaster) { + /* No reply! Only recmaster replies... */ + return; + } + + DEBUG(DEBUG_INFO, ("IP takover run on node %u\n", + header->destnode)); + ret = header->destnode; + + reply.srvid = srvid->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_call(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; + + (void) ctdb_get_peer_pid(fd, &state->pid); + + 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); + } + + ret = client_add(ctdb, state->pid, state); + 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; + size_t np; + unsigned int i; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + 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; i<ctdb->node_map->num_nodes; i++) { + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode == CTDB_BROADCAST_CONNECTED) { + for (i=0; i<ctdb->node_map->num_nodes; i++) { + if (ctdb->node_map->node[i].flags & + NODE_FLAGS_DISCONNECTED) { + continue; + } + + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + 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, &np); + 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; + size_t np; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + switch (header.operation) { + case CTDB_REQ_CALL: + client_process_call(req, buf, buflen); + break; + + 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_call(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_call request; + struct ctdb_reply_call reply; + struct database *db; + struct ctdb_ltdb_header hdr; + TDB_DATA data; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_call_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); + + if (header.destnode >= ctdb->node_map->num_nodes) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("call db_id = %u\n", request.db_id)); + + db = database_find(ctdb->db_map, request.db_id); + if (db == NULL) { + goto fail; + } + + ret = ltdb_fetch(db, request.key, &hdr, mem_ctx, &data); + if (ret != 0) { + goto fail; + } + + /* Fake migration */ + if (hdr.dmaster != ctdb->node_map->pnn) { + hdr.dmaster = ctdb->node_map->pnn; + + ret = ltdb_store(db, request.key, &hdr, data); + if (ret != 0) { + goto fail; + } + } + + talloc_free(mem_ctx); + + reply.status = 0; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); + return; + +fail: + talloc_free(mem_ctx); + reply.status = -1; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); +} + +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); + + if (header.destnode >= ctdb->node_map->num_nodes) { + /* Many messages are not replied to, so just behave as + * though this message was not received */ + fprintf(stderr, "Invalid node %d\n", header.destnode); + talloc_free(mem_ctx); + return; + } + + 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); + } else if (srvid == CTDB_SRVID_TAKEOVER_RUN) { + message_takeover_run(mem_ctx, req, &header, &request); + } else { + D_DEBUG("Message id 0x%"PRIx64" not implemented\n", srvid); + } + + /* 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); + + if (header.destnode >= ctdb->node_map->num_nodes) { + struct ctdb_reply_control reply; + + reply.rdata.opcode = request.opcode; + reply.errmsg = "Invalid node"; + reply.status = -1; + client_send_control(req, &header, &reply); + return; + } + + DEBUG(DEBUG_INFO, ("request opcode = %u, reqid = %u\n", + request.opcode, header.reqid)); + + if (fake_control_failure(mem_ctx, req, &header, &request)) { + goto done; + } + + 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_GETDBPATH: + control_getdbpath(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETVNNMAP: + control_getvnnmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DEBUG: + control_get_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DEBUG: + control_set_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBMAP: + control_get_dbmap(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_DB_ATTACH: + control_db_attach(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_DBNAME: + control_get_dbname(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PID: + control_get_pid(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_SET_TUNABLE: + control_set_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_TUNABLE: + control_get_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + control_list_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + control_modify_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + control_get_all_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + control_db_attach_persistent(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_RELEASE_IP: + control_release_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + control_takeover_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + control_get_public_ips(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODEMAP: + control_get_nodemap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + control_get_reclock_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_STOP_NODE: + control_stop_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CONTINUE_NODE: + control_continue_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + control_set_ban_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + control_trans3_commit(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + control_get_db_seqnum(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + control_db_get_health(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + control_get_public_ip_info(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_IFACES: + control_get_ifaces(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + control_set_iface_link_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + control_set_db_readonly(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + control_traverse_start_ext(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + control_set_db_sticky(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_IPREALLOCATED: + control_ipreallocated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RUNSTATE: + control_get_runstate(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + control_get_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + control_db_open_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + control_db_attach_replicated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + control_check_pid_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DISABLE_NODE: + control_disable_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_ENABLE_NODE: + control_enable_node(mem_ctx, req, &header, &request); + break; + + default: + if (! (request.flags & CTDB_CTRL_FLAG_NOREPLY)) { + control_error(mem_ctx, req, &header, &request); + } + break; + } + +done: + 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; + struct tevent_timer *leader_broadcast_te; + int fd; +}; + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +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; + + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(0, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } + + 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_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct server_state *state = talloc_get_type_abort( + private_data, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + uint32_t leader = ctdb->node_map->recmaster; + TDB_DATA data; + int ret; + + if (leader == CTDB_UNKNOWN_PNN) { + goto done; + } + + data.dptr = (uint8_t *)&leader; + data.dsize = sizeof(leader); + + ret = srvid_dispatch(ctdb->srv, CTDB_SRVID_LEADER, 0, data); + if (ret != 0) { + DBG_WARNING("Failed to send leader broadcast, ret=%d\n", ret); + } + +done: + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(1, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } +} + +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; + int ret = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &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 = 0; + int 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; + size_t len; + int ret, fd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + fprintf(stderr, "path too long: %s\n", sockpath); + return -1; + } + + 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 *dbdir; + const char *sockpath; + const char *pidfile; + const char *debuglevel; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "dbdir", 'D', POPT_ARG_STRING, &options.dbdir, 0, + "Database directory", "directory" }, + { "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" } , + POPT_TABLEEND +}; + +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"); + exit(1); + } + + 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; + 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.dbdir == NULL) { + fprintf(stderr, "Please specify database directory\n"); + poptPrintHelp(pc, stdout, 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); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + ret = logging_init(mem_ctx, "file:", options.debuglevel, "fake-ctdbd"); + if (ret != 0) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + ctdb = ctdbd_setup(mem_ctx, options.dbdir); + 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; +} |