summaryrefslogtreecommitdiffstats
path: root/ctdb/tests/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /ctdb/tests/src
parentInitial commit. (diff)
downloadsamba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz
samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ctdb/tests/src')
-rw-r--r--ctdb/tests/src/cluster_mutex_test.c844
-rw-r--r--ctdb/tests/src/cluster_wait.c346
-rw-r--r--ctdb/tests/src/cluster_wait.h30
-rw-r--r--ctdb/tests/src/cmdline_test.c480
-rw-r--r--ctdb/tests/src/comm_client_test.c217
-rw-r--r--ctdb/tests/src/comm_server_test.c292
-rw-r--r--ctdb/tests/src/comm_test.c501
-rw-r--r--ctdb/tests/src/conf_test.c513
-rw-r--r--ctdb/tests/src/ctdb_io_test.c356
-rw-r--r--ctdb/tests/src/ctdb_packet_parse.c136
-rw-r--r--ctdb/tests/src/ctdb_takeover_tests.c278
-rw-r--r--ctdb/tests/src/db_hash_test.c138
-rw-r--r--ctdb/tests/src/db_test_tool.c792
-rw-r--r--ctdb/tests/src/dummy_client.c163
-rw-r--r--ctdb/tests/src/errcode.c189
-rw-r--r--ctdb/tests/src/event_script_test.c120
-rw-r--r--ctdb/tests/src/fake_ctdbd.c4781
-rw-r--r--ctdb/tests/src/fetch_loop.c288
-rw-r--r--ctdb/tests/src/fetch_loop_key.c217
-rw-r--r--ctdb/tests/src/fetch_readonly.c166
-rw-r--r--ctdb/tests/src/fetch_readonly_loop.c272
-rw-r--r--ctdb/tests/src/fetch_ring.c398
-rw-r--r--ctdb/tests/src/g_lock_loop.c270
-rw-r--r--ctdb/tests/src/hash_count_test.c132
-rw-r--r--ctdb/tests/src/ipalloc_read_known_ips.c179
-rw-r--r--ctdb/tests/src/ipalloc_read_known_ips.h32
-rw-r--r--ctdb/tests/src/line_test.c102
-rw-r--r--ctdb/tests/src/lock_tdb.c60
-rw-r--r--ctdb/tests/src/message_ring.c369
-rw-r--r--ctdb/tests/src/pidfile_test.c242
-rw-r--r--ctdb/tests/src/pkt_read_test.c249
-rw-r--r--ctdb/tests/src/pkt_write_test.c359
-rw-r--r--ctdb/tests/src/porting_tests.c262
-rw-r--r--ctdb/tests/src/protocol_basic_test.c106
-rw-r--r--ctdb/tests/src/protocol_common.c1260
-rw-r--r--ctdb/tests/src/protocol_common.h238
-rw-r--r--ctdb/tests/src/protocol_common_basic.c305
-rw-r--r--ctdb/tests/src/protocol_common_basic.h175
-rw-r--r--ctdb/tests/src/protocol_common_ctdb.c1934
-rw-r--r--ctdb/tests/src/protocol_common_ctdb.h101
-rw-r--r--ctdb/tests/src/protocol_ctdb_compat_test.c1270
-rw-r--r--ctdb/tests/src/protocol_ctdb_test.c365
-rw-r--r--ctdb/tests/src/protocol_types_compat_test.c2371
-rw-r--r--ctdb/tests/src/protocol_types_test.c194
-rw-r--r--ctdb/tests/src/protocol_util_test.c417
-rw-r--r--ctdb/tests/src/rb_test.c336
-rw-r--r--ctdb/tests/src/reqid_test.c89
-rw-r--r--ctdb/tests/src/run_event_test.c251
-rw-r--r--ctdb/tests/src/run_proc_test.c111
-rw-r--r--ctdb/tests/src/sigcode.c120
-rw-r--r--ctdb/tests/src/sock_daemon_test.c1980
-rw-r--r--ctdb/tests/src/sock_io_test.c283
-rw-r--r--ctdb/tests/src/srvid_test.c105
-rw-r--r--ctdb/tests/src/system_socket_test.c266
-rw-r--r--ctdb/tests/src/test_backtrace.c37
-rw-r--r--ctdb/tests/src/test_backtrace.h25
-rw-r--r--ctdb/tests/src/test_mutex_raw.c434
-rw-r--r--ctdb/tests/src/test_options.c245
-rw-r--r--ctdb/tests/src/test_options.h44
-rw-r--r--ctdb/tests/src/tmon_ping_test.c381
-rw-r--r--ctdb/tests/src/tmon_test.c406
-rw-r--r--ctdb/tests/src/transaction_loop.c419
-rw-r--r--ctdb/tests/src/tunable_test.c71
-rw-r--r--ctdb/tests/src/tunnel_cmd.c199
-rw-r--r--ctdb/tests/src/tunnel_test.c480
-rw-r--r--ctdb/tests/src/update_record.c236
-rw-r--r--ctdb/tests/src/update_record_persistent.c218
67 files changed, 29245 insertions, 0 deletions
diff --git a/ctdb/tests/src/cluster_mutex_test.c b/ctdb/tests/src/cluster_mutex_test.c
new file mode 100644
index 0000000..2576163
--- /dev/null
+++ b/ctdb/tests/src/cluster_mutex_test.c
@@ -0,0 +1,844 @@
+/*
+ CTDB cluster mutex test
+
+ Copyright (C) Martin Schwenke 2019
+
+ 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/wait.h"
+
+#include <assert.h>
+
+#include <talloc.h>
+#include <tevent.h>
+
+#include "lib/util/util.h"
+#include "lib/util/smb_strtox.h"
+
+#include "tests/src/test_backtrace.h"
+
+/*
+ * ctdb_cluster_mutex.c is included below. This requires a few hacks...
+ */
+
+/* Avoid inclusion of ctdb_private.h */
+#define _CTDB_PRIVATE_H
+
+/* Fake ctdb_context */
+struct ctdb_context {
+ struct tevent_context *ev;
+};
+
+/*
+ * ctdb_fork() and ctdb_kill() are used in ctdb_cluster_mutex.c for
+ * safer tracking of PIDs. Fake them here to avoid dragging in the
+ * world.
+ */
+
+static pid_t ctdb_fork(struct ctdb_context *ctdb)
+{
+ return fork();
+}
+
+static int ctdb_kill(struct ctdb_context *ctdb, pid_t pid, int signum)
+{
+ /*
+ * Tests need to wait for the child to exit to ensure that the
+ * lock really has been released. The PID is only accessible
+ * in ctdb_cluster_mutex.c, so make a best attempt to ensure
+ * that the child process is waited for after it is killed.
+ * Avoid waiting if the process is already gone.
+ */
+ int ret;
+
+ if (signum == 0) {
+ return kill(pid, signum);
+ }
+
+ ret = kill(pid, signum);
+ waitpid(pid, NULL, 0);
+
+ return ret;
+}
+
+#include "server/ctdb_cluster_mutex.c"
+
+/*
+ * Mutex testing support
+ */
+
+struct mutex_handle {
+ bool done;
+ bool locked;
+ struct ctdb_cluster_mutex_handle *h;
+};
+
+struct do_lock_context {
+ struct mutex_handle *mh;
+ struct ctdb_context *ctdb;
+};
+
+static void do_lock_handler(char status, double latency, void *private_data)
+{
+ struct do_lock_context *dl = talloc_get_type_abort(
+ private_data, struct do_lock_context);
+ struct mutex_handle *mh;
+
+ assert(dl->mh != NULL);
+ mh = dl->mh;
+
+ mh->locked = (status == '0') ;
+
+ /*
+ * If unsuccessful then ensure the process has exited and that
+ * the file descriptor event handler has been cancelled
+ */
+ if (! mh->locked) {
+ TALLOC_FREE(mh->h);
+ }
+
+ switch (status) {
+ case '0':
+ printf("LOCK\n");
+ break;
+
+ case '1':
+ printf("CONTENTION\n");
+ break;
+
+ case '2':
+ printf("TIMEOUT\n");
+ break;
+
+ default:
+ printf("ERROR\n");
+ }
+
+ fflush(stdout);
+ mh->done = true;
+}
+
+static void do_lock_lost_handler(void *private_data)
+{
+ struct do_lock_context *dl = talloc_get_type_abort(
+ private_data, struct do_lock_context);
+
+ printf("LOST\n");
+ fflush(stdout);
+ TALLOC_FREE(dl->mh);
+}
+
+static void do_lock_take(struct do_lock_context *dl,
+ const char *mutex_string)
+{
+ struct ctdb_cluster_mutex_handle *h;
+
+ dl->mh = talloc_zero(dl, struct mutex_handle);
+ assert(dl->mh != NULL);
+
+ h = ctdb_cluster_mutex(dl->mh,
+ dl->ctdb,
+ mutex_string,
+ 120,
+ do_lock_handler,
+ dl,
+ do_lock_lost_handler,
+ dl);
+ assert(h != NULL);
+
+ dl->mh->h = h;
+}
+
+static void do_lock_wait_done(struct do_lock_context *dl)
+{
+ assert(dl->mh != NULL);
+
+ while (! dl->mh->done) {
+ tevent_loop_once(dl->ctdb->ev);
+ }
+}
+
+static void do_lock_check(struct do_lock_context *dl)
+{
+ assert(dl->mh != NULL);
+
+ if (! dl->mh->locked) {
+ printf("NOLOCK\n");
+ fflush(stdout);
+ TALLOC_FREE(dl->mh);
+ }
+}
+
+static void do_lock(struct do_lock_context *dl,
+ const char *mutex_string)
+{
+ do_lock_take(dl, mutex_string);
+
+ do_lock_wait_done(dl);
+
+ do_lock_check(dl);
+}
+
+static void do_unlock(struct do_lock_context *dl)
+{
+ if (dl->mh == NULL) {
+ return;
+ }
+
+ if (! dl->mh->done) {
+ /*
+ * Taking of lock still in progress. Free the cluster
+ * mutex handle to release it but leave the lock
+ * handle in place to allow taking of the lock to
+ * fail.
+ */
+ printf("CANCEL\n");
+ fflush(stdout);
+ TALLOC_FREE(dl->mh->h);
+ dl->mh->done = true;
+ dl->mh->locked = false;
+ return;
+ }
+
+ printf("UNLOCK\n");
+ fflush(stdout);
+ TALLOC_FREE(dl->mh);
+}
+
+static void wait_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t,
+ void *private_data)
+{
+ bool *done = (bool *)private_data;
+
+ *done = true;
+}
+
+static void do_lock_wait_time(struct do_lock_context *dl,
+ unsigned long wait_time)
+{
+ struct tevent_timer *tt;
+ bool done = false;
+
+ tt = tevent_add_timer(dl->ctdb->ev,
+ dl,
+ tevent_timeval_current_ofs(wait_time, 0),
+ wait_handler,
+ &done);
+ assert(tt != NULL);
+
+ while (!done && dl->mh != NULL) {
+ tevent_loop_once(dl->ctdb->ev);
+ }
+}
+
+/*
+ * Testcases
+ */
+
+static void test_lock_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+}
+
+static void test_lock_lock_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl1;
+ struct do_lock_context *dl2;
+
+ dl1 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl1 != NULL);
+ dl1->ctdb = ctdb;
+
+ dl2 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl2 != NULL);
+ dl2->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl1, mutex_string);
+ assert(dl1->mh != NULL);
+
+ /* CONTENTION */
+ do_lock(dl2, mutex_string);
+ assert(dl2->mh == NULL);
+
+ /* UNLOCK */
+ do_unlock(dl1);
+ assert(dl1->mh == NULL);
+}
+
+static void test_lock_unlock_lock_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl1;
+ struct do_lock_context *dl2;
+
+ dl1 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl1 != NULL);
+ dl1->ctdb = ctdb;
+
+ dl2 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl2 != NULL);
+ dl2->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl1, mutex_string);
+ assert(dl1->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl1);
+ assert(dl1->mh == NULL);
+
+ /* LOCK */
+ do_lock(dl2, mutex_string);
+ assert(dl2->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl2);
+ assert(dl2->mh == NULL);
+}
+
+static void test_lock_cancel_check(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ do_lock_take(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /* CANCEL */
+ do_unlock(dl);
+ assert(dl->mh != NULL);
+
+ do_lock_wait_done(dl);
+
+ /* NOLOCK */
+ do_lock_check(dl);
+ assert(dl->mh == NULL);
+}
+
+static void test_lock_cancel_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ do_lock_take(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /* CANCEL */
+ do_unlock(dl);
+ assert(dl->mh != NULL);
+
+ do_lock_wait_done(dl);
+
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+}
+
+static void test_lock_wait_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /* Wait for twice as long as the PPID timeout */
+ do_lock_wait_time(dl, 2 * 5);
+ assert(dl->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+}
+
+static void fd_done_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ bool *done = (bool *)private_data;
+
+ *done = true;
+}
+
+static void test_lock_ppid_gone_lock_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string)
+{
+ struct do_lock_context *dl;
+ struct tevent_fd *fde;
+ int pipefd[2];
+ int ret;
+ pid_t pid, pid2;
+ ssize_t nread;
+ bool done;
+
+ /*
+ * Do this in the parent - debugging aborts of the child is
+ * trickier
+ */
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ ssize_t nwritten;
+
+ close(pipefd[0]);
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /*
+ * Note that we never see corresponding LOST. That
+ * would come from this process, but it is killed
+ * below.
+ */
+
+ nwritten = write(pipefd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ sleep(999);
+ exit(1);
+ }
+
+ close(pipefd[1]);
+
+ nread = read(pipefd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+ assert(ret == 0);
+
+ /*
+ * pipefd[1] is leaked into the helper, so there will be an
+ * event generated when the helper exits
+ */
+ done = false;
+ fde = tevent_add_fd(ctdb->ev,
+ ctdb,
+ pipefd[0],
+ TEVENT_FD_READ,
+ fd_done_handler,
+ &done);
+ assert(fde != NULL);
+
+ ret = kill(pid, SIGKILL);
+ assert(ret == 0);
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+
+ while (! done) {
+ tevent_loop_once(ctdb->ev);
+ }
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+}
+
+static void test_lock_file_removed_no_recheck(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string,
+ const char *lock_file)
+{
+ struct do_lock_context *dl1;
+ struct do_lock_context *dl2;
+ int ret;
+
+ dl1 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl1 != NULL);
+ dl1->ctdb = ctdb;
+
+ dl2 = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl2 != NULL);
+ dl2->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl1, mutex_string);
+ assert(dl1->mh != NULL);
+
+ ret = unlink(lock_file);
+ assert(ret == 0);
+
+ /* LOCK */
+ do_lock(dl2, mutex_string);
+ assert(dl2->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl2);
+ assert(dl2->mh == NULL);
+
+ /* UNLOCK */
+ do_unlock(dl1);
+ assert(dl1->mh == NULL);
+}
+
+static void test_lock_file_wait_recheck_unlock(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string,
+ unsigned long wait_time)
+{
+ struct do_lock_context *dl;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ do_lock_wait_time(dl, wait_time);
+ assert(dl->mh != NULL);
+
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+}
+
+static void test_lock_file_removed(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string,
+ const char *lock_file)
+{
+ struct do_lock_context *dl;
+ int ret;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ ret = unlink(lock_file);
+ assert(ret == 0);
+
+ while (dl->mh != NULL) {
+ /* LOST */
+ tevent_loop_once(ctdb->ev);
+ }
+}
+
+static void test_lock_file_changed(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string,
+ const char *lock_file)
+{
+ struct do_lock_context *dl;
+ char *t;
+ int fd;
+ int ret;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ t = talloc_asprintf(ctdb, "%s.new", lock_file);
+ assert(t != NULL);
+
+ fd = open(t, O_RDWR|O_CREAT, 0600);
+ assert(fd != -1);
+ close(fd);
+
+ ret = rename(t, lock_file);
+ assert(ret == 0);
+
+ while (dl->mh != NULL) {
+ /* LOST */
+ tevent_loop_once(ctdb->ev);
+ }
+}
+
+static void test_lock_io_timeout(TALLOC_CTX *mem_ctx,
+ struct ctdb_context *ctdb,
+ const char *mutex_string,
+ const char *lock_file,
+ unsigned long block_wait,
+ unsigned long block_time)
+{
+ struct do_lock_context *dl;
+ int pipefd[2];
+ int ret;
+ pid_t pid, pid2;
+ ssize_t nwritten;
+
+ dl = talloc_zero(mem_ctx, struct do_lock_context);
+ assert(dl != NULL);
+ dl->ctdb = ctdb;
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ static struct flock lock = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 1,
+ .l_len = 1,
+ .l_pid = 0,
+ };
+ ssize_t nread;
+ int fd;
+
+ close(pipefd[1]);
+
+ /* Only continue when the parent is ready */
+ nread = read(pipefd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+ assert(ret == 0);
+
+ sleep(block_wait);
+
+ fd = open(lock_file, O_RDWR, 0600);
+ assert(fd != -1);
+
+ ret = fcntl(fd, F_SETLKW, &lock);
+ assert(ret == 0);
+
+ sleep(block_time);
+
+ close(fd);
+
+ sleep(999);
+
+ _exit(0);
+ }
+
+ close(pipefd[0]);
+
+ /* LOCK */
+ do_lock(dl, mutex_string);
+ assert(dl->mh != NULL);
+
+ nwritten = write(pipefd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ do_lock_wait_time(dl, block_wait + block_time * 2);
+ if (dl->mh != NULL) {
+ /* UNLOCK */
+ do_unlock(dl);
+ assert(dl->mh == NULL);
+ }
+
+ ret = kill(pid, SIGKILL);
+ assert(ret == 0);
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+}
+
+/*
+ * Main
+ */
+
+static const char *prog;
+
+static void usage(void)
+{
+ fprintf(stderr, "usage: %s <test> <mutex-string> [<arg>...]\n", prog);
+ exit(1);
+}
+
+static void alarm_handler(int sig)
+{
+ abort();
+}
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct ctdb_context *ctdb;
+ const char *mutex_string;
+ const char *test;
+ struct sigaction sa = { .sa_handler = NULL, };
+ int ret;
+ const char *lock_file;
+ unsigned int wait_time;
+
+ prog = argv[0];
+
+ if (argc < 3) {
+ usage();
+ }
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ctdb = talloc_zero(mem_ctx, struct ctdb_context);
+ assert(ctdb != NULL);
+
+ ctdb->ev = tevent_context_init(ctdb);
+ assert(ctdb->ev != NULL);
+
+ /* Add a 60s timeout for the whole test */
+ sa.sa_handler = alarm_handler;
+ sigemptyset(&sa.sa_mask);
+ ret = sigaction(SIGALRM, &sa, NULL);
+ assert(ret == 0);
+ alarm(60);
+
+ test_backtrace_setup();
+
+ test = argv[1];
+ mutex_string = argv[2];
+
+ if (strcmp(test, "lock-unlock") == 0) {
+ test_lock_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-lock-unlock") == 0) {
+ test_lock_lock_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-unlock-lock-unlock") == 0) {
+ test_lock_unlock_lock_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-cancel-check") == 0) {
+ test_lock_cancel_check(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-cancel-unlock") == 0) {
+ test_lock_cancel_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-wait-unlock") == 0) {
+ test_lock_wait_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-ppid-gone-lock-unlock") == 0) {
+ test_lock_ppid_gone_lock_unlock(mem_ctx, ctdb, mutex_string);
+ } else if (strcmp(test, "lock-file-removed-no-recheck") == 0) {
+ if (argc != 4) {
+ usage();
+ }
+
+ lock_file = argv[3];
+
+ test_lock_file_removed_no_recheck(mem_ctx,
+ ctdb,
+ mutex_string,
+ lock_file);
+ } else if (strcmp(test, "lock-file-wait-recheck-unlock") == 0) {
+ if (argc != 4) {
+ usage();
+ }
+
+ wait_time = smb_strtoul(argv[3],
+ NULL,
+ 10,
+ &ret,
+ SMB_STR_STANDARD);
+ if (ret != 0) {
+ usage();
+ }
+
+ test_lock_file_wait_recheck_unlock(mem_ctx,
+ ctdb,
+ mutex_string,
+ wait_time);
+ } else if (strcmp(test, "lock-file-removed") == 0) {
+ if (argc != 4) {
+ usage();
+ }
+
+ lock_file = argv[3];
+
+ test_lock_file_removed(mem_ctx,
+ ctdb,
+ mutex_string,
+ lock_file);
+ } else if (strcmp(test, "lock-file-changed") == 0) {
+ if (argc != 4) {
+ usage();
+ }
+
+ lock_file = argv[3];
+
+ test_lock_file_changed(mem_ctx,
+ ctdb,
+ mutex_string,
+ lock_file);
+ } else if (strcmp(test, "lock-io-timeout") == 0) {
+ unsigned long block_wait;
+ unsigned long block_time;
+
+ if (argc != 6) {
+ usage();
+ }
+
+ lock_file = argv[3];
+ block_wait = (unsigned long)atol(argv[4]);
+ block_time = (unsigned long)atol(argv[5]);
+
+ test_lock_io_timeout(mem_ctx,
+ ctdb,
+ mutex_string,
+ lock_file,
+ block_wait,
+ block_time);
+ } else {
+ fprintf(stderr, "Unknown test\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/cluster_wait.c b/ctdb/tests/src/cluster_wait.c
new file mode 100644
index 0000000..d411591
--- /dev/null
+++ b/ctdb/tests/src/cluster_wait.c
@@ -0,0 +1,346 @@
+/*
+ Cluster wide synchronization
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+
+#include "tests/src/cluster_wait.h"
+
+#define MSG_ID_JOIN (CTDB_SRVID_TEST_RANGE | 0x1)
+#define MSG_ID_SYNC (CTDB_SRVID_TEST_RANGE | 0x2)
+
+/* Wait for all the clients to initialize */
+
+struct cluster_wait_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ uint32_t num_nodes;
+ bool *ready;
+ bool join_done;
+};
+
+static void cluster_wait_join_registered(struct tevent_req *subreq);
+static void cluster_wait_sync_registered(struct tevent_req *subreq);
+static void cluster_wait_join(struct tevent_req *subreq);
+static void cluster_wait_join_sent(struct tevent_req *subreq);
+static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data);
+static void cluster_wait_join_unregistered(struct tevent_req *subreq);
+static void cluster_wait_sync_sent(struct tevent_req *subreq);
+static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data);
+static void cluster_wait_sync_unregistered(struct tevent_req *subreq);
+
+struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ uint32_t num_nodes)
+{
+ struct tevent_req *req, *subreq;
+ struct cluster_wait_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct cluster_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->num_nodes = num_nodes;
+
+ state->join_done = false;
+
+ if (ctdb_client_pnn(client) == 0) {
+ state->ready = talloc_zero_array(state, bool, num_nodes);
+ if (tevent_req_nomem(state->ready, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = ctdb_client_set_message_handler_send(
+ state, ev, client, MSG_ID_JOIN,
+ cluster_wait_join_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cluster_wait_join_registered,
+ req);
+ }
+
+ subreq = ctdb_client_set_message_handler_send(
+ state, ev, client, MSG_ID_SYNC,
+ cluster_wait_sync_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cluster_wait_sync_registered, req);
+
+ /* If cluster is not synchronized within 30 seconds, time out */
+ ok = tevent_req_set_endtime(
+ req,
+ ev,
+ tevent_timeval_current_ofs(30, 0));
+ if (!ok) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cluster_wait_join_registered(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int ret;
+
+ status = ctdb_client_set_message_handler_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ printf("Waiting for cluster\n");
+ fflush(stdout);
+}
+
+static void cluster_wait_sync_registered(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ bool status;
+ int ret;
+
+ status = ctdb_client_set_message_handler_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_join, req);
+}
+
+static void cluster_wait_join(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ struct ctdb_req_message msg;
+ uint32_t pnn;
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ pnn = ctdb_client_pnn(state->client);
+
+ msg.srvid = MSG_ID_JOIN;
+ msg.data.data.dsize = sizeof(pnn);
+ msg.data.data.dptr = (uint8_t *)&pnn;
+
+ subreq = ctdb_client_message_send(state, state->ev, state->client,
+ 0, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_join_sent, req);
+}
+
+static void cluster_wait_join_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ bool status;
+ int ret;
+
+ status = ctdb_client_message_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_join, req);
+}
+
+static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ struct tevent_req *subreq;
+ uint32_t pnn;
+ uint32_t i;
+
+ if (srvid != MSG_ID_JOIN) {
+ return;
+ }
+
+ if (data.dsize != sizeof(uint32_t)) {
+ return;
+ }
+
+ pnn = *(uint32_t *)data.dptr;
+
+ if (pnn > state->num_nodes) {
+ return;
+ }
+
+ state->ready[pnn] = true;
+
+ for (i=0; i<state->num_nodes; i++) {
+ if (! state->ready[i]) {
+ return;
+ }
+ }
+
+ if (state->join_done) {
+ return;
+ }
+
+ state->join_done = true;
+ subreq = ctdb_client_remove_message_handler_send(
+ state, state->ev, state->client,
+ MSG_ID_JOIN, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_join_unregistered, req);
+}
+
+static void cluster_wait_join_unregistered(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ struct ctdb_req_message msg;
+ bool status;
+ int ret;
+
+ status = ctdb_client_remove_message_handler_recv(subreq, &ret);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ msg.srvid = MSG_ID_SYNC;
+ msg.data.data = tdb_null;
+
+ subreq = ctdb_client_message_send(state, state->ev, state->client,
+ CTDB_BROADCAST_CONNECTED, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_sync_sent, req);
+}
+
+static void cluster_wait_sync_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int ret;
+
+ status = ctdb_client_message_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct cluster_wait_state *state = tevent_req_data(
+ req, struct cluster_wait_state);
+ struct tevent_req *subreq;
+
+ if (srvid != MSG_ID_SYNC) {
+ return;
+ }
+
+ subreq = ctdb_client_remove_message_handler_send(
+ state, state->ev, state->client,
+ MSG_ID_SYNC, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cluster_wait_sync_unregistered, req);
+}
+
+static void cluster_wait_sync_unregistered(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int ret;
+
+ status = ctdb_client_remove_message_handler_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+bool cluster_wait_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;
+}
diff --git a/ctdb/tests/src/cluster_wait.h b/ctdb/tests/src/cluster_wait.h
new file mode 100644
index 0000000..e0c64cc
--- /dev/null
+++ b/ctdb/tests/src/cluster_wait.h
@@ -0,0 +1,30 @@
+/*
+ Cluster wide synchronization
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __CLUSTER_WAIT_H__
+#define __CLUSTER_WAIT_H__
+
+struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ uint32_t num_nodes);
+
+bool cluster_wait_recv(struct tevent_req *req, int *perr);
+
+#endif /* __CLUSTER_WAIT_H__ */
diff --git a/ctdb/tests/src/cmdline_test.c b/ctdb/tests/src/cmdline_test.c
new file mode 100644
index 0000000..916d820
--- /dev/null
+++ b/ctdb/tests/src/cmdline_test.c
@@ -0,0 +1,480 @@
+/*
+ Command line processing tests
+
+ Copyright (C) Amitay Isaacs 2018
+
+ 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 <popt.h>
+#include <talloc.h>
+
+#include <assert.h>
+
+#include "common/cmdline.c"
+
+static int dummy_func(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ return 0;
+}
+
+static struct poptOption dummy_options[] = {
+ POPT_TABLEEND
+};
+
+static struct cmdline_command dummy_commands[] = {
+ CMDLINE_TABLEEND
+};
+
+static void test1(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx, NULL, NULL, NULL, NULL, &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx, "test1", NULL, NULL, NULL, &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test1",
+ dummy_options,
+ NULL,
+ NULL,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ talloc_free(mem_ctx);
+}
+
+static struct cmdline_command test2_nofunc[] = {
+ { "nofunc", NULL, NULL, NULL },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_nohelp[] = {
+ { "nohelp", dummy_func, NULL, NULL },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_long[] = {
+ { "really really long command with lots of words",
+ dummy_func, "long command help",
+ "<and lots of really long long arguments>" },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_longhelp[] = {
+ { "longhelp", dummy_func,
+ "this is a really really really long help message" \
+ "with lots of words and lots of description",
+ NULL },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test2_twowords[] = {
+ { "multiple words", dummy_func, "multiple words help", NULL },
+ CMDLINE_TABLEEND
+};
+
+static void test2(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test2",
+ NULL,
+ NULL,
+ test2_nofunc,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test2",
+ NULL,
+ NULL,
+ test2_nohelp,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test2",
+ NULL,
+ NULL,
+ test2_long,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test2",
+ NULL,
+ NULL,
+ test2_longhelp,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test2",
+ NULL,
+ NULL,
+ test2_twowords,
+ &cmdline);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+struct {
+ const char *str;
+} test3_data;
+
+static struct poptOption test3_noname[] = {
+ { NULL, 'o', POPT_ARG_STRING, &test3_data.str, 0,
+ "Noname option", NULL },
+ POPT_TABLEEND
+};
+
+static struct poptOption test3_notype[] = {
+ { "debug", 'd', POPT_ARG_NONE, NULL, 0,
+ "No argument option", NULL },
+ POPT_TABLEEND
+};
+
+static struct poptOption test3_noarg[] = {
+ { "debug", 'd', POPT_ARG_STRING, NULL, 0,
+ "No argument option", NULL },
+ POPT_TABLEEND
+};
+
+static void test3(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test3",
+ test3_noname,
+ NULL,
+ dummy_commands,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test3",
+ test3_notype,
+ NULL,
+ dummy_commands,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ ret = cmdline_init(mem_ctx,
+ "test3",
+ test3_noarg,
+ NULL,
+ dummy_commands,
+ &cmdline);
+ assert(ret == EINVAL);
+
+ talloc_free(mem_ctx);
+}
+
+static int test4_count;
+static int test4_value;
+
+static struct poptOption test4_options[] = {
+ { "count", 'c', POPT_ARG_INT, &test4_count, 0,
+ "Option help of length thirty.", NULL },
+ { "value", 'v', POPT_ARG_INT, &test4_value, 0,
+ "Short description", "Value help of length 23" },
+ POPT_TABLEEND
+};
+
+static struct cmdline_command test4_commands[] = {
+ { "A really really long command", dummy_func,
+ "This is a really long help message",
+ "<a long arguments message>" },
+ { "short command", dummy_func,
+ "short msg for short command", "<short arg msg>" },
+ CMDLINE_TABLEEND
+};
+
+static void test4(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test4",
+ test4_options,
+ NULL,
+ test4_commands,
+ &cmdline);
+ assert(ret == 0);
+
+ cmdline_usage(cmdline, NULL);
+ cmdline_usage(cmdline, "short command");
+
+ talloc_free(mem_ctx);
+}
+
+static int action_func(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ if (argc != 1) {
+ return 100;
+ }
+
+ printf("%s\n", argv[0]);
+ return 200;
+}
+
+static struct cmdline_command action_commands[] = {
+ { "action one", dummy_func, "action one help", NULL },
+ { "action two", action_func, "action two help", NULL },
+ CMDLINE_TABLEEND
+};
+
+static void test5(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ const char *argv1[] = { "test5", "--help" };
+ const char *argv2[] = { "test5", "action" };
+ const char *argv3[] = { "test5", "action", "--help" };
+ const char *argv4[] = { "test5", "action", "one" };
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test5",
+ NULL,
+ "Action",
+ action_commands,
+ &cmdline);
+ assert(ret == 0);
+
+ ret = cmdline_parse(cmdline, 2, argv1, true);
+ assert(ret == EAGAIN);
+
+ ret = cmdline_parse(cmdline, 2, argv2, true);
+ assert(ret == ENOENT);
+
+ ret = cmdline_parse(cmdline, 3, argv3, true);
+ assert(ret == EAGAIN);
+
+ ret = cmdline_parse(cmdline, 3, argv4, true);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+static void test6(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ const char *argv1[] = { "action", "two" };
+ const char *argv2[] = { "action", "two", "arg1" };
+ const char *argv3[] = { "action", "two", "arg1", "arg2" };
+ int ret, result;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test6",
+ NULL,
+ NULL,
+ action_commands,
+ &cmdline);
+ assert(ret == 0);
+
+ ret = cmdline_parse(cmdline, 2, argv1, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 100);
+
+ ret = cmdline_parse(cmdline, 3, argv2, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 200);
+
+ ret = cmdline_parse(cmdline, 4, argv3, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 100);
+
+ talloc_free(mem_ctx);
+}
+
+static int test7_func(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ assert(argc == 1);
+
+ printf("%s\n", argv[0]);
+
+ return 0;
+}
+
+static struct cmdline_command test7_basic_commands[] = {
+ { "cmd1", test7_func, "command one help", NULL },
+ { "cmd2", test7_func, "command two help", NULL },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test7_advanced_commands[] = {
+ { "cmd3", test7_func, "command three help", NULL },
+ { "cmd4", test7_func, "command four help", NULL },
+ CMDLINE_TABLEEND
+};
+
+static struct cmdline_command test7_ultimate_commands[] = {
+ { "cmd5", test7_func, "command five help", NULL },
+ { "cmd6", test7_func, "command six help", NULL },
+ CMDLINE_TABLEEND
+};
+
+static void test7(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct cmdline_context *cmdline;
+ const char *argv1[] = { "cmd1", "one" };
+ const char *argv2[] = { "cmd3", "three" };
+ const char *argv3[] = { "cmd6", "six" };
+ int ret, result;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = cmdline_init(mem_ctx,
+ "test7",
+ NULL,
+ "Basic",
+ test7_basic_commands,
+ &cmdline);
+ assert(ret == 0);
+
+ ret = cmdline_add(cmdline, "Advanced", test7_advanced_commands);
+ assert(ret == 0);
+
+ ret = cmdline_add(cmdline, "Ultimate", test7_ultimate_commands);
+ assert(ret == 0);
+
+ cmdline_usage(cmdline, NULL);
+
+ printf("\n");
+
+ ret = cmdline_parse(cmdline, 2, argv1, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 0);
+
+ ret = cmdline_parse(cmdline, 2, argv2, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 0);
+
+ ret = cmdline_parse(cmdline, 2, argv3, false);
+ assert(ret == 0);
+
+ ret = cmdline_run(cmdline, NULL, &result);
+ assert(ret == 0);
+ assert(result == 0);
+
+ talloc_free(mem_ctx);
+}
+
+
+int main(int argc, const char **argv)
+{
+ int num;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage %s <testnum>\n", argv[0]);
+ exit(1);
+ }
+
+ num = atoi(argv[1]);
+
+ switch (num) {
+ case 1:
+ test1();
+ break;
+
+ case 2:
+ test2();
+ break;
+
+ case 3:
+ test3();
+ break;
+
+ case 4:
+ test4();
+ break;
+
+ case 5:
+ test5();
+ break;
+
+ case 6:
+ test6();
+ break;
+
+ case 7:
+ test7();
+ break;
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/comm_client_test.c b/ctdb/tests/src/comm_client_test.c
new file mode 100644
index 0000000..41ed5f7
--- /dev/null
+++ b/ctdb/tests/src/comm_client_test.c
@@ -0,0 +1,217 @@
+/*
+ comm tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "common/pkt_read.c"
+#include "common/pkt_write.c"
+#include "common/comm.c"
+
+
+struct writer_state {
+ struct tevent_context *ev;
+ struct comm_context *comm;
+ uint8_t *buf;
+ size_t *pkt_size;
+ size_t count, id;
+};
+
+static void writer_done(struct tevent_req *subreq);
+static void read_handler(uint8_t *buf, size_t buflen, void *private_data);
+static void dead_handler(void *private_data);
+
+static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd, size_t *pkt_size,
+ size_t count)
+{
+ struct tevent_req *req, *subreq;
+ struct writer_state *state;
+ size_t max_size = 0, buflen;
+ size_t i;
+ int ret;
+
+ for (i=0; i<count; i++) {
+ if (pkt_size[i] > max_size) {
+ max_size = pkt_size[i];
+ }
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct writer_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->pkt_size = pkt_size;
+ state->count = count;
+ state->id = 0;
+
+ ret = comm_setup(state, ev, fd, read_handler, req,
+ dead_handler, req, &state->comm);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ state->buf = talloc_array(state, uint8_t, max_size);
+ if (state->buf == NULL) {
+ talloc_free(req);
+ return NULL;
+ }
+ for (i=0; i<max_size; i++) {
+ state->buf[i] = i%256;
+ }
+
+ buflen = state->pkt_size[state->id];
+ *(uint32_t *)state->buf = buflen;
+ subreq = comm_write_send(state, state->ev, state->comm,
+ state->buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, writer_done, req);
+
+ return req;
+}
+
+static void writer_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool ret;
+ int err;
+
+ ret = comm_write_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_error(req, err);
+ return;
+ }
+}
+
+static void 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 writer_state *state = tevent_req_data(
+ req, struct writer_state);
+ struct tevent_req *subreq;
+
+ if (buflen != state->pkt_size[state->id]) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ state->id++;
+ if (state->id >= state->count) {
+ tevent_req_done(req);
+ return;
+ }
+
+ buflen = state->pkt_size[state->id];
+ *(uint32_t *)state->buf = buflen;
+ subreq = comm_write_send(state, state->ev, state->comm,
+ state->buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, writer_done, req);
+}
+
+static void dead_handler(void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+
+ tevent_req_error(req, EPIPE);
+}
+
+static void writer_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return;
+ }
+ *perr = 0;
+}
+
+static int socket_init(char *sockpath)
+{
+ struct sockaddr_un addr;
+ int fd, ret, i;
+ size_t len;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+
+ len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path));
+ assert(len < sizeof(addr.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ assert(fd != -1);
+
+ for (i=0; i<5; i++) {
+ ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (ret == 0) {
+ break;
+ }
+ sleep(1);
+ }
+ assert(ret != -1);
+
+ return fd;
+}
+
+int main(int argc, char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int fd;
+ size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192,
+ 200, 16384, 300, 32768, 400, 65536,
+ 1024*1024 };
+ int err;
+
+ if (argc != 2) {
+ printf("Usage: %s <sockpath>\n", argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ fd = socket_init(argv[1]);
+
+ req = writer_send(mem_ctx, ev, fd, pkt_size, 13);
+ assert(req != NULL);
+
+ tevent_req_poll(req, ev);
+
+ writer_recv(req, &err);
+ assert(err == 0);
+
+ exit(0);
+}
diff --git a/ctdb/tests/src/comm_server_test.c b/ctdb/tests/src/comm_server_test.c
new file mode 100644
index 0000000..86b5658
--- /dev/null
+++ b/ctdb/tests/src/comm_server_test.c
@@ -0,0 +1,292 @@
+/*
+ comm tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "lib/async_req/async_sock.h"
+
+#include "common/pkt_read.c"
+#include "common/pkt_write.c"
+#include "common/comm.c"
+
+struct echo_state {
+ struct tevent_context *ev;
+ int fd;
+ struct comm_context *comm;
+ uint8_t *data;
+};
+
+static void read_handler(uint8_t *buf, size_t buflen, void *private_data);
+static void read_failed(void *private_data);
+static void write_done(struct tevent_req *subreq);
+
+static struct tevent_req *echo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd)
+{
+ struct tevent_req *req;
+ struct echo_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct echo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+
+ ret = comm_setup(state, ev, fd, read_handler, req,
+ read_failed, req, &state->comm);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void 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 echo_state *state = tevent_req_data(
+ req, struct echo_state);
+ struct tevent_req *subreq;
+
+ state->data = talloc_memdup(state, buf, buflen);
+ if (tevent_req_nomem(state->data, req)) {
+ return;
+ }
+
+ subreq = comm_write_send(state, state->ev, state->comm,
+ state->data, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, write_done, req);
+}
+
+static void read_failed(void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+
+ tevent_req_done(req);
+}
+
+static void write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct echo_state *state = tevent_req_data(
+ req, struct echo_state);
+ bool ret;
+ int err;
+
+ TALLOC_FREE(state->data);
+
+ ret = comm_write_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_error(req, err);
+ return;
+ }
+}
+
+static bool echo_recv(struct tevent_req *req, int *perr)
+{
+ struct echo_state *state = tevent_req_data(
+ req, struct echo_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ return false;
+ }
+
+ close(state->fd);
+ return true;
+}
+
+
+struct socket_process_state {
+ struct tevent_context *ev;
+ int fd;
+ int max_clients;
+ int num_clients;
+};
+
+static void socket_process_client(struct tevent_req *subreq);
+static void socket_process_client_done(struct tevent_req *subreq);
+
+static struct tevent_req *socket_process_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd, int max_clients)
+{
+ struct tevent_req *req, *subreq;
+ struct socket_process_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct socket_process_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+ state->max_clients = max_clients;
+ state->num_clients = 0;
+
+ subreq = accept_send(state, ev, fd);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, socket_process_client, req);
+
+ return req;
+}
+
+static void socket_process_client(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct socket_process_state *state = tevent_req_data(
+ req, struct socket_process_state);
+ int client_fd;
+ int err = 0;
+
+ client_fd = accept_recv(subreq, NULL, NULL, &err);
+ TALLOC_FREE(subreq);
+
+ state->num_clients++;
+
+ if (client_fd == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ subreq = echo_send(state, state->ev, client_fd);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, socket_process_client_done, req);
+
+ if (state->num_clients == state->max_clients) {
+ /* Stop accepting any more clients */
+ return;
+ }
+
+ subreq = accept_send(state, state->ev, state->fd);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, socket_process_client, req);
+}
+
+static void socket_process_client_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct socket_process_state *state = tevent_req_data(
+ req, struct socket_process_state);
+ bool ret;
+ int err = 0;
+
+ ret = echo_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (state->num_clients == state->max_clients) {
+ tevent_req_done(req);
+ }
+}
+
+static void socket_process_recv(struct tevent_req *req, int *perr)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ if (perr != NULL) {
+ *perr = err;
+ }
+ }
+}
+
+static int socket_init(char *sockpath)
+{
+ struct sockaddr_un addr;
+ int fd, ret;
+ size_t len;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+
+ len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path));
+ assert(len < sizeof(addr.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ assert(fd != -1);
+
+ ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ assert(ret != -1);
+
+ ret = listen(fd, 10);
+ assert(ret != -1);
+
+ return fd;
+}
+
+int main(int argc, char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int fd, err = 0;
+ int num_clients;
+
+ if (argc != 3) {
+ printf("Usage: %s <sockpath> <num_clients>\n", argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ fd = socket_init(argv[1]);
+ num_clients = atoi(argv[2]);
+ assert(num_clients > 0);
+
+ req = socket_process_send(mem_ctx, ev, fd, num_clients);
+ assert(req != NULL);
+
+ tevent_req_poll(req, ev);
+
+ socket_process_recv(req, &err);
+ return err;
+}
diff --git a/ctdb/tests/src/comm_test.c b/ctdb/tests/src/comm_test.c
new file mode 100644
index 0000000..4595928
--- /dev/null
+++ b/ctdb/tests/src/comm_test.c
@@ -0,0 +1,501 @@
+/*
+ comm tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "common/pkt_read.c"
+#include "common/pkt_write.c"
+#include "common/comm.c"
+
+/*
+ * Test read_handler and dead_handler
+ */
+
+static void test1_read_handler(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ int *result = (int *)private_data;
+
+ *result = -1;
+}
+
+static void test1_dead_handler(void *private_data)
+{
+ int *result = (int *)private_data;
+
+ *result = 1;
+}
+
+static void test1(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct comm_context *comm;
+ int fd[2];
+ int result = 0;
+ uint32_t data[2];
+ int ret;
+ ssize_t n;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ ret = comm_setup(ev, ev, fd[0], test1_read_handler, &result,
+ test1_dead_handler, &result, &comm);
+ assert(ret == 0);
+
+ data[0] = 2 * sizeof(uint32_t);
+ data[1] = 0;
+
+ n = write(fd[1], (void *)&data, data[0]);
+ assert(n == data[0]);
+
+ while (result == 0) {
+ tevent_loop_once(ev);
+ }
+
+ assert(result == -1);
+
+ result = 0;
+ close(fd[1]);
+
+ while (result == 0) {
+ tevent_loop_once(ev);
+ }
+
+ assert(result == 1);
+
+ talloc_free(mem_ctx);
+}
+
+/*
+ * Test that the tevent_req returned by comm_write_send() can be free'd.
+ */
+
+struct test2_state {
+ TALLOC_CTX *mem_ctx;
+ bool done;
+};
+
+static void test2_read_handler(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test2_state *state = (struct test2_state *)private_data;
+
+ TALLOC_FREE(state->mem_ctx);
+}
+
+static void test2_dead_handler(void *private_data)
+{
+ abort();
+}
+
+struct test2_write_state {
+ int count;
+};
+
+static void test2_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *test2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct comm_context *comm,
+ uint8_t *buf, size_t buflen)
+{
+ struct tevent_req *req, *subreq;
+ struct test2_write_state *state;
+ int i;
+
+ req = tevent_req_create(mem_ctx, &state, struct test2_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->count = 0;
+
+ for (i=0; i<10; i++) {
+ subreq = comm_write_send(state, ev, comm, buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test2_write_done, req);
+ }
+
+ return req;
+}
+
+static void test2_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test2_write_state *state = tevent_req_data(
+ req, struct test2_write_state);
+ bool status;
+ int ret;
+
+ status = comm_write_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->count += 1;
+
+ if (state->count == 10) {
+ tevent_req_done(req);
+ }
+}
+
+static void test2_timer_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval cur_time,
+ void *private_data)
+{
+ struct test2_state *state = (struct test2_state *)private_data;
+
+ state->done = true;
+}
+
+static void test2(void)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct comm_context *comm_reader, *comm_writer;
+ struct test2_state test2_state;
+ struct tevent_req *req;
+ struct tevent_timer *te;
+ int fd[2];
+ uint32_t data[2];
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ test2_state.mem_ctx = talloc_new(mem_ctx);
+ assert(test2_state.mem_ctx != NULL);
+
+ test2_state.done = false;
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ ret = comm_setup(ev, ev, fd[0], test2_read_handler, &test2_state,
+ test2_dead_handler, NULL, &comm_reader);
+ assert(ret == 0);
+
+ ret = comm_setup(ev, ev, fd[1], NULL, NULL, test2_dead_handler, NULL,
+ &comm_writer);
+ assert(ret == 0);
+
+ data[0] = 2 * sizeof(uint32_t);
+ data[1] = 0;
+
+ req = test2_write_send(test2_state.mem_ctx, ev, comm_writer,
+ (uint8_t *)data, data[0]);
+ assert(req != NULL);
+
+ te = tevent_add_timer(ev, ev, tevent_timeval_current_ofs(5,0),
+ test2_timer_handler, &test2_state);
+ assert(te != NULL);
+
+ while (! test2_state.done) {
+ tevent_loop_once(ev);
+ }
+
+ talloc_free(mem_ctx);
+}
+
+/*
+ * Test that data is written and read correctly.
+ */
+
+static void test3_dead_handler(void *private_data)
+{
+ int dead_data = *(int *)private_data;
+
+ assert(dead_data == 1 || dead_data == 2);
+
+ if (dead_data == 1) {
+ /* reader */
+ fprintf(stderr, "writer closed pipe\n");
+ } else {
+ /* writer */
+ fprintf(stderr, "reader closed pipe\n");
+ }
+}
+
+struct test3_writer_state {
+ struct tevent_context *ev;
+ struct comm_context *comm;
+ uint8_t *buf;
+ size_t *pkt_size;
+ int count, id;
+};
+
+static void test3_writer_next(struct tevent_req *subreq);
+
+static struct tevent_req *test3_writer_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct comm_context *comm,
+ size_t *pkt_size, size_t count)
+{
+ struct tevent_req *req, *subreq;
+ struct test3_writer_state *state;
+ size_t max_size = 0, buflen;
+ size_t i;
+
+ for (i=0; i<count; i++) {
+ if (pkt_size[i] > max_size) {
+ max_size = pkt_size[i];
+ }
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct test3_writer_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->comm = comm;
+ state->pkt_size = pkt_size;
+ state->count = count;
+ state->id = 0;
+
+ state->buf = talloc_array(state, uint8_t, max_size);
+ if (state->buf == NULL) {
+ talloc_free(req);
+ return NULL;
+ }
+ for (i=0; i<max_size; i++) {
+ state->buf[i] = i%256;
+ }
+
+ buflen = state->pkt_size[state->id];
+ *(uint32_t *)state->buf = buflen;
+ subreq = comm_write_send(state, state->ev, state->comm,
+ state->buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, test3_writer_next, req);
+ return req;
+}
+
+static void test3_writer_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test3_writer_state *state = tevent_req_data(
+ req, struct test3_writer_state);
+ bool ret;
+ int err;
+ size_t buflen;
+
+ ret = comm_write_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ state->id++;
+ if (state->id >= state->count) {
+ tevent_req_done(req);
+ return;
+ }
+
+ buflen = state->pkt_size[state->id];
+ *(uint32_t *)state->buf = buflen;
+ subreq = comm_write_send(state, state->ev, state->comm,
+ state->buf, buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ tevent_req_set_callback(subreq, test3_writer_next, req);
+}
+
+static void test3_writer_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return;
+ }
+ *perr = 0;
+}
+
+static void test3_writer(int fd, size_t *pkt_size, size_t count)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct comm_context *comm;
+ struct tevent_req *req;
+ int dead_data = 2;
+ int err;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ err = comm_setup(mem_ctx, ev, fd, NULL, NULL,
+ test3_dead_handler, &dead_data, &comm);
+ assert(err == 0);
+ assert(comm != NULL);
+
+ req = test3_writer_send(mem_ctx, ev, comm, pkt_size, count);
+ assert(req != NULL);
+
+ tevent_req_poll(req, ev);
+
+ test3_writer_recv(req, &err);
+ assert(err == 0);
+
+ talloc_free(mem_ctx);
+}
+
+struct test3_reader_state {
+ size_t *pkt_size;
+ int count, received;
+ bool done;
+};
+
+static void test3_reader_handler(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test3_reader_state *state = talloc_get_type_abort(
+ private_data, struct test3_reader_state);
+
+ assert(buflen == state->pkt_size[state->received]);
+ printf("%zi ", buflen);
+ state->received++;
+
+ if (state->received == state->count) {
+ printf("\n");
+ state->done = true;
+ }
+}
+
+static void test3_reader(int fd, size_t *pkt_size, int count)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct comm_context *comm;
+ struct test3_reader_state *state;
+ int dead_data = 1;
+ int err;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ state = talloc_zero(mem_ctx, struct test3_reader_state);
+ assert(state != NULL);
+
+ state->pkt_size = pkt_size;
+ state->count = count;
+ state->received = 0;
+ state->done = false;
+
+ err = comm_setup(mem_ctx, ev, fd, test3_reader_handler, state,
+ test3_dead_handler, &dead_data, &comm);
+ assert(err == 0);
+ assert(comm != NULL);
+
+ while (!state->done) {
+ tevent_loop_once(ev);
+ }
+
+ talloc_free(mem_ctx);
+}
+
+static void test3(void)
+{
+ int fd[2];
+ int ret;
+ pid_t pid;
+ size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192,
+ 200, 16384, 300, 32768, 400, 65536,
+ 1024*1024 };
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Child process */
+ close(fd[0]);
+ test3_writer(fd[1], pkt_size, 13);
+ close(fd[1]);
+ exit(0);
+ }
+
+ close(fd[1]);
+ test3_reader(fd[0], pkt_size, 13);
+ close(fd[0]);
+}
+
+
+int main(int argc, const char **argv)
+{
+ int num;
+
+ if (argc != 2) {
+ fprintf(stderr, "%s <testnum>\n", argv[0]);
+ exit(1);
+ }
+
+ num = atoi(argv[1]);
+
+ switch (num) {
+ case 1:
+ test1();
+ break;
+
+ case 2:
+ test2();
+ break;
+
+ case 3:
+ test3();
+ break;
+
+ default:
+ fprintf(stderr, "Unknown test number %s\n", argv[1]);
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/conf_test.c b/ctdb/tests/src/conf_test.c
new file mode 100644
index 0000000..9b3bd8f
--- /dev/null
+++ b/ctdb/tests/src/conf_test.c
@@ -0,0 +1,513 @@
+/*
+ Configuration file handling on top of tini
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <assert.h>
+
+#include "common/conf.c"
+
+static void test1(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_section(conf, NULL, NULL);
+ status = conf_valid(conf);
+ assert(status == false);
+
+ talloc_free(mem_ctx);
+}
+
+static void test2(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_string(conf, "section1", "key1", "default", NULL);
+ status = conf_valid(conf);
+ assert(status == false);
+
+ talloc_free(mem_ctx);
+}
+
+static void test3(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", NULL, NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", "value1", NULL);
+ status = conf_valid(conf);
+ assert(status == false);
+
+ talloc_free(mem_ctx);
+}
+
+static void test4(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", NULL, NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_integer(conf, "section1", "key1", 10, NULL);
+ status = conf_valid(conf);
+ assert(status == false);
+
+ talloc_free(mem_ctx);
+}
+
+static void test5(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ enum conf_type type;
+ int ret;
+ bool status;
+ const char *s_val;
+ int i_val;
+ bool b_val;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", "value1", NULL);
+ conf_define_integer(conf, "section1", "key2", 10, NULL);
+ conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+ conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+ conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+ conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ status = conf_query(conf, "section1", "key1", &type);
+ assert(status == true);
+ assert(type == CONF_STRING);
+
+ status = conf_query(conf, "section1", "key2", &type);
+ assert(status == true);
+ assert(type == CONF_INTEGER);
+
+ status = conf_query(conf, "section1", "key3", &type);
+ assert(status == true);
+ assert(type == CONF_BOOLEAN);
+
+ assert(strcmp(s_val, "value1") == 0);
+ assert(i_val == 10);
+ assert(b_val == true);
+
+ conf_set_defaults(conf);
+
+ assert(strcmp(s_val, "value1") == 0);
+ assert(i_val == 10);
+ assert(b_val == true);
+
+ talloc_free(mem_ctx);
+}
+
+static void test6(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+ const char *s_val, *s2_val;
+ int i_val, i2_val;
+ bool b_val, b2_val, is_default;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", "default", NULL);
+ conf_define_integer(conf, "section1", "key2", 10, NULL);
+ conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+ conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+ conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+ conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ is_default = false;
+ ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default);
+ assert(ret == 0);
+ assert(strcmp(s2_val, "default") == 0);
+ assert(is_default == true);
+
+ is_default = false;
+ ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default);
+ assert(ret == 0);
+ assert(i2_val == 10);
+ assert(is_default == true);
+
+ is_default = false;
+ ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default);
+ assert(ret == 0);
+ assert(b2_val == true);
+ assert(is_default == true);
+
+ ret = conf_set_string(conf, "section1", "key1", "foobar");
+ assert(ret == 0);
+
+ ret = conf_set_integer(conf, "section1", "key2", 20);
+ assert(ret == 0);
+
+ ret = conf_set_boolean(conf, "section1", "key3", false);
+ assert(ret == 0);
+
+ assert(strcmp(s_val, "foobar") == 0);
+ assert(i_val == 20);
+ assert(b_val == false);
+
+ is_default = true;
+ ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default);
+ assert(ret == 0);
+ assert(strcmp(s2_val, "foobar") == 0);
+ assert(is_default == false);
+
+ is_default = true;
+ ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default);
+ assert(ret == 0);
+ assert(i2_val == 20);
+ assert(is_default == false);
+
+ is_default = true;
+ ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default);
+ assert(ret == 0);
+ assert(b2_val == false);
+ assert(is_default == false);
+
+ conf_dump(conf, stdout);
+
+ conf_set_defaults(conf);
+
+ assert(strcmp(s_val, "default") == 0);
+ assert(i_val == 10);
+ assert(b_val == true);
+
+ talloc_free(mem_ctx);
+}
+
+static bool test7_validate_string(const char *key,
+ const char *old_value, const char *new_value,
+ enum conf_update_mode mode)
+{
+ return false;
+}
+
+static bool test7_validate_integer(const char *key,
+ int old_value, int new_value,
+ enum conf_update_mode mode)
+{
+ return false;
+}
+
+static bool test7_validate_boolean(const char *key,
+ bool old_value, bool new_value,
+ enum conf_update_mode mode)
+{
+ return false;
+}
+
+static void test7(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+ const char *s_val, *s2_val;
+ int i_val, i2_val;
+ bool b_val, b2_val;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", "default",
+ test7_validate_string);
+ conf_define_integer(conf, "section1", "key2", 10,
+ test7_validate_integer);
+ conf_define_boolean(conf, "section1", "key3", true,
+ test7_validate_boolean);
+
+ conf_assign_string_pointer(conf, "section1", "key1", &s_val);
+ conf_assign_integer_pointer(conf, "section1", "key2", &i_val);
+ conf_assign_boolean_pointer(conf, "section1", "key3", &b_val);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ ret = conf_set_string(conf, "section1", "key1", "default");
+ assert(ret == 0);
+
+ ret = conf_set_string(conf, "section1", "key1", "foobar");
+ assert(ret == EINVAL);
+
+ ret = conf_set_integer(conf, "section1", "key2", 10);
+ assert(ret == 0);
+
+ ret = conf_set_integer(conf, "section1", "key2", 20);
+ assert(ret == EINVAL);
+
+ ret = conf_set_boolean(conf, "section1", "key3", true);
+ assert(ret == 0);
+
+ ret = conf_set_boolean(conf, "section1", "key3", false);
+ assert(ret == EINVAL);
+
+ assert(strcmp(s_val, "default") == 0);
+ assert(i_val == 10);
+ assert(b_val == true);
+
+ ret = conf_get_string(conf, "section1", "key2", &s2_val, NULL);
+ assert(ret == EINVAL);
+
+ ret = conf_get_integer(conf, "section1", "key3", &i2_val, NULL);
+ assert(ret == EINVAL);
+
+ ret = conf_get_boolean(conf, "section1", "key1", &b2_val, NULL);
+ assert(ret == EINVAL);
+
+ talloc_free(mem_ctx);
+}
+
+static bool test8_validate(struct conf_context *conf,
+ const char *section,
+ enum conf_update_mode mode)
+{
+ return false;
+}
+
+static void test8(const char *filename)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", test8_validate);
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_define_string(conf, "section1", "key1", "default", NULL);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ ret = conf_load(conf, filename, true);
+ conf_dump(conf, stdout);
+
+ talloc_free(mem_ctx);
+ exit(ret);
+}
+
+static void test9(const char *filename, bool ignore_unknown)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+
+ conf_define_string(conf, "section1", "key1", "value1", NULL);
+ conf_define_integer(conf, "section1", "key2", 10, NULL);
+ conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ conf_set_boolean(conf, "section1", "key3", false);
+
+ ret = conf_load(conf, filename, ignore_unknown);
+ conf_dump(conf, stdout);
+
+ talloc_free(mem_ctx);
+ exit(ret);
+}
+
+static void test11(const char *filename)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ char reload[PATH_MAX];
+ struct conf_context *conf;
+ int ret;
+ bool status;
+
+ ret = snprintf(reload, sizeof(reload), "%s.reload", filename);
+ assert((size_t)ret < sizeof(reload));
+
+ ret = conf_init(mem_ctx, &conf);
+ assert(ret == 0);
+ assert(conf != NULL);
+
+ conf_define_section(conf, "section1", NULL);
+
+ conf_define_string(conf, "section1", "key1", "value1", NULL);
+ conf_define_integer(conf, "section1", "key2", 10, NULL);
+ conf_define_boolean(conf, "section1", "key3", true, NULL);
+
+ status = conf_valid(conf);
+ assert(status == true);
+
+ ret = conf_load(conf, filename, false);
+ assert(ret == 0);
+
+ ret = rename(reload, filename);
+ assert(ret == 0);
+
+ ret = conf_reload(conf);
+ assert(ret == 0);
+
+ conf_dump(conf, stdout);
+
+ talloc_free(mem_ctx);
+ exit(ret);
+}
+
+int main(int argc, const char **argv)
+{
+ int num;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]);
+ exit(1);
+ }
+
+ num = atoi(argv[1]);
+ if (num > 7 && argc != 3) {
+ fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]);
+ exit(1);
+ }
+
+ switch (num) {
+ case 1:
+ test1();
+ break;
+
+ case 2:
+ test2();
+ break;
+
+ case 3:
+ test3();
+ break;
+
+ case 4:
+ test4();
+ break;
+
+ case 5:
+ test5();
+ break;
+
+ case 6:
+ test6();
+ break;
+
+ case 7:
+ test7();
+ break;
+
+ case 8:
+ test8(argv[2]);
+ break;
+
+ case 9:
+ test9(argv[2], true);
+ break;
+
+ case 10:
+ test9(argv[2], false);
+ break;
+
+ case 11:
+ test11(argv[2]);
+ break;
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/ctdb_io_test.c b/ctdb/tests/src/ctdb_io_test.c
new file mode 100644
index 0000000..d8f2216
--- /dev/null
+++ b/ctdb/tests/src/ctdb_io_test.c
@@ -0,0 +1,356 @@
+/*
+ ctdb_io tests
+
+ Copyright (C) Christof Schmitt 2019
+
+ 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/filesys.h"
+
+#include <assert.h>
+
+#include "common/ctdb_io.c"
+
+void ctdb_set_error(struct ctdb_context *ctdb, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ assert(false);
+}
+
+static void test_setup(ctdb_queue_cb_fn_t cb,
+ int *pfd,
+ struct ctdb_context **pctdb,
+ struct ctdb_queue **pqueue)
+{
+ int pipefd[2], ret;
+ struct ctdb_context *ctdb;
+ struct ctdb_queue *queue;
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ ctdb = talloc_zero(NULL, struct ctdb_context);
+ assert(ctdb != NULL);
+
+ ctdb->ev = tevent_context_init(NULL);
+
+ queue = ctdb_queue_setup(ctdb, ctdb, pipefd[0], 0, cb,
+ NULL, "test queue");
+ assert(queue != NULL);
+
+ *pctdb = ctdb;
+ *pfd = pipefd[1];
+ if (pqueue != NULL) {
+ *pqueue = queue;
+ }
+}
+
+static const size_t test1_req_len = 8;
+static const char *test1_req = "abcdefgh";
+
+static void test1_callback(uint8_t *data, size_t length, void *private_data)
+{
+ uint32_t len;
+
+ len = *(uint32_t *)data;
+ assert(len == sizeof(uint32_t) + test1_req_len);
+
+ assert(length == sizeof(uint32_t) + test1_req_len);
+ assert(memcmp(data + sizeof(len), test1_req, test1_req_len) == 0);
+}
+
+static void test1(void)
+{
+ struct ctdb_context *ctdb;
+ int fd;
+ ssize_t ret;
+ uint32_t pkt_size;
+
+ test_setup(test1_callback, &fd, &ctdb, NULL);
+
+ pkt_size = sizeof(uint32_t) + test1_req_len;
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, test1_req, test1_req_len);
+ assert(ret != -1 && (size_t)ret == test1_req_len);
+
+ tevent_loop_once(ctdb->ev);
+
+ TALLOC_FREE(ctdb);
+}
+
+static const size_t test2_req_len[] = { 900, 24, 600 };
+
+static int test2_cb_num = 0;
+
+static void test2_callback(uint8_t *data, size_t length, void *private_data)
+{
+ uint32_t len;
+
+ len = *(uint32_t *)data;
+ assert(len == sizeof(uint32_t) + test2_req_len[test2_cb_num]);
+ assert(length == sizeof(uint32_t) + test2_req_len[test2_cb_num]);
+
+ test2_cb_num++;
+}
+
+static void test2(void)
+{
+ struct ctdb_context *ctdb;
+ int fd;
+ ssize_t ret;
+ size_t i;
+ uint32_t pkt_size;
+ char req[1024] = { 0 };
+
+ for (i = 0; i < sizeof(req); i++) {
+ req[i] = i % CHAR_MAX;
+ }
+
+ test_setup(test2_callback, &fd, &ctdb, NULL);
+
+ /*
+ * request 0
+ */
+
+ pkt_size = sizeof(uint32_t) + test2_req_len[0];
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, req, test2_req_len[0]);
+ assert(ret != -1 && (size_t)ret == test2_req_len[0]);
+
+ /*
+ * request 1
+ */
+ pkt_size = sizeof(uint32_t) + test2_req_len[1];
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ /*
+ * Omit the last byte to avoid buffer processing.
+ */
+ ret = write(fd, req, test2_req_len[1] - 1);
+ assert(ret != -1 && (size_t)ret == test2_req_len[1] - 1);
+
+ tevent_loop_once(ctdb->ev);
+
+ /*
+ * Write the missing byte now.
+ */
+ ret = write(fd, &req[test2_req_len[1] - 1], 1);
+ assert(ret != -1 && (size_t)ret == 1);
+
+ /*
+ * request 2
+ */
+ pkt_size = sizeof(uint32_t) + test2_req_len[2];
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, req, test2_req_len[2]);
+ assert(ret != -1 && (size_t)ret == test2_req_len[2]);
+
+ tevent_loop_once(ctdb->ev);
+ tevent_loop_once(ctdb->ev);
+
+ assert(test2_cb_num == 2);
+
+ TALLOC_FREE(ctdb);
+}
+
+static void test_cb(uint8_t *data, size_t length, void *private_data)
+{
+ /* dummy handler, not verifying anything */
+ TALLOC_FREE(data);
+}
+
+static void test3(void)
+{
+ struct ctdb_context *ctdb;
+ struct ctdb_queue *queue;
+ uint32_t pkt_size;
+ char *request;
+ size_t req_len;
+ int fd;
+ ssize_t ret;
+
+ test_setup(test_cb, &fd, &ctdb, &queue);
+ request = talloc_zero_size(queue, queue->buffer_size);
+
+ /*
+ * calculate a request length which will fit into the buffer
+ * but not twice. Because we need to write the size integer
+ * as well (4-bytes) we're guaranteed that no 2 packets will fit.
+ */
+ req_len = queue->buffer_size >> 1;
+
+ /* writing first packet */
+ pkt_size = sizeof(uint32_t) + req_len;
+
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, request, req_len);
+ assert(ret != -1 && (size_t)ret == req_len);
+
+ /* writing second, incomplete packet */
+ pkt_size = sizeof(uint32_t) + req_len;
+
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, request, req_len >> 1);
+ assert(ret != -1 && (size_t)ret == req_len >> 1);
+
+ /* process...only 1st packet can be processed */
+ tevent_loop_once(ctdb->ev);
+
+ /* we should see a progressed offset of req_len + sizeof(pkt_size) */
+ assert(queue->buffer.offset == req_len + sizeof(pkt_size));
+
+ /* writing another few bytes of the still incomplete packet */
+ ret = write(fd, request, (req_len >> 1) - 1);
+ assert(ret != -1 && (size_t)ret == (req_len >> 1) - 1);
+
+ /*
+ * the packet is still incomplete and connot be processed
+ * but the packet data had to be moved in the buffer in order
+ * to fetch the new 199 bytes -> offset must be 0 now.
+ */
+ tevent_loop_once(ctdb->ev);
+ /*
+ * needs to be called twice as an incomplete packet
+ * does not trigger a schedule_immediate
+ */
+ tevent_loop_once(ctdb->ev);
+
+ assert(queue->buffer.offset == 0);
+
+ TALLOC_FREE(ctdb);
+}
+
+static void test4(void)
+{
+ struct ctdb_context *ctdb;
+ struct ctdb_queue *queue;
+ uint32_t pkt_size;
+ char *request;
+ size_t req_len, half_buf_size;
+ int fd;
+ ssize_t ret;
+
+ test_setup(test_cb, &fd, &ctdb, &queue);
+
+ req_len = queue->buffer_size << 1; /* double the buffer size */
+ request = talloc_zero_size(queue, req_len);
+
+ /* writing first part of packet exceeding standard buffer size */
+ pkt_size = sizeof(uint32_t) + req_len;
+
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ half_buf_size = queue->buffer_size >> 1;
+
+ ret = write(fd, request, req_len - half_buf_size);
+ assert(ret != -1 && (size_t)ret == req_len - half_buf_size);
+
+ /*
+ * process...
+ * this needs to be done to have things changed
+ */
+ tevent_loop_once(ctdb->ev);
+ /*
+ * needs to be called twice as an initial incomplete packet
+ * does not trigger a schedule_immediate
+ */
+ tevent_loop_once(ctdb->ev);
+
+ /* the buffer should be resized to packet size now */
+ assert(queue->buffer.size == pkt_size);
+
+ /* writing remaining data */
+ ret = write(fd, request, half_buf_size);
+ assert(ret != -1 && (size_t)ret == half_buf_size);
+
+ /* process... */
+ tevent_loop_once(ctdb->ev);
+
+ /*
+ * the buffer was increased beyond its standard size.
+ * once packet got processed, the buffer has to be free'd
+ * and will be re-allocated with standard size on new request arrival.
+ */
+
+ assert(queue->buffer.size == 0);
+
+ /* writing new packet to verify standard buffer size */
+ pkt_size = sizeof(uint32_t) + half_buf_size;
+
+ ret = write(fd, &pkt_size, sizeof(pkt_size));
+ assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
+
+ ret = write(fd, request, half_buf_size);
+ assert(ret != -1 && (size_t)ret == half_buf_size);
+
+ /* process... */
+ tevent_loop_once(ctdb->ev);
+
+ /* back to standard buffer size */
+ assert(queue->buffer.size == queue->buffer_size);
+
+ TALLOC_FREE(ctdb);
+}
+
+int main(int argc, const char **argv)
+{
+ int num;
+
+ if (argc != 2) {
+ fprintf(stderr, "%s <testnum>\n", argv[0]);
+ exit(1);
+ }
+
+
+ num = atoi(argv[1]);
+ switch (num) {
+ case 1:
+ test1();
+ break;
+
+ case 2:
+ test2();
+ break;
+
+ case 3:
+ test3();
+ break;
+
+ case 4:
+ test4();
+ break;
+
+ default:
+ fprintf(stderr, "Unknown test number %s\n", argv[1]);
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/ctdb_packet_parse.c b/ctdb/tests/src/ctdb_packet_parse.c
new file mode 100644
index 0000000..0b99b34
--- /dev/null
+++ b/ctdb/tests/src/ctdb_packet_parse.c
@@ -0,0 +1,136 @@
+/*
+ CTDB protocol parser
+
+ 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/locale.h"
+
+#include <talloc.h>
+#include <tdb.h>
+
+#include "protocol/protocol.h"
+#include "protocol/protocol_api.h"
+
+static TDB_DATA strace_parser(char *buf, TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA data;
+ size_t i = 0, j = 0;
+
+ data.dptr = talloc_size(mem_ctx, strlen(buf));
+ if (data.dptr == NULL) {
+ return tdb_null;
+ }
+
+ while (i < strlen(buf)) {
+ if (buf[i] == '\\') {
+ /* first char after '\' is a digit or other escape */
+ if (isdigit(buf[i+1])) {
+ char tmp[4] = { '\0', '\0', '\0', '\0' };
+
+ tmp[0] = buf[i+1];
+ if (isdigit(buf[i+2])) {
+ tmp[1] = buf[i+2];
+ if (isdigit(buf[i+3])) {
+ tmp[2] = buf[i+3];
+ i += 4;
+ } else {
+ i += 3;
+ }
+ } else {
+ i += 2;
+ }
+ data.dptr[j] = strtol(tmp, NULL, 8);
+ } else if (buf[i+1] == 'a') {
+ data.dptr[j] = 7;
+ i += 2;
+ } else if (buf[i+1] == 'b') {
+ data.dptr[j] = 8;
+ i += 2;
+ } else if (buf[i+1] == 't') {
+ data.dptr[j] = 9;
+ i += 2;
+ } else if (buf[i+1] == 'n') {
+ data.dptr[j] = 10;
+ i += 2;
+ } else if (buf[i+1] == 'v') {
+ data.dptr[j] = 11;
+ i += 2;
+ } else if (buf[i+1] == 'f') {
+ data.dptr[j] = 12;
+ i += 2;
+ } else if (buf[i+1] == 'r') {
+ data.dptr[j] = 13;
+ i += 2;
+ } else {
+ fprintf(stderr,
+ "Unknown escape \\%c\n",
+ buf[i+1]);
+ data.dptr[j] = 0;
+ }
+
+ j += 1;
+ } else if (buf[i] == '\n') {
+ i += 1;
+ } else if (buf[i] == '\0') {
+ break;
+ } else {
+ data.dptr[j] = buf[i];
+ i += 1;
+ j += 1;
+ }
+ }
+
+ data.dsize = j;
+
+ return data;
+}
+
+int main(int argc, char *argv[])
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ char line[1024];
+ char *ptr;
+ TDB_DATA (*parser)(char *, TALLOC_CTX *);
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s strace\n", argv[0]);
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "strace") == 0) {
+ parser = strace_parser;
+ } else {
+ fprintf(stderr, "Unknown input format - %s\n", argv[1]);
+ exit(1);
+ }
+
+ while ((ptr = fgets(line, sizeof(line), stdin)) != NULL) {
+ TDB_DATA data;
+
+ data = parser(ptr, mem_ctx);
+ if (data.dptr == NULL) {
+ continue;
+ }
+
+ ctdb_packet_print(data.dptr, data.dsize, stdout);
+ TALLOC_FREE(data.dptr);
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/ctdb_takeover_tests.c b/ctdb/tests/src/ctdb_takeover_tests.c
new file mode 100644
index 0000000..cc6b441
--- /dev/null
+++ b/ctdb/tests/src/ctdb_takeover_tests.c
@@ -0,0 +1,278 @@
+/*
+ Tests for ctdb_takeover.c
+
+ Copyright (C) Martin Schwenke 2011
+
+ 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 <assert.h>
+#include <talloc.h>
+
+#include "lib/util/debug.h"
+
+#include "protocol/protocol.h"
+#include "protocol/protocol_util.h"
+#include "common/logging.h"
+#include "common/system.h"
+
+#include "server/ipalloc.h"
+
+#include "ipalloc_read_known_ips.h"
+
+static void print_ctdb_public_ip_list(TALLOC_CTX *mem_ctx,
+ struct public_ip_list * ips)
+{
+ while (ips) {
+ printf("%s %d\n",
+ ctdb_sock_addr_to_string(mem_ctx, &(ips->addr), false),
+ ips->pnn);
+ ips = ips->next;
+ }
+}
+
+static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx,
+ int numnodes,
+ const char *tunable);
+static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx,
+ int numnodes);
+
+static void read_ctdb_public_ip_info(TALLOC_CTX *ctx,
+ int numnodes,
+ bool multi,
+ struct ctdb_public_ip_list ** known,
+ struct ctdb_public_ip_list ** avail)
+{
+ int n;
+ enum ctdb_runstate *runstate;
+
+ *known = ipalloc_read_known_ips(ctx, numnodes, multi);
+ assert(*known != NULL);
+
+ *avail = talloc_zero_array(ctx, struct ctdb_public_ip_list,
+ numnodes);
+ assert(*avail != NULL);
+
+ runstate = get_runstate(ctx, numnodes);
+ for (n = 0; n < numnodes; n++) {
+ if (runstate[n] == CTDB_RUNSTATE_RUNNING) {
+ (*avail)[n] = (*known)[n];
+ }
+ }
+}
+
+static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx,
+ int numnodes,
+ const char *tunable)
+{
+ int i;
+ char *tok;
+ uint32_t *tvals = talloc_zero_array(tmp_ctx, uint32_t, numnodes);
+ char *t = getenv(tunable);
+
+ if (t) {
+ if (strcmp(t, "1") == 0) {
+ for (i=0; i<numnodes; i++) {
+ tvals[i] = 1;
+ }
+ } else {
+ tok = strtok(t, ",");
+ i = 0;
+ while (tok != NULL) {
+ tvals[i] =
+ (uint32_t) strtol(tok, NULL, 0);
+ i++;
+ tok = strtok(NULL, ",");
+ }
+ if (i != numnodes) {
+ fprintf(stderr, "ERROR: Wrong number of values in %s\n", tunable);
+ exit(1);
+ }
+ }
+ }
+
+ return tvals;
+}
+
+static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx,
+ int numnodes)
+{
+ int i;
+ uint32_t *tvals;
+ enum ctdb_runstate *runstate =
+ talloc_zero_array(tmp_ctx, enum ctdb_runstate, numnodes);
+ char *t = getenv("CTDB_TEST_RUNSTATE");
+
+ if (t == NULL) {
+ for (i=0; i<numnodes; i++) {
+ runstate[i] = CTDB_RUNSTATE_RUNNING;
+ }
+ } else {
+ tvals = get_tunable_values(tmp_ctx, numnodes, "CTDB_TEST_RUNSTATE");
+ for (i=0; i<numnodes; i++) {
+ runstate[i] = (enum ctdb_runstate) tvals[i];
+ }
+ talloc_free(tvals);
+ }
+
+ return runstate;
+}
+
+/* Fake up enough CTDB state to be able to run the IP allocation
+ * algorithm. Usually this sets up some standard state, sets the node
+ * states from the command-line and reads the current IP layout from
+ * stdin.
+ *
+ * However, if read_ips_for_multiple_nodes is true then each node's
+ * idea of the IP layout is read separately from stdin. In this mode
+ * is doesn't make much sense to use read_ctdb_public_ip_info's
+ * optional ALLOWED_PNN,... list in the input, since each node is
+ * being handled separately anyway. IPs for each node are separated
+ * by a blank line. This mode is for testing weird behaviours where
+ * the IP layouts differs across nodes and we want to improve
+ * create_merged_ip_list(), so should only be used in tests of
+ * ipalloc(). Yes, it is a hack... :-)
+ */
+static void ctdb_test_init(TALLOC_CTX *mem_ctx,
+ const char nodestates[],
+ struct ipalloc_state **ipalloc_state,
+ bool read_ips_for_multiple_nodes)
+{
+ struct ctdb_public_ip_list *known;
+ struct ctdb_public_ip_list *avail;
+ char *tok, *ns;
+ const char *t;
+ struct ctdb_node_map *nodemap;
+ uint32_t noiptakeover;
+ ctdb_sock_addr sa_zero = { .ip = { 0 } };
+ enum ipalloc_algorithm algorithm;
+ uint32_t n;
+
+ /* Avoid that const */
+ ns = talloc_strdup(mem_ctx, nodestates);
+
+ nodemap = talloc_zero(mem_ctx, struct ctdb_node_map);
+ assert(nodemap != NULL);
+ nodemap->num = 0;
+ tok = strtok(ns, ",");
+ while (tok != NULL) {
+ n = nodemap->num;
+ nodemap->node = talloc_realloc(nodemap, nodemap->node,
+ struct ctdb_node_and_flags, n+1);
+ nodemap->node[n].pnn = n;
+ nodemap->node[n].flags = (uint32_t) strtol(tok, NULL, 0);
+ nodemap->node[n].addr = sa_zero;
+ nodemap->num++;
+ tok = strtok(NULL, ",");
+ }
+
+ algorithm = IPALLOC_LCP2;
+ if ((t = getenv("CTDB_IP_ALGORITHM"))) {
+ if (strcmp(t, "lcp2") == 0) {
+ algorithm = IPALLOC_LCP2;
+ } else if (strcmp(t, "nondet") == 0) {
+ algorithm = IPALLOC_NONDETERMINISTIC;
+ } else if (strcmp(t, "det") == 0) {
+ algorithm = IPALLOC_DETERMINISTIC;
+ } else {
+ DEBUG(DEBUG_ERR,
+ ("ERROR: unknown IP algorithm %s\n", t));
+ exit(1);
+ }
+ }
+
+ t = getenv("CTDB_SET_NoIPTakeover");
+ if (t != NULL) {
+ noiptakeover = (uint32_t) strtol(t, NULL, 0);
+ } else {
+ noiptakeover = 0;
+ }
+
+ *ipalloc_state = ipalloc_state_init(mem_ctx, nodemap->num,
+ algorithm,
+ (noiptakeover != 0),
+ false,
+ NULL);
+ assert(*ipalloc_state != NULL);
+
+ read_ctdb_public_ip_info(mem_ctx, nodemap->num,
+ read_ips_for_multiple_nodes,
+ &known, &avail);
+
+ /* Drop available IPs for INACTIVE/DISABLED nodes */
+ for (n = 0; n < nodemap->num; n++) {
+ uint32_t flags = nodemap->node[n].flags;
+ if ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0) {
+ avail[n].num = 0;
+ }
+ }
+
+ ipalloc_set_public_ips(*ipalloc_state, known, avail);
+}
+
+/* IP layout is read from stdin. See comment for ctdb_test_init() for
+ * explanation of read_ips_for_multiple_nodes.
+ */
+static void ctdb_test_ipalloc(const char nodestates[],
+ bool read_ips_for_multiple_nodes)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ struct ipalloc_state *ipalloc_state;
+
+ ctdb_test_init(tmp_ctx, nodestates, &ipalloc_state,
+ read_ips_for_multiple_nodes);
+
+ print_ctdb_public_ip_list(tmp_ctx, ipalloc(ipalloc_state));
+
+ talloc_free(tmp_ctx);
+}
+
+static void usage(void)
+{
+ fprintf(stderr, "usage: ctdb_takeover_tests <op>\n");
+ exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ int loglevel;
+ const char *debuglevelstr = getenv("CTDB_TEST_LOGLEVEL");
+
+ setup_logging("ctdb_takeover_tests", DEBUG_STDERR);
+
+ if (! debug_level_parse(debuglevelstr, &loglevel)) {
+ loglevel = DEBUG_DEBUG;
+ }
+ debuglevel_set(loglevel);
+
+ if (argc < 2) {
+ usage();
+ }
+
+ if (argc == 3 &&
+ strcmp(argv[1], "ipalloc") == 0) {
+ ctdb_test_ipalloc(argv[2], false);
+ } else if (argc == 4 &&
+ strcmp(argv[1], "ipalloc") == 0 &&
+ strcmp(argv[3], "multi") == 0) {
+ ctdb_test_ipalloc(argv[2], true);
+ } else {
+ usage();
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/db_hash_test.c b/ctdb/tests/src/db_hash_test.c
new file mode 100644
index 0000000..31aa501
--- /dev/null
+++ b/ctdb/tests/src/db_hash_test.c
@@ -0,0 +1,138 @@
+/*
+ db_hash tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <assert.h>
+
+#include "common/db_hash.c"
+
+static int record_parser(uint8_t *keybuf, size_t keylen,
+ uint8_t *databuf, size_t datalen,
+ void *private_data)
+{
+ int *count = (int *)private_data;
+
+ (*count) += 1;
+ return 0;
+}
+
+static void do_test(enum db_hash_type type)
+{
+ struct db_hash_context *dh = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ uint8_t key[] = "This is a long key";
+ uint8_t value[] = "This is a long value";
+ int ret;
+ int count = 0;
+
+ ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
+ assert(ret == EINVAL);
+
+ ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value));
+ assert(ret == EINVAL);
+
+ ret = db_hash_exists(dh, key, sizeof(key));
+ assert(ret == EINVAL);
+
+ ret = db_hash_delete(dh, key, sizeof(key));
+ assert(ret == EINVAL);
+
+ ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
+ assert(ret == 0);
+
+ ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
+ assert(ret == 0);
+
+ ret = db_hash_exists(dh, key, sizeof(key));
+ assert(ret == 0);
+
+ ret = db_hash_fetch(dh, key, sizeof(key), NULL, NULL);
+ assert(ret == EINVAL);
+
+ ret = db_hash_fetch(dh, key, sizeof(key), record_parser, &count);
+ assert(ret == 0);
+ assert(count == 1);
+
+ ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
+ assert(ret == EEXIST);
+
+ ret = db_hash_delete(dh, key, sizeof(key));
+ assert(ret == 0);
+
+ ret = db_hash_exists(dh, key, sizeof(key));
+ assert(ret == ENOENT);
+
+ ret = db_hash_delete(dh, key, sizeof(key));
+ assert(ret == ENOENT);
+
+ ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key));
+ assert(ret == 0);
+
+ ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value));
+ assert(ret == 0);
+
+ talloc_free(dh);
+ ret = talloc_get_size(mem_ctx);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+static void do_traverse_test(enum db_hash_type type)
+{
+ struct db_hash_context *dh = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ char key[16] = "keyXXXX";
+ char value[] = "This is some test value";
+ int count, ret, i;
+
+ ret = db_hash_traverse(dh, NULL, NULL, &count);
+ assert(ret == EINVAL);
+
+ ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
+ assert(ret == 0);
+
+ for (i=0; i<2000; i++) {
+ sprintf(key, "key%04d", i);
+ ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key),
+ (uint8_t *)value, sizeof(value));
+ assert(ret == 0);
+ }
+
+ ret = db_hash_traverse(dh, NULL, NULL, &count);
+ assert(ret == 0);
+ assert(count == 2000);
+
+ ret = db_hash_traverse(dh, record_parser, &count, NULL);
+ assert(ret == 0);
+ assert(count == 4000);
+
+ talloc_free(dh);
+ talloc_free(mem_ctx);
+}
+
+int main(void)
+{
+ do_test(DB_HASH_SIMPLE);
+ do_test(DB_HASH_COMPLEX);
+ do_traverse_test(DB_HASH_SIMPLE);
+ do_traverse_test(DB_HASH_COMPLEX);
+ return 0;
+}
diff --git a/ctdb/tests/src/db_test_tool.c b/ctdb/tests/src/db_test_tool.c
new file mode 100644
index 0000000..7ac6b03
--- /dev/null
+++ b/ctdb/tests/src/db_test_tool.c
@@ -0,0 +1,792 @@
+/*
+ CTDB DB test tool
+
+ Copyright (C) Martin Schwenke 2019
+
+ Parts based on ctdb.c, event_tool.c:
+
+ Copyright (C) Amitay Isaacs 2015, 2018
+
+ 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/filesys.h"
+#include "system/network.h"
+#include "system/time.h"
+
+#include <ctype.h>
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "lib/util/debug.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/util.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+
+#include "common/cmdline.h"
+#include "common/logging.h"
+#include "common/path.h"
+#include "common/event_script.h"
+#include "common/system_socket.h"
+
+#include "protocol/protocol.h"
+#include "protocol/protocol_api.h"
+#include "protocol/protocol_util.h"
+
+#include "client/client.h"
+#include "client/client_sync.h"
+
+struct tdb_context *client_db_tdb(struct ctdb_db_context *db);
+
+#define TIMEOUT() tevent_timeval_current_ofs(ctx->timelimit, 0)
+
+struct db_test_tool_context {
+ struct cmdline_context *cmdline;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ uint32_t destnode;
+ uint32_t timelimit;
+};
+
+/*
+ * If this is ever consolodated into a larger test tool then these
+ * forward declarations can be moved to an include file
+ */
+int db_test_tool_init(TALLOC_CTX *mem_ctx,
+ const char *prog,
+ struct poptOption *options,
+ int argc,
+ const char **argv,
+ bool parse_options,
+ struct db_test_tool_context **result);
+int db_test_tool_run(struct db_test_tool_context *ctx, int *result);
+
+static int db_test_get_lmaster(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ struct db_test_tool_context *ctx = talloc_get_type_abort(
+ private_data, struct db_test_tool_context);
+ struct ctdb_vnn_map *vnnmap;
+ TDB_DATA key;
+ uint32_t idx, lmaster;
+ unsigned int hash;
+ int ret = 0;
+
+ if (argc != 1) {
+ cmdline_usage(ctx->cmdline, "get-lmaster");
+ return 1;
+ }
+
+ ret = ctdb_ctrl_getvnnmap(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ CTDB_CURRENT_NODE,
+ TIMEOUT(),
+ &vnnmap);
+ if (ret != 0) {
+ D_ERR("Control GETVNN_MAP failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ key.dsize = strlen(argv[0]);
+ key.dptr = (uint8_t *)discard_const(argv[0]);
+
+ hash = tdb_jenkins_hash(&key);
+ idx = hash % vnnmap->size;
+ lmaster = vnnmap->map[idx];
+
+ printf("%"PRId32"\n", lmaster);
+
+ return 0;
+}
+
+static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx,
+ struct db_test_tool_context *ctx,
+ struct ctdb_dbid_map *dbmap,
+ const char *db_name)
+{
+ struct ctdb_dbid *db = NULL;
+ const char *name;
+ unsigned int i;
+ int ret;
+
+ for (i=0; i<dbmap->num; i++) {
+ ret = ctdb_ctrl_get_dbname(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ ctx->destnode,
+ TIMEOUT(),
+ dbmap->dbs[i].db_id,
+ &name);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ if (strcmp(db_name, name) == 0) {
+ talloc_free(discard_const(name));
+ db = &dbmap->dbs[i];
+ break;
+ }
+ }
+
+ return db;
+}
+
+static bool db_exists(TALLOC_CTX *mem_ctx,
+ struct db_test_tool_context *ctx,
+ const char *db_arg,
+ uint32_t *db_id,
+ const char **db_name,
+ uint8_t *db_flags)
+{
+ struct ctdb_dbid_map *dbmap;
+ struct ctdb_dbid *db = NULL;
+ uint32_t id = 0;
+ const char *name = NULL;
+ unsigned int i;
+ int ret = 0;
+
+ ret = ctdb_ctrl_get_dbmap(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ ctx->destnode,
+ TIMEOUT(),
+ &dbmap);
+ if (ret != 0) {
+ return false;
+ }
+
+ if (strncmp(db_arg, "0x", 2) == 0) {
+ id = smb_strtoul(db_arg, NULL, 0, &ret, SMB_STR_STANDARD);
+ if (ret != 0) {
+ return false;
+ }
+ for (i=0; i<dbmap->num; i++) {
+ if (id == dbmap->dbs[i].db_id) {
+ db = &dbmap->dbs[i];
+ break;
+ }
+ }
+ } else {
+ name = db_arg;
+ db = db_find(mem_ctx, ctx, dbmap, name);
+ }
+
+ if (db == NULL) {
+ fprintf(stderr, "No database matching '%s' found\n", db_arg);
+ return false;
+ }
+
+ if (name == NULL) {
+ ret = ctdb_ctrl_get_dbname(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ ctx->destnode,
+ TIMEOUT(),
+ id,
+ &name);
+ if (ret != 0) {
+ return false;
+ }
+ }
+
+ if (db_id != NULL) {
+ *db_id = db->db_id;
+ }
+ if (db_name != NULL) {
+ *db_name = talloc_strdup(mem_ctx, name);
+ }
+ if (db_flags != NULL) {
+ *db_flags = db->flags;
+ }
+ return true;
+}
+
+static int db_test_fetch_local_delete(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ struct db_test_tool_context *ctx = talloc_get_type_abort(
+ private_data, struct db_test_tool_context);
+ struct ctdb_db_context *db = NULL;
+ struct ctdb_record_handle *h = NULL;
+ struct tdb_context *tdb;
+ struct ctdb_ltdb_header header;
+ const char *db_name;
+ TDB_DATA key, data;
+ uint32_t db_id;
+ uint8_t db_flags;
+ size_t len;
+ uint8_t *buf;
+ size_t np;
+ int ret;
+
+ if (argc != 2) {
+ cmdline_usage(ctx->cmdline, "fetch-local-delete");
+ return 1;
+ }
+
+ if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
+ return ENOENT;
+ }
+
+ if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
+ D_ERR("DB %s is not a volatile database\n", db_name);
+ return EINVAL;
+ }
+
+ ret = ctdb_attach(ctx->ev,
+ ctx->client,
+ TIMEOUT(),
+ db_name,
+ db_flags,
+ &db);
+ if (ret != 0) {
+ D_ERR("Failed to attach to DB %s\n", db_name);
+ return ret;
+ }
+
+ key.dsize = strlen(argv[1]);
+ key.dptr = (uint8_t *)discard_const(argv[1]);
+
+ ret = ctdb_fetch_lock(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ db,
+ key,
+ false,
+ &h,
+ &header,
+ NULL);
+ if (ret != 0) {
+ D_ERR("Failed to fetch record for key %s\n", argv[1]);
+ goto done;
+ }
+
+ len = ctdb_ltdb_header_len(&header);
+ buf = talloc_size(mem_ctx, len);
+ if (buf == NULL) {
+ D_ERR("Memory allocation error\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ctdb_ltdb_header_push(&header, buf, &np);
+
+ data.dsize = np;
+ data.dptr = buf;
+
+ tdb = client_db_tdb(db);
+
+ ret = tdb_store(tdb, key, data, TDB_REPLACE);
+ TALLOC_FREE(buf);
+ if (ret != 0) {
+ D_ERR("fetch_lock delete: %s tdb_store failed, %s\n",
+ db_name,
+ tdb_errorstr(tdb));
+ }
+
+done:
+ TALLOC_FREE(h);
+
+ return ret;
+}
+
+#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x)))
+
+static void dump(const char *name, uint8_t *dptr, size_t dsize)
+{
+ size_t i;
+
+ fprintf(stdout, "%s(%zu) = \"", name, dsize);
+ for (i = 0; i < dsize; i++) {
+ if (ISASCII(dptr[i])) {
+ fprintf(stdout, "%c", dptr[i]);
+ } else {
+ fprintf(stdout, "\\%02X", dptr[i]);
+ }
+ }
+ fprintf(stdout, "\"\n");
+}
+
+static void dump_ltdb_header(struct ctdb_ltdb_header *header)
+{
+ fprintf(stdout, "dmaster: %u\n", header->dmaster);
+ fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn);
+ fprintf(stdout, "flags: 0x%08x", header->flags);
+ if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) {
+ fprintf(stdout, " MIGRATED_WITH_DATA");
+ }
+ if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) {
+ fprintf(stdout, " VACUUM_MIGRATED");
+ }
+ if (header->flags & CTDB_REC_FLAG_AUTOMATIC) {
+ fprintf(stdout, " AUTOMATIC");
+ }
+ if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) {
+ fprintf(stdout, " RO_HAVE_DELEGATIONS");
+ }
+ if (header->flags & CTDB_REC_RO_HAVE_READONLY) {
+ fprintf(stdout, " RO_HAVE_READONLY");
+ }
+ if (header->flags & CTDB_REC_RO_REVOKING_READONLY) {
+ fprintf(stdout, " RO_REVOKING_READONLY");
+ }
+ if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) {
+ fprintf(stdout, " RO_REVOKE_COMPLETE");
+ }
+ fprintf(stdout, "\n");
+
+}
+
+static int db_test_local_lock(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ struct db_test_tool_context *ctx = talloc_get_type_abort(
+ private_data, struct db_test_tool_context);
+ struct ctdb_db_context *db;
+ const char *db_name;
+ int pipefd[2];
+ TDB_DATA key;
+ uint32_t db_id;
+ uint8_t db_flags;
+ pid_t pid;
+ int ret;
+
+ if (argc != 2) {
+ cmdline_usage(ctx->cmdline, "local-lock");
+ return 1;
+ }
+
+
+ if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
+ D_ERR("DB %s not attached\n", db_name);
+ return 1;
+ }
+
+ if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
+ D_ERR("DB %s is not a volatile database\n", db_name);
+ return 1;
+ }
+
+ ret = ctdb_attach(ctx->ev,
+ ctx->client,
+ TIMEOUT(),
+ db_name,
+ db_flags,
+ &db);
+ if (ret != 0) {
+ D_ERR("Failed to attach to DB %s\n", db_name);
+ return 1;
+ }
+
+ ret = pipe(pipefd);
+ if (ret != 0) {
+ DBG_ERR("Failed to create pipe\n");
+ return 1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ DBG_ERR("Failed to fork()\n");
+ return 1;
+ }
+
+ if (pid != 0) {
+ ssize_t nread;
+ int status;
+
+ close(pipefd[1]);
+
+ nread = sys_read(pipefd[0], &status, sizeof(status));
+ if (nread < 0 || (size_t)nread != sizeof(status)) {
+ status = EINVAL;
+ }
+
+ if (status == 0) {
+ printf("OK %d\n", pid);
+ } else {
+ printf("FAIL %d\n", status);
+ }
+ fflush(stdout);
+
+ return status;
+ }
+
+ close(pipefd[0]);
+
+ key.dsize = strlen(argv[1]);
+ key.dptr = (uint8_t *)discard_const(argv[1]);
+
+ ret = tdb_chainlock(client_db_tdb(db), key);
+ if (ret != 0) {
+ D_ERR("Failed to lock chain for key %s\n", argv[1]);
+ goto fail;
+ }
+
+ sys_write(pipefd[1], &ret, sizeof(ret));
+
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+
+ /* Hold the lock- the caller should SIGTERM to release the lock */
+ sleep(120);
+ exit(1);
+
+fail:
+ sys_write(pipefd[1], &ret, sizeof(ret));
+ return ret;
+}
+
+static int db_test_local_read(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ struct db_test_tool_context *ctx = talloc_get_type_abort(
+ private_data, struct db_test_tool_context);
+ struct ctdb_db_context *db;
+ struct ctdb_ltdb_header header;
+ const char *db_name;
+ TDB_DATA key, data;
+ uint32_t db_id;
+ uint8_t db_flags;
+ size_t np;
+ int ret;
+
+ if (argc != 2) {
+ cmdline_usage(ctx->cmdline, "local-read");
+ return 1;
+ }
+
+ if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) {
+ return ENOENT;
+ }
+
+ if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
+ D_ERR("DB %s is not a volatile database\n", db_name);
+ return EINVAL;
+ }
+
+ ret = ctdb_attach(ctx->ev,
+ ctx->client,
+ TIMEOUT(),
+ db_name,
+ db_flags,
+ &db);
+ if (ret != 0) {
+ D_ERR("Failed to attach to DB %s\n", db_name);
+ return ret;
+ }
+
+ key.dsize = strlen(argv[1]);
+ key.dptr = (uint8_t *)discard_const(argv[1]);
+
+ data = tdb_fetch(client_db_tdb(db), key);
+
+ if (data.dptr == NULL) {
+ D_ERR("No record for key %s\n", argv[1]);
+ return 1;
+ }
+
+ if (data.dsize < sizeof(struct ctdb_ltdb_header)) {
+ D_ERR("Invalid record for key %s\n", argv[1]);
+ free(data.dptr);
+ return 1;
+ }
+
+ ret = ctdb_ltdb_header_pull(data.dptr, data.dsize, &header, &np);
+ if (ret != 0) {
+ D_ERR("Failed to parse header from data\n");
+ free(data.dptr);
+ return 1;
+ }
+
+ dump_ltdb_header(&header);
+ dump("data", data.dptr + np, data.dsize - np);
+
+ free(data.dptr);
+
+ return 0;
+}
+
+static int db_test_vacuum(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ void *private_data)
+{
+ struct db_test_tool_context *ctx = talloc_get_type_abort(
+ private_data, struct db_test_tool_context);
+ struct ctdb_db_vacuum db_vacuum;
+ struct ctdb_req_control request;
+ struct ctdb_reply_control *reply;
+ const char *db_arg;
+ uint32_t db_id;
+ const char *db_name;
+ uint8_t db_flags;
+ int ret = 0;
+
+ if (argc != 1 && argc != 2) {
+ cmdline_usage(ctx->cmdline, "vacuum");
+ return 1;
+ }
+
+ db_arg = argv[0];
+
+ db_vacuum.full_vacuum_run = false;
+ if (argc == 2) {
+ if (strcmp(argv[1], "full") == 0) {
+ db_vacuum.full_vacuum_run = true;
+ } else {
+ cmdline_usage(ctx->cmdline, "vacuum");
+ return 1;
+ }
+ }
+
+ if (! db_exists(mem_ctx, ctx, db_arg, &db_id, &db_name, &db_flags)) {
+ return ENOENT;
+ }
+
+ if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) {
+ D_ERR("DB %s is not a volatile database\n", db_name);
+ return EINVAL;
+ }
+
+ db_vacuum.db_id = db_id;
+
+ ctdb_req_control_db_vacuum(&request, &db_vacuum);
+
+ ret = ctdb_client_control(mem_ctx,
+ ctx->ev,
+ ctx->client,
+ ctx->destnode,
+ TIMEOUT(),
+ &request,
+ &reply);
+ if (ret != 0) {
+ D_ERR("Control DB_VACUUM failed to node %u, ret=%d\n",
+ ctx->destnode,
+ ret);
+ return ret;
+ }
+
+
+ ret = ctdb_reply_control_db_vacuum(reply);
+ if (ret != 0) {
+ D_ERR("Control DB_VACUUM failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct cmdline_command db_test_commands[] = {
+ {
+ .name = "get-lmaster",
+ .fn = db_test_get_lmaster,
+ .msg_help = "Print lmaster for key",
+ .msg_args = "<key>"
+ },
+ {
+ .name = "fetch-local-delete",
+ .fn = db_test_fetch_local_delete,
+ .msg_help = "Fetch record and delete from local database",
+ .msg_args = "<dbname|dbid> <key>"
+ },
+ {
+ .name = "local-lock",
+ .fn = db_test_local_lock,
+ .msg_help = "Lock a record in a local database",
+ .msg_args = "<dbname|dbid> <key>"
+ },
+ {
+ .name = "local-read",
+ .fn = db_test_local_read,
+ .msg_help = "Read a record from local database",
+ .msg_args = "<dbname|dbid> <key>"
+ },
+ {
+ .name = "vacuum",
+ .fn = db_test_vacuum,
+ .msg_help = "Vacuum a database",
+ .msg_args = "<dbname|dbid> [full]"
+ },
+ CMDLINE_TABLEEND
+};
+
+int db_test_tool_init(TALLOC_CTX *mem_ctx,
+ const char *prog,
+ struct poptOption *options,
+ int argc,
+ const char **argv,
+ bool parse_options,
+ struct db_test_tool_context **result)
+{
+ struct db_test_tool_context *ctx;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct db_test_tool_context);
+ if (ctx == NULL) {
+ D_ERR("Memory allocation error\n");
+ return ENOMEM;
+ }
+
+ ret = cmdline_init(mem_ctx,
+ prog,
+ options,
+ NULL,
+ db_test_commands,
+ &ctx->cmdline);
+ if (ret != 0) {
+ D_ERR("Failed to initialize cmdline, ret=%d\n", ret);
+ talloc_free(ctx);
+ return ret;
+ }
+
+ ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options);
+ if (ret != 0) {
+ cmdline_usage(ctx->cmdline, NULL);
+ talloc_free(ctx);
+ return ret;
+ }
+
+ *result = ctx;
+ return 0;
+}
+
+int db_test_tool_run(struct db_test_tool_context *ctx, int *result)
+{
+ char *ctdb_socket;
+ int ret;
+
+ ctx->ev = tevent_context_init(ctx);
+ if (ctx->ev == NULL) {
+ D_ERR("Failed to initialize tevent\n");
+ return ENOMEM;
+ }
+
+ ctdb_socket = path_socket(ctx, "ctdbd");
+ if (ctdb_socket == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ return ENOMEM;
+ }
+
+ ret = ctdb_client_init(ctx, ctx->ev, ctdb_socket, &ctx->client);
+ if (ret != 0) {
+ D_ERR("Failed to connect to CTDB daemon (%s)\n", ctdb_socket);
+ return ret;
+ }
+
+ ret = cmdline_run(ctx->cmdline, ctx, result);
+ return ret;
+}
+
+#ifdef CTDB_DB_TEST_TOOL
+
+static struct {
+ const char *debug;
+ int destnode;
+ int timelimit;
+} db_test_data = {
+ .debug = "ERROR",
+ .destnode = CTDB_CURRENT_NODE,
+ .timelimit = 60,
+};
+
+struct poptOption db_test_options[] = {
+ {
+ .longName = "debug",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &db_test_data.debug,
+ .val = 0,
+ .descrip = "debug level",
+ .argDescrip = "ERROR|WARNING|NOTICE|INFO|DEBUG"
+ },
+ {
+ .longName = "node",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_INT,
+ .arg = &db_test_data.destnode,
+ .val = 0,
+ .descrip = "node number",
+ .argDescrip = "NUM"
+ },
+ {
+ .longName = "timelimit",
+ .shortName = 't',
+ .argInfo = POPT_ARG_INT,
+ .arg = &db_test_data.timelimit,
+ .val = 0,
+ .descrip = "control time limit",
+ .argDescrip = "SECONDS"
+ },
+ POPT_TABLEEND
+};
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct db_test_tool_context *ctx;
+ int ret, result = 0;
+ int level;
+ bool ok;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = db_test_tool_init(mem_ctx,
+ "ctdb-db-test",
+ db_test_options,
+ argc,
+ argv,
+ true,
+ &ctx);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ exit(1);
+ }
+
+ setup_logging("ctdb-db-test", DEBUG_STDERR);
+ ok = debug_level_parse(db_test_data.debug, &level);
+ if (!ok) {
+ level = DEBUG_ERR;
+ }
+ debuglevel_set(level);
+
+ ctx->destnode = db_test_data.destnode;
+ ctx->timelimit = db_test_data.timelimit;
+
+ ret = db_test_tool_run(ctx, &result);
+ if (ret != 0) {
+ result = ret;
+ }
+
+ talloc_free(mem_ctx);
+ exit(result);
+}
+
+#endif /* CTDB_DB_TEST_TOOL */
diff --git a/ctdb/tests/src/dummy_client.c b/ctdb/tests/src/dummy_client.c
new file mode 100644
index 0000000..13e0691
--- /dev/null
+++ b/ctdb/tests/src/dummy_client.c
@@ -0,0 +1,163 @@
+/*
+ Dummy CTDB client for testing
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "common/logging.h"
+#include "common/path.h"
+
+#include "client/client.h"
+
+static struct {
+ const char *sockpath;
+ const char *debuglevel;
+ int num_connections;
+ int timelimit;
+ const char *srvidstr;
+} options;
+
+static struct poptOption cmdline_options[] = {
+ POPT_AUTOHELP
+ { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0,
+ "Unix domain socket path", "filename" },
+ { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0,
+ "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } ,
+ { "nconn", 'n', POPT_ARG_INT, &options.num_connections, 0,
+ "number of connections", "" },
+ { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0,
+ "time limit", "seconds" },
+ { "srvid", 'S', POPT_ARG_STRING, &options.srvidstr, 0,
+ "srvid to register", "srvid" },
+ POPT_TABLEEND
+};
+
+static void dummy_handler(uint64_t srvid, TDB_DATA data, void *private_data)
+{
+ bool *done = (bool *)private_data;
+
+ *done = true;
+}
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context **client;
+ struct ctdb_client_context *last_client;
+ poptContext pc;
+ int opt, ret, i;
+ int log_level;
+ bool status, done;
+
+ /* Set default options */
+ options.sockpath = NULL;
+ options.debuglevel = "ERR";
+ options.num_connections = 1;
+ options.timelimit = 60;
+ options.srvidstr = NULL;
+
+ 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);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ status = debug_level_parse(options.debuglevel, &log_level);
+ if (! status) {
+ fprintf(stderr, "Invalid debug level\n");
+ poptPrintHelp(pc, stdout, 0);
+ exit(1);
+ }
+
+ setup_logging("dummy_client", DEBUG_STDERR);
+ debuglevel_set(log_level);
+
+ if (options.sockpath == NULL) {
+ options.sockpath = path_socket(mem_ctx, "ctdbd");
+ if (options.sockpath == NULL) {
+ D_ERR("Memory allocation error\n");
+ exit(1);
+ }
+ }
+
+ client = talloc_array(mem_ctx, struct ctdb_client_context *,
+ options.num_connections);
+ if (client == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ for (i=0; i<options.num_connections; i++) {
+ ret = ctdb_client_init(client, ev, options.sockpath,
+ &client[i]);
+ if (ret != 0) {
+ D_ERR("Failed to initialize client %d, ret=%d\n",
+ i, ret);
+ exit(1);
+ }
+ }
+
+ last_client = client[options.num_connections-1];
+
+ done = false;
+ if (options.srvidstr != NULL) {
+ uint64_t srvid;
+
+ srvid = strtoull(options.srvidstr, NULL, 0);
+
+ ret = ctdb_client_set_message_handler(ev, last_client, srvid,
+ dummy_handler, &done);
+ if (ret != 0) {
+ D_ERR("Failed to register srvid, ret=%d\n", ret);
+ talloc_free(client);
+ exit(1);
+ }
+
+ D_INFO("Registered SRVID 0x%"PRIx64"\n", srvid);
+ }
+
+ ret = ctdb_client_wait_timeout(ev, &done,
+ tevent_timeval_current_ofs(options.timelimit, 0));
+ if (ret != 0 && ret == ETIMEDOUT) {
+ D_ERR("client_wait_timeout() failed, ret=%d\n", ret);
+ talloc_free(client);
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ exit(0);
+}
diff --git a/ctdb/tests/src/errcode.c b/ctdb/tests/src/errcode.c
new file mode 100644
index 0000000..7343e81
--- /dev/null
+++ b/ctdb/tests/src/errcode.c
@@ -0,0 +1,189 @@
+/*
+ Portability layer for error codes
+
+ Copyright (C) Amitay Isaacs 2018
+
+ 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/>.
+*/
+
+/*
+ * These errors are as listed in POSIX standard
+ * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)
+ *
+ * Error codes marked obsolete are removed (ENODATA, ENOSR, ENOSTR, ETIME)
+ */
+
+#include "replace.h"
+
+struct {
+ const char *label;
+ int code;
+} err_codes[] = {
+ { "E2BIG", E2BIG },
+
+ { "EACCES", EACCES },
+ { "EADDRINUSE", EADDRINUSE },
+ { "EADDRNOTAVAIL", EADDRNOTAVAIL },
+ { "EAFNOSUPPORT", EAFNOSUPPORT },
+ { "EAGAIN", EAGAIN },
+ { "EALREADY", EALREADY },
+
+ { "EBADF", EBADF },
+ { "EBADMSG", EBADMSG },
+ { "EBUSY", EBUSY },
+
+ { "ECANCELED", ECANCELED },
+ { "ECHILD", ECHILD },
+ { "ECONNABORTED", ECONNABORTED },
+ { "ECONNREFUSED", ECONNREFUSED },
+ { "ECONNRESET", ECONNRESET },
+
+ { "EDEADLK", EDEADLK },
+ { "EDESTADDRREQ", EDESTADDRREQ },
+ { "EDOM", EDOM },
+ { "EDQUOT", EDQUOT },
+
+ { "EEXIST", EEXIST },
+
+ { "EFAULT", EFAULT },
+ { "EFBIG", EFBIG },
+
+ { "EHOSTUNREACH", EHOSTUNREACH },
+
+ { "EIDRM", EIDRM },
+ { "EILSEQ", EILSEQ },
+ { "EINPROGRESS", EINPROGRESS },
+ { "EINTR", EINTR },
+ { "EINVAL", EINVAL },
+ { "EIO", EIO },
+ { "EISCONN", EISCONN },
+ { "EISDIR", EISDIR },
+
+ { "ELOOP", ELOOP },
+
+ { "EMFILE", EMFILE },
+ { "EMLINK", EMLINK },
+ { "EMSGSIZE", EMSGSIZE },
+ { "EMULTIHOP", EMULTIHOP },
+
+ { "ENAMETOOLONG", ENAMETOOLONG },
+ { "ENETDOWN", ENETDOWN },
+ { "ENETRESET", ENETRESET },
+ { "ENETUNREACH", ENETUNREACH },
+ { "ENFILE", ENFILE },
+ { "ENOBUFS", ENOBUFS },
+ { "ENODEV", ENODEV },
+ { "ENOENT", ENOENT },
+ { "ENOEXEC", ENOEXEC },
+ { "ENOLCK", ENOLCK },
+ { "ENOLINK", ENOLINK },
+ { "ENOMEM", ENOMEM },
+ { "ENOMSG", ENOMSG },
+ { "ENOPROTOOPT", ENOPROTOOPT },
+ { "ENOSPC", ENOSPC },
+ { "ENOSYS", ENOSYS },
+ { "ENOTCONN", ENOTCONN },
+ { "ENOTDIR", ENOTDIR },
+ { "ENOTEMPTY", ENOTEMPTY },
+ { "ENOTSOCK", ENOTSOCK },
+ { "ENOTSUP", ENOTSUP },
+ { "ENOTTY", ENOTTY },
+ { "ENXIO", ENXIO },
+
+ { "EOPNOTSUPP", EOPNOTSUPP },
+ { "EOVERFLOW", EOVERFLOW },
+
+ { "EPERM", EPERM },
+ { "EPIPE", EPIPE },
+ { "EPROTO", EPROTO },
+ { "EPROTONOSUPPORT", EPROTONOSUPPORT },
+ { "EPROTOTYPE", EPROTOTYPE },
+
+ { "ERANGE", ERANGE },
+ { "EROFS", EROFS },
+
+ { "ESPIPE", ESPIPE },
+ { "ESRCH", ESRCH },
+ { "ESTALE", ESTALE },
+
+ { "ETIMEDOUT", ETIMEDOUT },
+ { "ETXTBSY", ETXTBSY },
+
+ { "EWOULDBLOCK", EWOULDBLOCK },
+
+ { "EXDEV", EXDEV },
+};
+
+static void dump(void)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(err_codes); i++) {
+ printf("%s %d\n", err_codes[i].label, err_codes[i].code);
+ }
+}
+
+static void match_label(const char *str)
+{
+ int code = -1;
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(err_codes); i++) {
+ if (strcasecmp(err_codes[i].label, str) == 0) {
+ code = err_codes[i].code;
+ break;
+ }
+ }
+
+ printf("%d\n", code);
+}
+
+static void match_code(int code)
+{
+ const char *label = "UNKNOWN";
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(err_codes); i++) {
+ if (err_codes[i].code == code) {
+ label = err_codes[i].label;
+ break;
+ }
+ }
+
+ printf("%s\n", label);
+}
+
+int main(int argc, const char **argv)
+{
+ long int code;
+ char *endptr;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s dump|<errcode>\n", argv[0]);
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "dump") == 0) {
+ dump();
+ } else {
+ code = strtol(argv[1], &endptr, 0);
+ if (*endptr == '\0') {
+ match_code(code);
+ } else {
+ match_label(argv[1]);
+ }
+ }
+
+ exit(0);
+}
diff --git a/ctdb/tests/src/event_script_test.c b/ctdb/tests/src/event_script_test.c
new file mode 100644
index 0000000..a78e400
--- /dev/null
+++ b/ctdb/tests/src/event_script_test.c
@@ -0,0 +1,120 @@
+/*
+ Low level event script handling tests
+
+ Copyright (C) Martin Schwenke 2018
+
+ Based on run_event_test.c:
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <popt.h>
+#include <talloc.h>
+
+#include <assert.h>
+
+#include "common/event_script.c"
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "Usage: %s list <scriptdir>\n",
+ prog);
+ fprintf(stderr,
+ " %s chmod enable <scriptdir> <scriptname>\n",
+ prog);
+ fprintf(stderr,
+ " %s chmod diable <scriptdir> <scriptname>\n",
+ prog);
+}
+
+static void do_list(TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct event_script_list *script_list = NULL;
+ unsigned int i;
+ int ret;
+
+ if (argc != 3) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ ret = event_script_get_list(mem_ctx, argv[2], &script_list);
+ if (ret != 0) {
+ printf("Script list %s failed with result=%d\n", argv[2], ret);
+ return;
+ }
+
+ if (script_list == NULL || script_list->num_scripts == 0) {
+ printf("No scripts found\n");
+ return;
+ }
+
+ for (i=0; i < script_list->num_scripts; i++) {
+ struct event_script *s = script_list->script[i];
+ printf("%s\n", s->name);
+ }
+}
+
+static void do_chmod(TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ bool enable)
+{
+ int ret;
+
+ if (argc != 4) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ ret = event_script_chmod(argv[2], argv[3], enable);
+
+ printf("Script %s %s %s completed with result=%d\n",
+ argv[1], argv[2], argv[3], ret);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+
+ if (argc < 3) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "talloc_new() failed\n");
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "list") == 0) {
+ do_list(mem_ctx, argc, argv);
+ } else if (strcmp(argv[1], "enable") == 0) {
+ do_chmod(mem_ctx, argc, argv, true);
+ } else if (strcmp(argv[1], "disable") == 0) {
+ do_chmod(mem_ctx, argc, argv, false);
+ } else {
+ fprintf(stderr, "Invalid command %s\n", argv[2]);
+ usage(argv[0]);
+ }
+
+ talloc_free(mem_ctx);
+ exit(0);
+}
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;
+}
diff --git a/ctdb/tests/src/fetch_loop.c b/ctdb/tests/src/fetch_loop.c
new file mode 100644
index 0000000..0e1d9da
--- /dev/null
+++ b/ctdb/tests/src/fetch_loop.c
@@ -0,0 +1,288 @@
+/*
+ simple ctdb benchmark
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+#define TESTDB "fetch_loop.tdb"
+#define TESTKEY "testkey"
+
+struct fetch_loop_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ int num_nodes;
+ int timelimit;
+ TDB_DATA key;
+ int locks_count;
+};
+
+static void fetch_loop_start(struct tevent_req *subreq);
+static void fetch_loop_next(struct tevent_req *subreq);
+static void fetch_loop_each_second(struct tevent_req *subreq);
+static void fetch_loop_finish(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *ctdb_db,
+ int num_nodes, int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct fetch_loop_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->ctdb_db = ctdb_db;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->key.dptr = discard_const(TESTKEY);
+ state->key.dsize = strlen(TESTKEY);
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_loop_start, req);
+
+ return req;
+}
+
+static void fetch_loop_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+
+ if (ctdb_client_pnn(state->client) == 0) {
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_each_second, req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_finish, req);
+}
+
+static void fetch_loop_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ struct ctdb_record_handle *h;
+ TDB_DATA data;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (data.dsize == sizeof(uint32_t)) {
+ state->locks_count = *(uint32_t *)data.dptr;
+ }
+ TALLOC_FREE(data.dptr);
+
+ state->locks_count += 1;
+ data.dsize = sizeof(uint32_t);
+ data.dptr = (uint8_t *)&state->locks_count;
+
+ ret = ctdb_store_record(h, data);
+ if (ret != 0) {
+ talloc_free(h);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(h);
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+}
+
+static void fetch_loop_each_second(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("Locks:%d\r", state->locks_count);
+ fflush(stdout);
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_each_second, req);
+}
+
+static void fetch_loop_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("Locks:%d\n", state->locks_count);
+
+ tevent_req_done(req);
+}
+
+static bool fetch_loop_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("fetch_loop", DEBUG_STDERR);
+
+ status = process_options_basic(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0,
+ &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to DB %s\n", TESTDB);
+ exit(1);
+ }
+
+ req = fetch_loop_send(mem_ctx, ev, client, ctdb_db,
+ opts->num_nodes, opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = fetch_loop_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "fetch loop test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/fetch_loop_key.c b/ctdb/tests/src/fetch_loop_key.c
new file mode 100644
index 0000000..3f41ca7
--- /dev/null
+++ b/ctdb/tests/src/fetch_loop_key.c
@@ -0,0 +1,217 @@
+/*
+ simple ctdb benchmark
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+struct fetch_loop_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ int timelimit;
+ TDB_DATA key;
+ int locks_count;
+};
+
+static void fetch_loop_next(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *ctdb_db,
+ const char *keystr,
+ int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct fetch_loop_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->ctdb_db = ctdb_db;
+ state->timelimit = timelimit;
+ state->key.dptr = discard_const(keystr);
+ state->key.dsize = strlen(keystr);
+
+ subreq = ctdb_fetch_lock_send(state, ev, client, ctdb_db,
+ state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+
+ return req;
+}
+
+static void fetch_loop_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ struct ctdb_record_handle *h;
+ TDB_DATA data;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (data.dsize == sizeof(uint32_t)) {
+ state->locks_count = *(uint32_t *)data.dptr;
+ }
+ TALLOC_FREE(data.dptr);
+
+ state->locks_count += 1;
+ data.dsize = sizeof(uint32_t);
+ data.dptr = (uint8_t *)&state->locks_count;
+
+ ret = ctdb_store_record(h, data);
+ if (ret != 0) {
+ talloc_free(h);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(h);
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+}
+
+static bool fetch_loop_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;
+}
+
+static struct tevent_req *global_req;
+
+static void alarm_handler(int sig)
+{
+ struct fetch_loop_state *state = tevent_req_data(
+ global_req, struct fetch_loop_state);
+ static int time_passed = 0;
+
+ time_passed += 1;
+
+ printf("Locks:%d\n", state->locks_count);
+ fflush(stdout);
+
+ if (time_passed >= state->timelimit) {
+ tevent_req_done(global_req);
+ }
+
+ alarm(1);
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ int ret;
+ bool status;
+
+ setup_logging("fetch_loop_key", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, %s\n",
+ strerror(ret));
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0,
+ &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname);
+ exit(1);
+ }
+
+ global_req = fetch_loop_send(mem_ctx, ev, client, ctdb_db,
+ opts->keystr, opts->timelimit);
+ if (global_req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ signal(SIGALRM, alarm_handler);
+ alarm(1);
+
+ tevent_req_poll(global_req, ev);
+
+ status = fetch_loop_recv(global_req, &ret);
+ if (! status) {
+ fprintf(stderr, "fetch loop test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/fetch_readonly.c b/ctdb/tests/src/fetch_readonly.c
new file mode 100644
index 0000000..ff126bd
--- /dev/null
+++ b/ctdb/tests/src/fetch_readonly.c
@@ -0,0 +1,166 @@
+/*
+ Fetch a single record using readonly
+
+ 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 "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+
+struct fetch_readonly_state {
+ struct tevent_context *ev;
+};
+
+static void fetch_readonly_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_readonly_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *db,
+ const char *keystr,
+ int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct fetch_readonly_state *state;
+ TDB_DATA key;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_readonly_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+
+ key.dptr = (uint8_t *)discard_const(keystr);
+ key.dsize = strlen(keystr);
+
+ subreq = ctdb_fetch_lock_send(state, ev, client, db, key, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_readonly_done, req);
+
+ return req;
+}
+
+static void fetch_readonly_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_readonly_state *state = tevent_req_data(
+ req, struct fetch_readonly_state);
+ struct ctdb_record_handle *h;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(h);
+ tevent_req_done(req);
+}
+
+static bool fetch_readonly_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("fetch_readonly", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, %s\n",
+ strerror(ret));
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0,
+ &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname);
+ exit(1);
+ }
+
+ req = fetch_readonly_send(mem_ctx, ev, client, ctdb_db,
+ opts->keystr, opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = fetch_readonly_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "fetch readonly loop test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/fetch_readonly_loop.c b/ctdb/tests/src/fetch_readonly_loop.c
new file mode 100644
index 0000000..08cf476
--- /dev/null
+++ b/ctdb/tests/src/fetch_readonly_loop.c
@@ -0,0 +1,272 @@
+/*
+ simple ctdb benchmark
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+#define TESTDB "fetch_readonly_loop.tdb"
+#define TESTKEY "testkey"
+
+struct fetch_loop_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ int num_nodes;
+ int timelimit;
+ TDB_DATA key;
+ int locks_count;
+};
+
+static void fetch_loop_start(struct tevent_req *subreq);
+static void fetch_loop_next(struct tevent_req *subreq);
+static void fetch_loop_each_second(struct tevent_req *subreq);
+static void fetch_loop_finish(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *ctdb_db,
+ int num_nodes, int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct fetch_loop_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->ctdb_db = ctdb_db;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->key.dptr = discard_const(TESTKEY);
+ state->key.dsize = strlen(TESTKEY);
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_loop_start, req);
+
+ return req;
+}
+
+static void fetch_loop_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+
+ if (ctdb_client_pnn(state->client) == 0) {
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_each_second, req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_finish, req);
+}
+
+static void fetch_loop_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ struct ctdb_record_handle *h;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->locks_count += 1;
+ talloc_free(h);
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_next, req);
+}
+
+static void fetch_loop_each_second(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("Locks:%d\r", state->locks_count);
+ fflush(stdout);
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_loop_each_second, req);
+}
+
+static void fetch_loop_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_loop_state *state = tevent_req_data(
+ req, struct fetch_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("Locks:%d\n", state->locks_count);
+
+ tevent_req_done(req);
+}
+
+static bool fetch_loop_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("fetch_readonly_loop", DEBUG_STDERR);
+
+ status = process_options_basic(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0,
+ &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to DB %s\n", TESTDB);
+ exit(1);
+ }
+
+ req = fetch_loop_send(mem_ctx, ev, client, ctdb_db,
+ opts->num_nodes, opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = fetch_loop_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "fetch readonly loop test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/fetch_ring.c b/ctdb/tests/src/fetch_ring.c
new file mode 100644
index 0000000..f1786ef
--- /dev/null
+++ b/ctdb/tests/src/fetch_ring.c
@@ -0,0 +1,398 @@
+/*
+ simple ctdb benchmark
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/time.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+#define MSG_ID_FETCH 0
+
+static uint32_t next_node(struct ctdb_client_context *client, uint32_t num_nodes)
+{
+ return (ctdb_client_pnn(client) + 1) % num_nodes;
+}
+
+struct fetch_ring_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ uint32_t num_nodes;
+ int timelimit;
+ int interactive;
+ TDB_DATA key;
+ int msg_count;
+ struct timeval start_time;
+};
+
+static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data);
+static void fetch_ring_wait(struct tevent_req *subreq);
+static void fetch_ring_start(struct tevent_req *subreq);
+static void fetch_ring_update(struct tevent_req *subreq);
+static void fetch_ring_msg_sent(struct tevent_req *subreq);
+static void fetch_ring_finish(struct tevent_req *subreq);
+static void fetch_ring_final_read(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_ring_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *ctdb_db,
+ const char *keystr,
+ uint32_t num_nodes,
+ int timelimit,
+ int interactive)
+{
+ struct tevent_req *req, *subreq;
+ struct fetch_ring_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_ring_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->ctdb_db = ctdb_db;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->interactive = interactive;
+ state->key.dptr = discard_const(keystr);
+ state->key.dsize = strlen(keystr);
+
+ subreq = ctdb_client_set_message_handler_send(
+ state, ev, client, MSG_ID_FETCH,
+ fetch_ring_msg_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_ring_wait, req);
+
+ return req;
+}
+
+static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ struct tevent_req *subreq;
+
+ state->msg_count += 1;
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_update, req);
+}
+
+static void fetch_ring_wait(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ bool status;
+ int ret;
+
+ status = ctdb_client_set_message_handler_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_start, req);
+}
+
+static void fetch_ring_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->start_time = tevent_timeval_current();
+
+ if (ctdb_client_pnn(state->client) == state->num_nodes-1) {
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key,
+ false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_update, req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_finish, req);
+
+}
+
+static void fetch_ring_update(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ struct ctdb_record_handle *h;
+ struct ctdb_req_message msg;
+ TDB_DATA data;
+ uint32_t pnn;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (data.dsize > 1000) {
+ TALLOC_FREE(data.dptr);
+ data.dsize = 0;
+ }
+
+ if (data.dsize == 0) {
+ data.dptr = (uint8_t *)talloc_asprintf(state, "Test data\n");
+ if (tevent_req_nomem(data.dptr, req)) {
+ return;
+ }
+ }
+
+ data.dptr = (uint8_t *)talloc_asprintf_append(
+ (char *)data.dptr,
+ "msg_count=%d on node %d\n",
+ state->msg_count,
+ ctdb_client_pnn(state->client));
+ if (tevent_req_nomem(data.dptr, req)) {
+ return;
+ }
+
+ data.dsize = strlen((const char *)data.dptr) + 1;
+
+ ret = ctdb_store_record(h, data);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(data.dptr);
+ talloc_free(h);
+
+ msg.srvid = MSG_ID_FETCH;
+ msg.data.data = tdb_null;
+
+ pnn = next_node(state->client, state->num_nodes);
+
+ subreq = ctdb_client_message_send(state, state->ev, state->client,
+ pnn, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_msg_sent, req);
+}
+
+static void fetch_ring_msg_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int ret;
+
+ status = ctdb_client_message_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static void fetch_ring_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ bool status;
+ double t;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ t = timeval_elapsed(&state->start_time);
+
+ printf("Fetch[%u]: %.2f msgs/sec\n", ctdb_client_pnn(state->client),
+ state->msg_count / t);
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->ctdb_db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fetch_ring_final_read, req);
+}
+
+static void fetch_ring_final_read(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_ring_state *state = tevent_req_data(
+ req, struct fetch_ring_state);
+ struct ctdb_record_handle *h;
+ TDB_DATA data;
+ int err;
+
+ h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &err);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ if (state->interactive == 1) {
+ printf("DATA:\n%s\n", (char *)data.dptr);
+ }
+ talloc_free(data.dptr);
+ talloc_free(h);
+
+ tevent_req_done(req);
+}
+
+static bool fetch_ring_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("fetch_ring", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev,
+ client,
+ tevent_timeval_zero(),
+ opts->dbname,
+ 0,
+ &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname);
+ exit(1);
+ }
+
+ req = fetch_ring_send(mem_ctx,
+ ev,
+ client,
+ ctdb_db,
+ opts->keystr,
+ opts->num_nodes,
+ opts->timelimit,
+ opts->interactive);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = fetch_ring_recv(req, NULL);
+ if (! status) {
+ fprintf(stderr, "fetch ring test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/g_lock_loop.c b/ctdb/tests/src/g_lock_loop.c
new file mode 100644
index 0000000..3b84241
--- /dev/null
+++ b/ctdb/tests/src/g_lock_loop.c
@@ -0,0 +1,270 @@
+/*
+ simple ctdb benchmark for g_lock operations
+
+ 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 "lib/util/tevent_unix.h"
+#include "lib/util/debug.h"
+
+#include "protocol/protocol_api.h"
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+#define TESTKEY "testkey"
+
+struct glock_loop_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *db;
+ int num_nodes;
+ int timelimit;
+ uint32_t pnn;
+ uint32_t counter;
+ struct ctdb_server_id sid;
+ const char *key;
+};
+
+static void glock_loop_start(struct tevent_req *subreq);
+static void glock_loop_locked(struct tevent_req *subreq);
+static void glock_loop_unlocked(struct tevent_req *subreq);
+static void glock_loop_finish(struct tevent_req *subreq);
+
+static struct tevent_req *glock_loop_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *db,
+ int num_nodes, int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct glock_loop_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct glock_loop_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->db = db;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->pnn = ctdb_client_pnn(client);
+ state->counter = 0;
+ state->sid = ctdb_client_get_server_id(client, 1);
+ state->key = TESTKEY;
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, glock_loop_start, req);
+
+ return req;
+}
+
+static void glock_loop_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct glock_loop_state *state = tevent_req_data(
+ req, struct glock_loop_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_g_lock_lock_send(state, state->ev, state->client,
+ state->db, state->key, &state->sid,
+ false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, glock_loop_locked, req);
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, glock_loop_finish, req);
+}
+
+static void glock_loop_locked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct glock_loop_state *state = tevent_req_data(
+ req, struct glock_loop_state);
+ int ret;
+ bool status;
+
+ status = ctdb_g_lock_lock_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ fprintf(stderr, "g_lock_lock failed\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->counter += 1;
+
+ subreq = ctdb_g_lock_unlock_send(state, state->ev, state->client,
+ state->db, state->key, state->sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, glock_loop_unlocked, req);
+}
+
+static void glock_loop_unlocked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct glock_loop_state *state = tevent_req_data(
+ req, struct glock_loop_state);
+ int ret;
+ bool status;
+
+ status = ctdb_g_lock_unlock_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ fprintf(stderr, "g_lock_unlock failed\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_g_lock_lock_send(state, state->ev, state->client,
+ state->db, state->key, &state->sid,
+ false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, glock_loop_locked, req);
+}
+
+static void glock_loop_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct glock_loop_state *state = tevent_req_data(
+ req, struct glock_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("PNN:%u counter:%u\n", state->pnn, state->counter);
+
+ tevent_req_done(req);
+}
+
+static bool glock_loop_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("glock_loop", DEBUG_STDERR);
+
+ status = process_options_basic(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), "g_lock.tdb",
+ 0, &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to g_lock.tdb\n");
+ exit(1);
+ }
+
+ req = glock_loop_send(mem_ctx, ev, client, ctdb_db,
+ opts->num_nodes, opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = glock_loop_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "g_lock loop test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/hash_count_test.c b/ctdb/tests/src/hash_count_test.c
new file mode 100644
index 0000000..6ddde08
--- /dev/null
+++ b/ctdb/tests/src/hash_count_test.c
@@ -0,0 +1,132 @@
+/*
+ hash_count tests
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <assert.h>
+
+#include "common/db_hash.c"
+#include "common/hash_count.c"
+
+#define KEY "this_is_a_test_key"
+
+static void test1_handler(TDB_DATA key, uint64_t counter, void *private_data)
+{
+ int *count = (int *)private_data;
+
+ assert(key.dsize == strlen(KEY));
+ assert(strcmp((char *)key.dptr, KEY) == 0);
+ assert(counter > 0);
+
+ (*count) += 1;
+}
+
+static void do_test1(void)
+{
+ struct hash_count_context *hc = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct timeval interval = {1, 0};
+ TDB_DATA key;
+ int count = 0;
+ int ret, i;
+
+ key.dptr = (uint8_t *)discard_const(KEY);
+ key.dsize = strlen(KEY);
+
+ ret = hash_count_increment(hc, key);
+ assert(ret == EINVAL);
+
+ ret = hash_count_init(mem_ctx, interval, NULL, NULL, &hc);
+ assert(ret == EINVAL);
+
+ ret = hash_count_init(mem_ctx, interval, test1_handler, &count, &hc);
+ assert(ret == 0);
+ assert(hc != NULL);
+
+ for (i=0; i<10; i++) {
+ ret = hash_count_increment(hc, key);
+ assert(ret == 0);
+ assert(count == i+1);
+ }
+
+ talloc_free(hc);
+ ret = talloc_get_size(mem_ctx);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+static void test2_handler(TDB_DATA key, uint64_t counter, void *private_data)
+{
+ uint64_t *count = (uint64_t *)private_data;
+
+ *count = counter;
+}
+
+static void do_test2(void)
+{
+ struct hash_count_context *hc;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct timeval interval = {1, 0};
+ TDB_DATA key;
+ uint64_t count = 0;
+ int ret;
+
+ key.dptr = (uint8_t *)discard_const(KEY);
+ key.dsize = strlen(KEY);
+
+ ret = hash_count_init(mem_ctx, interval, test2_handler, &count, &hc);
+ assert(ret == 0);
+
+ ret = hash_count_increment(hc, key);
+ assert(ret == 0);
+ assert(count == 1);
+
+ hash_count_expire(hc, &ret);
+ assert(ret == 0);
+
+ ret = hash_count_increment(hc, key);
+ assert(ret == 0);
+ assert(count == 2);
+
+ sleep(2);
+
+ ret = hash_count_increment(hc, key);
+ assert(ret == 0);
+ assert(count == 1);
+
+ sleep(2);
+
+ hash_count_expire(hc, &ret);
+ assert(ret == 1);
+
+ talloc_free(hc);
+ ret = talloc_get_size(mem_ctx);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+int main(void)
+{
+ do_test1();
+ do_test2();
+
+ return 0;
+}
diff --git a/ctdb/tests/src/ipalloc_read_known_ips.c b/ctdb/tests/src/ipalloc_read_known_ips.c
new file mode 100644
index 0000000..33d0f94
--- /dev/null
+++ b/ctdb/tests/src/ipalloc_read_known_ips.c
@@ -0,0 +1,179 @@
+/*
+ Tests support for CTDB IP allocation
+
+ Copyright (C) Martin Schwenke 2011
+
+ 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 <talloc.h>
+
+#include "lib/util/debug.h"
+
+#include "protocol/protocol.h"
+#include "protocol/protocol_util.h"
+#include "common/logging.h"
+
+#include "ipalloc_read_known_ips.h"
+
+static bool add_ip(TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list *l,
+ ctdb_sock_addr *addr,
+ uint32_t pnn)
+{
+
+ l->ip = talloc_realloc(mem_ctx, l->ip,
+ struct ctdb_public_ip, l->num + 1);
+ if (l->ip == NULL) {
+ D_ERR(__location__ " out of memory\n");
+ return false;
+ }
+
+ l->ip[l->num].addr = *addr;
+ l->ip[l->num].pnn = pnn;
+ l->num++;
+
+ return true;
+}
+
+/* Format of each line is "IP CURRENT_PNN [ALLOWED_PNN,...]".
+ * If multi is true then ALLOWED_PNNs are not allowed. */
+static bool read_ctdb_public_ip_info_node(bool multi,
+ int numnodes,
+ struct ctdb_public_ip_list **k,
+ struct ctdb_public_ip_list *known)
+{
+ char line[1024];
+ ctdb_sock_addr addr;
+ char *t, *tok;
+ int pnn, n;
+
+ /* Known public IPs */
+ *k = talloc_zero(known, struct ctdb_public_ip_list);
+ if (*k == NULL) {
+ goto fail;
+ }
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int ret;
+
+ /* Get rid of pesky newline */
+ if ((t = strchr(line, '\n')) != NULL) {
+ *t = '\0';
+ }
+
+ /* Exit on an empty line */
+ if (line[0] == '\0') {
+ break;
+ }
+
+ /* Get the IP address */
+ tok = strtok(line, " \t");
+ if (tok == NULL) {
+ D_WARNING("WARNING, bad line ignored :%s\n", line);
+ continue;
+ }
+
+ ret = ctdb_sock_addr_from_string(tok, &addr, false);
+ if (ret != 0) {
+ D_ERR("ERROR, bad address :%s\n", tok);
+ continue;
+ }
+
+ /* Get the PNN */
+ pnn = -1;
+ tok = strtok(NULL, " \t");
+ if (tok != NULL) {
+ pnn = (int) strtol(tok, (char **) NULL, 10);
+ }
+
+ if (! add_ip(*k, *k, &addr, pnn)) {
+ goto fail;
+ }
+
+ tok = strtok(NULL, " \t#");
+ if (tok == NULL) {
+ if (! multi) {
+ for (n = 0; n < numnodes; n++) {
+ if (! add_ip(known, &known[n],
+ &addr, pnn)) {
+ goto fail;
+ }
+ }
+ }
+ continue;
+ }
+
+ /* Handle allowed nodes for addr */
+ if (multi) {
+ D_ERR("ERROR, bad token\n");
+ goto fail;
+ }
+ t = strtok(tok, ",");
+ while (t != NULL) {
+ n = (int) strtol(t, (char **) NULL, 10);
+ if (! add_ip(known, &known[n], &addr, pnn)) {
+ goto fail;
+ }
+ t = strtok(NULL, ",");
+ }
+ }
+
+ return true;
+
+fail:
+ TALLOC_FREE(*k);
+ return false;
+}
+
+struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx,
+ int numnodes,
+ bool multi)
+{
+ int n;
+ struct ctdb_public_ip_list *k;
+ struct ctdb_public_ip_list *known;
+
+ known = talloc_zero_array(ctx, struct ctdb_public_ip_list,
+ numnodes);
+ if (known == NULL) {
+ D_ERR(__location__ " out of memory\n");
+ goto fail;
+ }
+
+ if (multi) {
+ for (n = 0; n < numnodes; n++) {
+ if (! read_ctdb_public_ip_info_node(multi, numnodes,
+ &k, known)) {
+ goto fail;
+ }
+
+ known[n] = *k;
+ }
+ } else {
+ if (! read_ctdb_public_ip_info_node(multi, numnodes,
+ &k, known)) {
+ goto fail;
+ }
+ }
+
+ return known;
+
+fail:
+ talloc_free(known);
+ return NULL;
+}
diff --git a/ctdb/tests/src/ipalloc_read_known_ips.h b/ctdb/tests/src/ipalloc_read_known_ips.h
new file mode 100644
index 0000000..aa6d154
--- /dev/null
+++ b/ctdb/tests/src/ipalloc_read_known_ips.h
@@ -0,0 +1,32 @@
+/*
+ Tests support for CTDB IP allocation
+
+ Copyright (C) Martin Schwenke 2011
+
+ 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/>.
+*/
+
+#ifndef __IPALLOC_READ_KNOWN_IPS_H__
+#define __IPALLOC_READ_KNOWN_IPS_H__
+
+#include <stdbool.h>
+#include <talloc.h>
+
+#include "protocol/protocol.h"
+
+struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx,
+ int numnodes,
+ bool multi);
+
+#endif /* __IPALLOC_READ_KNOWN_IPS_H__ */
diff --git a/ctdb/tests/src/line_test.c b/ctdb/tests/src/line_test.c
new file mode 100644
index 0000000..806d883
--- /dev/null
+++ b/ctdb/tests/src/line_test.c
@@ -0,0 +1,102 @@
+/*
+ Test code for line based I/O over fds
+
+ Copyright (C) Amitay Isaacs 2018
+
+ 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/filesys.h"
+
+#include <talloc.h>
+#include <assert.h>
+
+#include "common/line.c"
+
+static int line_print(char *line, void *private_data)
+{
+ printf("%s\n", line);
+ fflush(stdout);
+
+ return 0;
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ size_t hint = 32;
+ pid_t pid;
+ int ret, lines = 0;
+ int pipefd[2];
+
+ if (argc < 2 || argc > 3) {
+ fprintf(stderr, "Usage: %s <filename> [<hint>]\n", argv[0]);
+ exit(1);
+ }
+
+ if (argc == 3) {
+ long value;
+
+ value = atol(argv[2]);
+ assert(value > 0);
+ hint = value;
+ }
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ char buffer[16];
+ ssize_t n, n2;
+ int fd;
+
+ close(pipefd[0]);
+
+ fd = open(argv[1], O_RDONLY);
+ assert(fd != -1);
+
+ while (1) {
+ n = read(fd, buffer, sizeof(buffer));
+ assert(n >= 0 && (size_t)n <= sizeof(buffer));
+
+ if (n == 0) {
+ break;
+ }
+
+ n2 = write(pipefd[1], buffer, n);
+ assert(n2 == n);
+ }
+
+ close(pipefd[1]);
+ close(fd);
+
+ exit(0);
+ }
+
+ close(pipefd[1]);
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ret = line_read(pipefd[0], hint, NULL, line_print, NULL, &lines);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+
+ return lines;
+}
diff --git a/ctdb/tests/src/lock_tdb.c b/ctdb/tests/src/lock_tdb.c
new file mode 100644
index 0000000..c37f846
--- /dev/null
+++ b/ctdb/tests/src/lock_tdb.c
@@ -0,0 +1,60 @@
+/*
+ Lock a tdb and sleep
+
+ Copyright (C) Amitay Isaacs 2012
+
+ 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/filesys.h"
+
+#include <tdb.h>
+
+const char *tdb_file;
+TDB_CONTEXT *tdb;
+
+static void signal_handler(int signum)
+{
+ tdb_close(tdb);
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ printf("Usage: %s <tdb file>\n", argv[0]);
+ exit(1);
+ }
+
+ tdb_file = argv[1];
+
+ tdb = tdb_open(tdb_file, 0, 0, O_RDWR, 0);
+ if (tdb == NULL) {
+ fprintf(stderr, "Failed to open TDB file %s\n", tdb_file);
+ exit(1);
+ }
+
+ signal(SIGINT, signal_handler);
+
+ if (tdb_lockall(tdb) != 0) {
+ fprintf(stderr, "Failed to lock database %s\n", tdb_file);
+ tdb_close(tdb);
+ exit(1);
+ }
+
+ sleep(999999);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/message_ring.c b/ctdb/tests/src/message_ring.c
new file mode 100644
index 0000000..d1fcee4
--- /dev/null
+++ b/ctdb/tests/src/message_ring.c
@@ -0,0 +1,369 @@
+/*
+ simple ctdb benchmark - send messages in a ring around cluster
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+
+#include "lib/util/debug.h"
+#include "lib/util/time.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+#define MSG_ID_BENCH 0
+
+struct message_ring_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ int num_nodes;
+ int timelimit;
+ int interactive;
+ int msg_count;
+ int msg_plus, msg_minus;
+ struct timeval start_time;
+};
+
+static void message_ring_wait(struct tevent_req *subreq);
+static void message_ring_start(struct tevent_req *subreq);
+static void message_ring_each_second(struct tevent_req *subreq);
+static void message_ring_msg_sent(struct tevent_req *subreq);
+static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data);
+static void message_ring_finish(struct tevent_req *subreq);
+
+static struct tevent_req *message_ring_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ int num_nodes, int timelimit,
+ int interactive)
+{
+ struct tevent_req *req, *subreq;
+ struct message_ring_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct message_ring_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->interactive = interactive;
+
+ subreq = ctdb_client_set_message_handler_send(
+ state, state->ev, state->client,
+ MSG_ID_BENCH,
+ message_ring_msg_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, message_ring_wait, req);
+
+ return req;
+}
+
+static void message_ring_wait(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct message_ring_state *state = tevent_req_data(
+ req, struct message_ring_state);
+ bool status;
+ int ret;
+
+ status = ctdb_client_set_message_handler_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_start, req);
+}
+
+static void message_ring_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct message_ring_state *state = tevent_req_data(
+ req, struct message_ring_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->start_time = tevent_timeval_current();
+
+ if (ctdb_client_pnn(state->client) == 0) {
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_each_second, req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_finish, req);
+}
+
+static uint32_t next_node(struct ctdb_client_context *client,
+ int num_nodes, int incr)
+{
+ return (ctdb_client_pnn(client) + num_nodes + incr) % num_nodes;
+}
+
+static void message_ring_each_second(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct message_ring_state *state = tevent_req_data(
+ req, struct message_ring_state);
+ struct ctdb_req_message msg;
+ uint32_t pnn;
+ int incr;
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ pnn = ctdb_client_pnn(state->client);
+ if (pnn == 0 && state->interactive == 1) {
+ double t;
+
+ t = timeval_elapsed(&state->start_time);
+ printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n",
+ pnn, state->msg_count / t,
+ state->msg_plus, state->msg_minus);
+ fflush(stdout);
+ }
+
+ if (state->msg_plus == 0) {
+ incr = 1;
+
+ msg.srvid = 0;
+ msg.data.data.dptr = (uint8_t *)&incr;
+ msg.data.data.dsize = sizeof(incr);
+
+ pnn = next_node(state->client, state->num_nodes, incr);
+
+ subreq = ctdb_client_message_send(state, state->ev,
+ state->client, pnn, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_msg_sent, req);
+ }
+
+ if (state->msg_minus == 0) {
+ incr = -1;
+
+ msg.srvid = 0;
+ msg.data.data.dptr = (uint8_t *)&incr;
+ msg.data.data.dsize = sizeof(incr);
+
+ pnn = next_node(state->client, state->num_nodes, incr);
+
+ subreq = ctdb_client_message_send(state, state->ev,
+ state->client, pnn, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_msg_sent, req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_each_second, req);
+}
+
+static void message_ring_msg_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int ret;
+
+ status = ctdb_client_message_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ }
+}
+
+static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct message_ring_state *state = tevent_req_data(
+ req, struct message_ring_state);
+ struct ctdb_req_message msg;
+ struct tevent_req *subreq;
+ int incr;
+ uint32_t pnn;
+
+ if (srvid != MSG_ID_BENCH) {
+ return;
+ }
+
+ if (data.dsize != sizeof(int)) {
+ return;
+ }
+ incr = *(int *)data.dptr;
+
+ state->msg_count += 1;
+ if (incr == 1) {
+ state->msg_plus += 1;
+ } else {
+ state->msg_minus += 1;
+ }
+
+ pnn = next_node(state->client, state->num_nodes, incr);
+
+ msg.srvid = srvid;
+ msg.data.data = data;
+
+ subreq = ctdb_client_message_send(state, state->ev, state->client,
+ pnn, &msg);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, message_ring_msg_sent, req);
+}
+
+static void message_ring_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct message_ring_state *state = tevent_req_data(
+ req, struct message_ring_state);
+ bool status;
+ double t;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ t = timeval_elapsed(&state->start_time);
+
+ printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n",
+ ctdb_client_pnn(state->client), state->msg_count / t,
+ state->msg_plus, state->msg_minus);
+
+ tevent_req_done(req);
+}
+
+static bool message_ring_recv(struct tevent_req *req)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("message_ring", DEBUG_STDERR);
+
+ status = process_options_basic(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Failed to wait for recovery\n");
+ exit(1);
+ }
+
+ req = message_ring_send(mem_ctx, ev, client,
+ opts->num_nodes, opts->timelimit,
+ opts->interactive);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = message_ring_recv(req);
+ if (! status) {
+ fprintf(stderr, "message ring test failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/pidfile_test.c b/ctdb/tests/src/pidfile_test.c
new file mode 100644
index 0000000..592fc2b
--- /dev/null
+++ b/ctdb/tests/src/pidfile_test.c
@@ -0,0 +1,242 @@
+/*
+ pidfile tests
+
+ 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/wait.h"
+
+#include <assert.h>
+
+#include "common/pidfile.c"
+
+
+/* create pid file, check pid file exists, check pid and remove pid file */
+static void test1(const char *pidfile)
+{
+ struct pidfile_context *pid_ctx;
+ int ret;
+ struct stat st;
+ FILE *fp;
+ pid_t pid;
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret == 0);
+ assert(pid_ctx != NULL);
+
+ ret = stat(pidfile, &st);
+ assert(ret == 0);
+ assert(S_ISREG(st.st_mode));
+
+ fp = fopen(pidfile, "r");
+ assert(fp != NULL);
+ ret = fscanf(fp, "%d", &pid);
+ assert(ret == 1);
+ assert(pid == getpid());
+ fclose(fp);
+
+ TALLOC_FREE(pid_ctx);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+}
+
+/* create pid file in two processes */
+static void test2(const char *pidfile)
+{
+ struct pidfile_context *pid_ctx;
+ pid_t pid, pid2;
+ int fd[2];
+ int ret;
+ size_t nread;
+ FILE *fp;
+ struct stat st;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ ssize_t nwritten;
+
+ close(fd[0]);
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret == 0);
+ assert(pid_ctx != NULL);
+
+ nwritten = write(fd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ sleep(10);
+
+ TALLOC_FREE(pid_ctx);
+
+ nwritten = write(fd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ exit(1);
+ }
+
+ close(fd[1]);
+
+ nread = read(fd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+ assert(ret == 0);
+
+ fp = fopen(pidfile, "r");
+ assert(fp != NULL);
+ ret = fscanf(fp, "%d", &pid2);
+ assert(ret == 1);
+ assert(pid == pid2);
+ fclose(fp);
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret != 0);
+
+ nread = read(fd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+ assert(ret == 0);
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret == 0);
+ assert(pid_ctx != NULL);
+
+ TALLOC_FREE(pid_ctx);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+}
+
+/* create pid file, fork, try to remove pid file in separate process */
+static void test3(const char *pidfile)
+{
+ struct pidfile_context *pid_ctx;
+ pid_t pid;
+ int fd[2];
+ int ret;
+ size_t nread;
+ struct stat st;
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret == 0);
+ assert(pid_ctx != NULL);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ ssize_t nwritten;
+
+ close(fd[0]);
+
+ TALLOC_FREE(pid_ctx);
+
+ nwritten = write(fd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ exit(1);
+ }
+
+ close(fd[1]);
+
+ nread = read(fd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+
+ ret = stat(pidfile, &st);
+ assert(ret == 0);
+
+ TALLOC_FREE(pid_ctx);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+}
+
+/* create pid file, kill process, overwrite pid file in different process */
+static void test4(const char *pidfile)
+{
+ struct pidfile_context *pid_ctx;
+ pid_t pid, pid2;
+ int fd[2];
+ int ret;
+ size_t nread;
+ struct stat st;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ ssize_t nwritten;
+
+ close(fd[0]);
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+
+ nwritten = write(fd[1], &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ sleep(99);
+ exit(1);
+ }
+
+ close(fd[1]);
+
+ nread = read(fd[0], &ret, sizeof(ret));
+ assert(nread == sizeof(ret));
+ assert(ret == 0);
+
+ ret = stat(pidfile, &st);
+ assert(ret == 0);
+
+ ret = kill(pid, SIGKILL);
+ assert(ret == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+
+ ret = pidfile_context_create(NULL, pidfile, &pid_ctx);
+ assert(ret == 0);
+ assert(pid_ctx != NULL);
+
+ ret = stat(pidfile, &st);
+ assert(ret == 0);
+
+ TALLOC_FREE(pid_ctx);
+}
+
+int main(int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <pidfile>\n", argv[0]);
+ exit(1);
+ }
+
+ test1(argv[1]);
+ test2(argv[1]);
+ test3(argv[1]);
+ test4(argv[1]);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/pkt_read_test.c b/ctdb/tests/src/pkt_read_test.c
new file mode 100644
index 0000000..a3ebe0a
--- /dev/null
+++ b/ctdb/tests/src/pkt_read_test.c
@@ -0,0 +1,249 @@
+/*
+ packet read tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "lib/util/blocking.h"
+
+#include "common/pkt_read.c"
+
+static void writer(int fd)
+{
+ uint8_t buf[1024*1024];
+ size_t buflen;
+ size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 };
+ int i, j;
+ int ret;
+
+ for (i=0; i<1024*1024; i++) {
+ buf[i] = i%256;
+ }
+
+ for (i=0; i<1000; i++) {
+ for (j=0; j<4; j++) {
+ buflen = pkt_size[j];
+ memcpy(buf, &buflen, sizeof(buflen));
+
+ ret = write(fd, buf, buflen);
+ if (ret < 0) {
+ printf("write error: %s\n", strerror(errno));
+ assert(ret > 0);
+ }
+ }
+ }
+
+ close(fd);
+}
+
+struct reader_state {
+ struct tevent_context *ev;
+ int fd;
+ uint8_t *buf;
+ size_t buflen;
+ struct tevent_req *subreq;
+};
+
+static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data);
+static void reader_done(struct tevent_req *subreq);
+
+static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd, uint8_t *buf,
+ size_t buflen)
+{
+ struct tevent_req *req, *subreq;
+ struct reader_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct reader_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+ state->buf = buf;
+ state->buflen = buflen;
+
+ subreq = pkt_read_send(state, state->ev, state->fd, 4,
+ state->buf, state->buflen, reader_more, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, ev);
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, reader_done, req);
+ return req;
+}
+
+static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data)
+{
+ uint32_t pkt_len;
+
+ if (buflen < sizeof(pkt_len)) {
+ return sizeof(pkt_len) - buflen;
+ }
+
+ pkt_len = *(uint32_t *)buf;
+ return pkt_len - buflen;
+}
+
+static void reader_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+ ssize_t nread;
+ uint8_t *buf;
+ bool free_buf;
+ int err;
+
+ nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (nread == -1) {
+ if (err == EPIPE) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, err);
+ }
+ return;
+ }
+
+ if (free_buf) {
+ talloc_free(buf);
+ }
+
+ subreq = pkt_read_send(state, state->ev, state->fd, 4,
+ state->buf, state->buflen, reader_more, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, reader_done, req);
+}
+
+static void reader_recv(struct tevent_req *req, int *perr)
+{
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+ int err = 0;
+
+ if (state->subreq != NULL) {
+ *perr = -1;
+ }
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ *perr = err;
+ return;
+ }
+
+ *perr = 0;
+}
+
+static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+
+ assert(state->subreq != NULL);
+ pkt_read_handler(ev, fde, flags, state->subreq);
+}
+
+static void reader(int fd, bool fixed)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_fd *fde;
+ struct tevent_req *req;
+ int err;
+ uint8_t *buf = NULL;
+ size_t buflen = 0;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ if (fixed) {
+ buflen = 1024;
+ buf = talloc_size(mem_ctx, buflen);
+ assert(buf != NULL);
+ }
+
+ req = reader_send(mem_ctx, ev, fd, buf, buflen);
+ assert(req != NULL);
+
+ fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ,
+ reader_handler, req);
+ assert(fde != NULL);
+
+ tevent_req_poll(req, ev);
+
+ reader_recv(req, &err);
+ assert(err == 0);
+
+ close(fd);
+
+ talloc_free(mem_ctx);
+}
+
+static void reader_test(bool fixed)
+{
+ int fd[2];
+ int ret;
+ pid_t pid;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Child process */
+ close(fd[0]);
+ writer(fd[1]);
+ exit(0);
+ }
+
+ close(fd[1]);
+ ret = set_blocking(fd[0], false);
+ if (ret == -1) {
+ exit(1);
+ }
+
+ reader(fd[0], fixed);
+}
+
+int main(void)
+{
+ reader_test(true);
+ reader_test(false);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/pkt_write_test.c b/ctdb/tests/src/pkt_write_test.c
new file mode 100644
index 0000000..dae92a5
--- /dev/null
+++ b/ctdb/tests/src/pkt_write_test.c
@@ -0,0 +1,359 @@
+/*
+ packet write tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "lib/util/blocking.h"
+
+#include "common/pkt_read.c"
+#include "common/pkt_write.c"
+
+struct writer_state {
+ struct tevent_context *ev;
+ int fd;
+ uint8_t *buf;
+ size_t buflen;
+ int count;
+ struct tevent_req *subreq;
+};
+
+static void writer_next(struct tevent_req *subreq);
+
+static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd, uint8_t *buf, size_t buflen)
+{
+ struct tevent_req *req, *subreq;
+ struct writer_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct writer_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+ state->buf = buf;
+ state->buflen = buflen;
+ state->count = 0;
+
+ subreq = pkt_write_send(state, state->ev, state->fd,
+ state->buf, state->buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, writer_next, req);
+ return req;
+}
+
+static void writer_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct writer_state *state = tevent_req_data(
+ req, struct writer_state);
+ ssize_t nwritten;
+ int err = 0;
+
+ nwritten = pkt_write_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (nwritten == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ if ((size_t)nwritten != state->buflen) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ state->count++;
+ if (state->count >= 1000) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = pkt_write_send(state, state->ev, state->fd,
+ state->buf, state->buflen);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, writer_next, req);
+}
+
+static void writer_recv(struct tevent_req *req, int *perr)
+{
+ struct writer_state *state = tevent_req_data(
+ req, struct writer_state);
+ int err = 0;
+
+ if (state->subreq != NULL) {
+ *perr = -1;
+ return;
+ }
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ *perr = err;
+ return;
+ }
+
+ *perr = 0;
+}
+
+static void writer_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct writer_state *state = tevent_req_data(
+ req, struct writer_state);
+
+ assert(state->subreq != NULL);
+ pkt_write_handler(ev, fde, flags, state->subreq);
+}
+
+static void writer(int fd)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_fd *fde;
+ struct tevent_req *req;
+ uint8_t buf[1024*1024];
+ size_t buflen;
+ size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 };
+ int i, err;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ for (i=0; i<1024*1024; i++) {
+ buf[i] = i%256;
+ }
+
+ for (i=0; i<4; i++) {
+ buflen = pkt_size[i];
+ memcpy(buf, &buflen, sizeof(buflen));
+
+ req = writer_send(mem_ctx, ev, fd, buf, buflen);
+ assert(req != NULL);
+
+ fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_WRITE,
+ writer_handler, req);
+ assert(fde != NULL);
+
+ tevent_req_poll(req, ev);
+
+ writer_recv(req, &err);
+ assert(err == 0);
+
+ talloc_free(fde);
+ talloc_free(req);
+ }
+
+ close(fd);
+
+ talloc_free(mem_ctx);
+}
+
+struct reader_state {
+ struct tevent_context *ev;
+ int fd;
+ uint8_t buf[1024];
+ struct tevent_req *subreq;
+};
+
+static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data);
+static void reader_done(struct tevent_req *subreq);
+
+static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd)
+{
+ struct tevent_req *req, *subreq;
+ struct reader_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct reader_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->fd = fd;
+
+ subreq = pkt_read_send(state, state->ev, state->fd, 4,
+ state->buf, 1024, reader_more, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, ev);
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, reader_done, req);
+ return req;
+}
+
+static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data)
+{
+ uint32_t pkt_len;
+
+ if (buflen < sizeof(pkt_len)) {
+ return sizeof(pkt_len) - buflen;
+ }
+
+ pkt_len = *(uint32_t *)buf;
+ return pkt_len - buflen;
+}
+
+static void reader_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+ ssize_t nread;
+ uint8_t *buf;
+ bool free_buf;
+ int err = 0;
+
+ nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (nread == -1) {
+ if (err == EPIPE) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, err);
+ }
+ return;
+ }
+
+ if (free_buf) {
+ talloc_free(buf);
+ }
+
+ subreq = pkt_read_send(state, state->ev, state->fd, 4,
+ state->buf, 1024, reader_more, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, reader_done, req);
+}
+
+static void reader_recv(struct tevent_req *req, int *perr)
+{
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+ int err = 0;
+
+ if (state->subreq != NULL) {
+ *perr = -1;
+ }
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ *perr = err;
+ return;
+ }
+
+ *perr = 0;
+}
+
+static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct reader_state *state = tevent_req_data(
+ req, struct reader_state);
+
+ assert(state->subreq != NULL);
+ pkt_read_handler(ev, fde, flags, state->subreq);
+}
+
+static void reader(int fd)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_fd *fde;
+ struct tevent_req *req;
+ int err;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ req = reader_send(mem_ctx, ev, fd);
+ assert(req != NULL);
+
+ fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ,
+ reader_handler, req);
+ assert(fde != NULL);
+
+ tevent_req_poll(req, ev);
+
+ reader_recv(req, &err);
+ assert(err == 0);
+
+ close(fd);
+
+ talloc_free(mem_ctx);
+}
+
+int main(void)
+{
+ int fd[2];
+ int ret;
+ pid_t pid;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Child process */
+ close(fd[0]);
+ writer(fd[1]);
+ exit(0);
+ }
+
+ close(fd[1]);
+ ret = set_blocking(fd[0], false);
+ if (ret == -1) {
+ exit(1);
+ }
+
+ reader(fd[0]);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/porting_tests.c b/ctdb/tests/src/porting_tests.c
new file mode 100644
index 0000000..00618d2
--- /dev/null
+++ b/ctdb/tests/src/porting_tests.c
@@ -0,0 +1,262 @@
+/*
+ Test porting lib (common/system_*.c)
+
+ Copyright (C) Mathieu Parent 2013
+
+ 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/filesys.h"
+#include "system/network.h"
+
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <tdb.h>
+#include <assert.h>
+
+#include "lib/util/debug.h"
+#include "lib/util/blocking.h"
+
+#include "protocol/protocol.h"
+#include "common/system.h"
+#include "common/logging.h"
+
+
+static struct {
+ const char *socketname;
+ const char *debuglevel;
+ pid_t helper_pid;
+ int socket;
+} globals = {
+ .socketname = "/tmp/test.sock"
+};
+
+
+
+/*
+ Socket functions
+*/
+/*
+ create a unix domain socket and bind it
+ return a file descriptor open on the socket
+*/
+static int socket_server_create(void)
+{
+ struct sockaddr_un addr;
+ int ret;
+
+ globals.socket = socket(AF_UNIX, SOCK_STREAM, 0);
+ assert(globals.socket != -1);
+
+ set_close_on_exec(globals.socket);
+ //set_blocking(globals.socket, false);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1);
+
+ ret = bind(globals.socket, (struct sockaddr *)&addr, sizeof(addr));
+ assert(ret == 0);
+
+ ret = chown(globals.socketname, geteuid(), getegid());
+ assert(ret == 0);
+
+ ret = chmod(globals.socketname, 0700);
+ assert(ret == 0);
+
+ ret = listen(globals.socket, 100);
+ assert(ret == 0);
+
+ return 0;
+}
+
+static int socket_server_wait_peer(void)
+{
+ struct sockaddr_un addr;
+ socklen_t len;
+ int fd;
+
+ memset(&addr, 0, sizeof(addr));
+ len = sizeof(addr);
+ fd = accept(globals.socket, (struct sockaddr *)&addr, &len);
+ assert(fd != -1);
+
+ //set_blocking(fd, false);
+ set_close_on_exec(fd);
+ return fd;
+}
+
+static int socket_server_close(void)
+{
+ int ret;
+
+ ret = close(globals.socket);
+ assert(ret == 0);
+
+ ret = unlink(globals.socketname);
+ assert(ret == 0);
+
+ return 0;
+}
+
+static int socket_client_connect(void)
+{
+ struct sockaddr_un addr;
+ int client = 0;
+ int ret;
+
+ client = socket(AF_UNIX, SOCK_STREAM, 0);
+ assert(client != -1);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1);
+
+ ret = connect(client, (struct sockaddr *)&addr, sizeof(addr));
+ assert(ret == 0);
+
+ return client;
+}
+
+static int socket_client_close(int client)
+{
+ int ret;
+
+ ret = close(client);
+ assert(ret == 0);
+
+ return 0;
+}
+
+/*
+ forked program
+*/
+static int fork_helper(void)
+{
+ pid_t pid;
+ int client;
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) { // Child
+ pid = getppid();
+ client = socket_client_connect();
+ while (kill(pid, 0) == 0) {
+ sleep(1);
+ }
+ socket_client_close(client);
+ exit(0);
+ } else {
+ globals.helper_pid = pid;
+ }
+ return 0;
+}
+
+/*
+ tests
+*/
+static int test_ctdb_sys_check_iface_exists(void)
+{
+ bool test1, test2;
+
+ test1 = ctdb_sys_check_iface_exists("unlikely123xyz");
+ assert(!test1);
+
+ /* Linux and others */
+ test1 = ctdb_sys_check_iface_exists("lo");
+ /* FreeBSD */
+ test2 = ctdb_sys_check_iface_exists("lo0");
+ assert(test1 || test2);
+
+ return 0;
+}
+
+static int test_ctdb_get_peer_pid(void)
+{
+ int ret;
+ int fd;
+ pid_t peer_pid = 0;
+
+ fd = socket_server_wait_peer();
+
+ ret = ctdb_get_peer_pid(fd, &peer_pid);
+ assert(ret == 0 || ret == ENOSYS);
+
+ if (ret == 0) {
+ assert(peer_pid == globals.helper_pid);
+
+ kill(peer_pid, SIGTERM);
+ } else {
+ kill(globals.helper_pid, SIGTERM);
+ }
+
+ close(fd);
+ return 0;
+}
+
+/*
+ main program
+*/
+int main(int argc, const char *argv[])
+{
+ struct poptOption popt_options[] = {
+ POPT_AUTOHELP
+ { "socket", 0, POPT_ARG_STRING, &globals.socketname, 0, "local socket name", "filename" },
+ POPT_TABLEEND
+ };
+ int opt, ret;
+ const char **extra_argv;
+ int extra_argc = 0;
+ poptContext pc;
+
+ pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST);
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ fprintf(stderr, "Invalid option %s: %s\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ exit(1);
+ }
+ }
+
+ /* setup the remaining options for the main program to use */
+ extra_argv = poptGetArgs(pc);
+ if (extra_argv) {
+ extra_argv++;
+ while (extra_argv[extra_argc]) extra_argc++;
+ }
+
+ assert(globals.socketname != NULL);
+
+ ret = socket_server_create();
+ assert(ret == 0);
+
+ /* FIXME: Test tcp_checksum6, tcp_checksum */
+ /* FIXME: Test ctdb_sys_send_arp, ctdb_sys_send_tcp */
+ /* FIXME: Test ctdb_sys_{open,close}_capture_socket, ctdb_sys_read_tcp_packet */
+ test_ctdb_sys_check_iface_exists();
+
+ ret = fork_helper();
+ assert(ret == 0);
+ test_ctdb_get_peer_pid();
+
+ ret = socket_server_close();
+ assert(ret == 0);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_basic_test.c b/ctdb/tests/src/protocol_basic_test.c
new file mode 100644
index 0000000..7046718
--- /dev/null
+++ b/ctdb/tests/src/protocol_basic_test.c
@@ -0,0 +1,106 @@
+/*
+ protocol types tests
+
+ Copyright (C) Amitay Isaacs 2015-2017
+
+ 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 <assert.h>
+
+#include "protocol/protocol_basic.c"
+
+#include "tests/src/protocol_common_basic.h"
+
+PROTOCOL_TYPE1_TEST(uint8_t, ctdb_uint8);
+PROTOCOL_TYPE1_TEST(uint16_t, ctdb_uint16);
+PROTOCOL_TYPE1_TEST(int32_t, ctdb_int32);
+PROTOCOL_TYPE1_TEST(uint32_t, ctdb_uint32);
+PROTOCOL_TYPE1_TEST(uint64_t, ctdb_uint64);
+PROTOCOL_TYPE1_TEST(double, ctdb_double);
+PROTOCOL_TYPE1_TEST(bool, ctdb_bool);
+
+static void test_ctdb_chararray(void)
+{
+ size_t len = rand_int(1000) + 1;
+ char p1[len], p2[len];
+ size_t buflen, np = 0;
+ size_t i;
+ int ret;
+
+ for (i=0; i<len-1; i++) {
+ p1[i] = 'A' + rand_int(26);
+ }
+ p1[len-1] = '\0';
+ buflen = ctdb_chararray_len(p1, len);
+ assert(buflen < sizeof(BUFFER));
+ ctdb_chararray_push(p1, len, BUFFER, &np);
+ assert(np == buflen);
+ np = 0;
+ ret = ctdb_chararray_pull(BUFFER, buflen, p2, len, &np);
+ assert(ret == 0);
+ assert(np == buflen);
+ assert(strncmp(p1, p2, len) == 0);
+}
+
+PROTOCOL_TYPE2_TEST(const char *, ctdb_string);
+PROTOCOL_TYPE2_TEST(const char *, ctdb_stringn);
+
+PROTOCOL_TYPE1_TEST(pid_t, ctdb_pid);
+PROTOCOL_TYPE1_TEST(struct timeval, ctdb_timeval);
+
+static void test_ctdb_padding(void)
+{
+ int padding;
+ size_t buflen, np = 0;
+ int ret;
+
+ padding = rand_int(8);
+
+ buflen = ctdb_padding_len(padding);
+ assert(buflen < sizeof(BUFFER));
+ ctdb_padding_push(padding, BUFFER, &np);
+ assert(np == buflen);
+ np = 0;
+ ret = ctdb_padding_pull(BUFFER, buflen, padding, &np);
+ assert(ret == 0);
+ assert(np == buflen);
+}
+
+static void protocol_basic_test(void)
+{
+ TEST_FUNC(ctdb_uint8)();
+ TEST_FUNC(ctdb_uint16)();
+ TEST_FUNC(ctdb_int32)();
+ TEST_FUNC(ctdb_uint32)();
+ TEST_FUNC(ctdb_uint64)();
+ TEST_FUNC(ctdb_double)();
+ TEST_FUNC(ctdb_bool)();
+
+ test_ctdb_chararray();
+
+ TEST_FUNC(ctdb_string)();
+ TEST_FUNC(ctdb_stringn)();
+
+ TEST_FUNC(ctdb_pid)();
+ TEST_FUNC(ctdb_timeval)();
+
+ test_ctdb_padding();
+}
+
+int main(int argc, const char *argv[])
+{
+ protocol_test_iterate(argc, argv, protocol_basic_test);
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_common.c b/ctdb/tests/src/protocol_common.c
new file mode 100644
index 0000000..212c23c
--- /dev/null
+++ b/ctdb/tests/src/protocol_common.c
@@ -0,0 +1,1260 @@
+/*
+ protocol tests - common functions
+
+ Copyright (C) Amitay Isaacs 2015-2017
+
+ 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 <assert.h>
+
+#include "protocol/protocol_api.h"
+
+#include "tests/src/protocol_common_basic.h"
+#include "tests/src/protocol_common.h"
+
+void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p)
+{
+ p->dsize = rand_int(1024) + 1;
+ p->dptr = talloc_array(mem_ctx, uint8_t, p->dsize);
+ assert(p->dptr != NULL);
+ fill_buffer(p->dptr, p->dsize);
+}
+
+void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p)
+{
+ if (rand_int(5) == 0) {
+ p->dsize = 0;
+ p->dptr = NULL;
+ } else {
+ fill_tdb_data_nonnull(mem_ctx, p);
+ }
+}
+
+void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2)
+{
+ assert(p1->dsize == p2->dsize);
+ verify_buffer(p1->dptr, p2->dptr, p1->dsize);
+}
+
+void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p)
+{
+ fill_tdb_data(mem_ctx, p);
+}
+
+void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2)
+{
+ verify_tdb_data(p1, p2);
+}
+
+void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p)
+{
+ fill_tdb_data(mem_ctx, p);
+}
+
+void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2)
+{
+ verify_tdb_data(p1, p2);
+}
+
+void fill_ctdb_latency_counter(struct ctdb_latency_counter *p)
+{
+ p->num = rand32i();
+ p->min = rand_double();
+ p->max = rand_double();
+ p->total = rand_double();
+}
+
+void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1,
+ struct ctdb_latency_counter *p2)
+{
+ assert(p1->num == p2->num);
+ assert(p1->min == p2->min);
+ assert(p1->max == p2->max);
+ assert(p1->total == p2->total);
+}
+
+void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p)
+{
+ int i;
+
+ p->num_clients = rand32();
+ p->frozen = rand32();
+ p->recovering = rand32();
+ p->client_packets_sent = rand32();
+ p->client_packets_recv = rand32();
+ p->node_packets_sent = rand32();
+ p->node_packets_recv = rand32();
+ p->keepalive_packets_sent = rand32();
+ p->keepalive_packets_recv = rand32();
+
+ p->node.req_call = rand32();
+ p->node.reply_call = rand32();
+ p->node.req_dmaster = rand32();
+ p->node.reply_dmaster = rand32();
+ p->node.reply_error = rand32();
+ p->node.req_message = rand32();
+ p->node.req_control = rand32();
+ p->node.reply_control = rand32();
+
+ p->client.req_call = rand32();
+ p->client.req_message = rand32();
+ p->client.req_control = rand32();
+
+ p->timeouts.call = rand32();
+ p->timeouts.control = rand32();
+ p->timeouts.traverse = rand32();
+
+ fill_ctdb_latency_counter(&p->reclock.ctdbd);
+ fill_ctdb_latency_counter(&p->reclock.recd);
+
+ p->locks.num_calls = rand32();
+ p->locks.num_current = rand32();
+ p->locks.num_pending = rand32();
+ p->locks.num_failed = rand32();
+ fill_ctdb_latency_counter(&p->locks.latency);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ p->locks.buckets[i] = rand32();
+ }
+
+ p->total_calls = rand32();
+ p->pending_calls = rand32();
+ p->childwrite_calls = rand32();
+ p->pending_childwrite_calls = rand32();
+ p->memory_used = rand32();
+ p->__last_counter = rand32();
+ p->max_hop_count = rand32();
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ p->hop_count_bucket[i] = rand32();
+ }
+ fill_ctdb_latency_counter(&p->call_latency);
+ fill_ctdb_latency_counter(&p->childwrite_latency);
+ p->num_recoveries = rand32();
+ fill_ctdb_timeval(&p->statistics_start_time);
+ fill_ctdb_timeval(&p->statistics_current_time);
+ p->total_ro_delegations = rand32();
+ p->total_ro_revokes = rand32();
+}
+
+void verify_ctdb_statistics(struct ctdb_statistics *p1,
+ struct ctdb_statistics *p2)
+{
+ int i;
+
+ assert(p1->num_clients == p2->num_clients);
+ assert(p1->frozen == p2->frozen);
+ assert(p1->recovering == p2->recovering);
+ assert(p1->client_packets_sent == p2->client_packets_sent);
+ assert(p1->client_packets_recv == p2->client_packets_recv);
+ assert(p1->node_packets_sent == p2->node_packets_sent);
+ assert(p1->node_packets_recv == p2->node_packets_recv);
+ assert(p1->keepalive_packets_sent == p2->keepalive_packets_sent);
+ assert(p1->keepalive_packets_recv == p2->keepalive_packets_recv);
+
+ assert(p1->node.req_call == p2->node.req_call);
+ assert(p1->node.reply_call == p2->node.reply_call);
+ assert(p1->node.req_dmaster == p2->node.req_dmaster);
+ assert(p1->node.reply_dmaster == p2->node.reply_dmaster);
+ assert(p1->node.reply_error == p2->node.reply_error);
+ assert(p1->node.req_message == p2->node.req_message);
+ assert(p1->node.req_control == p2->node.req_control);
+ assert(p1->node.reply_control == p2->node.reply_control);
+
+ assert(p1->client.req_call == p2->client.req_call);
+ assert(p1->client.req_message == p2->client.req_message);
+ assert(p1->client.req_control == p2->client.req_control);
+
+ assert(p1->timeouts.call == p2->timeouts.call);
+ assert(p1->timeouts.control == p2->timeouts.control);
+ assert(p1->timeouts.traverse == p2->timeouts.traverse);
+
+ verify_ctdb_latency_counter(&p1->reclock.ctdbd, &p2->reclock.ctdbd);
+ verify_ctdb_latency_counter(&p1->reclock.recd, &p2->reclock.recd);
+
+ assert(p1->locks.num_calls == p2->locks.num_calls);
+ assert(p1->locks.num_current == p2->locks.num_current);
+ assert(p1->locks.num_pending == p2->locks.num_pending);
+ assert(p1->locks.num_failed == p2->locks.num_failed);
+ verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ assert(p1->locks.buckets[i] == p2->locks.buckets[i]);
+ }
+
+ assert(p1->total_calls == p2->total_calls);
+ assert(p1->pending_calls == p2->pending_calls);
+ assert(p1->childwrite_calls == p2->childwrite_calls);
+ assert(p1->pending_childwrite_calls == p2->pending_childwrite_calls);
+ assert(p1->memory_used == p2->memory_used);
+ assert(p1->__last_counter == p2->__last_counter);
+ assert(p1->max_hop_count == p2->max_hop_count);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]);
+ }
+ verify_ctdb_latency_counter(&p1->call_latency, &p2->call_latency);
+ verify_ctdb_latency_counter(&p1->childwrite_latency,
+ &p2->childwrite_latency);
+ assert(p1->num_recoveries == p2->num_recoveries);
+ verify_ctdb_timeval(&p1->statistics_start_time,
+ &p2->statistics_start_time);
+ verify_ctdb_timeval(&p1->statistics_current_time,
+ &p2->statistics_current_time);
+ assert(p1->total_ro_delegations == p2->total_ro_delegations);
+ assert(p1->total_ro_revokes == p2->total_ro_revokes);
+}
+
+void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p)
+{
+ unsigned int i;
+
+ p->generation = rand32();
+ p->size = rand_int(20);
+ if (p->size > 0) {
+ p->map = talloc_array(mem_ctx, uint32_t, p->size);
+ assert(p->map != NULL);
+
+ for (i=0; i<p->size; i++) {
+ p->map[i] = rand32();
+ }
+ } else {
+ p->map = NULL;
+ }
+}
+
+void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2)
+{
+ unsigned int i;
+
+ assert(p1->generation == p2->generation);
+ assert(p1->size == p2->size);
+ for (i=0; i<p1->size; i++) {
+ assert(p1->map[i] == p2->map[i]);
+ }
+}
+
+void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p)
+{
+ p->db_id = rand32();
+ p->flags = rand8();
+}
+
+void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->flags == p2->flags);
+}
+
+void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p)
+{
+ unsigned int i;
+
+ p->num = rand_int(40);
+ if (p->num > 0) {
+ p->dbs = talloc_zero_array(mem_ctx, struct ctdb_dbid, p->num);
+ assert(p->dbs != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_dbid(mem_ctx, &p->dbs[i]);
+ }
+ } else {
+ p->dbs = NULL;
+ }
+}
+
+void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2)
+{
+ unsigned int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_dbid(&p1->dbs[i], &p2->dbs[i]);
+ }
+}
+
+void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p)
+{
+ p->db_id = rand32();
+ p->lmaster = rand32();
+}
+
+void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->lmaster == p2->lmaster);
+}
+
+void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p)
+{
+ p->db_id = rand32();
+ p->lmaster = rand32();
+ p->srvid = rand64();
+}
+
+void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1,
+ struct ctdb_pulldb_ext *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->lmaster == p2->lmaster);
+ assert(p1->srvid == p2->srvid);
+}
+
+void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p)
+{
+ fill_ctdb_uint32(&p->db_id);
+ fill_ctdb_bool(&p->full_vacuum_run);
+}
+
+void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1,
+ struct ctdb_db_vacuum *p2)
+{
+ verify_ctdb_uint32(&p1->db_id, &p2->db_id);
+ verify_ctdb_bool(&p1->full_vacuum_run, &p2->full_vacuum_run);
+}
+
+void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p)
+{
+ fill_ctdb_uint32(&p->timeout);
+ fill_tdb_data(mem_ctx, &p->buf);
+}
+
+void verify_ctdb_echo_data(struct ctdb_echo_data *p1,
+ struct ctdb_echo_data *p2)
+{
+ verify_ctdb_uint32(&p1->timeout, &p2->timeout);
+ verify_tdb_data(&p1->buf, &p2->buf);
+}
+
+void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p)
+{
+ p->rsn = rand64();
+ p->dmaster = rand32();
+ p->reserved1 = rand32();
+ p->flags = rand32();
+}
+
+void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1,
+ struct ctdb_ltdb_header *p2)
+{
+ assert(p1->rsn == p2->rsn);
+ assert(p1->dmaster == p2->dmaster);
+ assert(p1->reserved1 == p2->reserved1);
+ assert(p1->flags == p2->flags);
+}
+
+void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p)
+{
+ p->reqid = rand32();
+ if (p->reqid % 5 == 0) {
+ p->header = talloc(mem_ctx, struct ctdb_ltdb_header);
+ assert(p->header != NULL);
+ fill_ctdb_ltdb_header(p->header);
+ } else {
+ p->header = NULL;
+ }
+ fill_tdb_data_nonnull(mem_ctx, &p->key);
+ fill_tdb_data(mem_ctx, &p->data);
+}
+
+void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2)
+{
+ struct ctdb_ltdb_header header;
+
+ assert(p1->reqid == p2->reqid);
+ if (p1->header != NULL) {
+ assert(ctdb_ltdb_header_extract(&p2->data, &header) == 0);
+ verify_ctdb_ltdb_header(p1->header, &header);
+ }
+ verify_tdb_data(&p1->key, &p2->key);
+ verify_tdb_data(&p1->data, &p2->data);
+}
+
+void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p)
+{
+ struct ctdb_rec_data rec;
+ int ret, i;
+ int count;
+
+ p->db_id = rand32();
+ p->count = 0;
+ p->buf = NULL;
+ p->buflen = 0;
+
+ count = rand_int(100);
+ if (count > 0) {
+ for (i=0; i<count; i++) {
+ fill_ctdb_rec_data(mem_ctx, &rec);
+ ret = ctdb_rec_buffer_add(mem_ctx, p, rec.reqid,
+ rec.header,
+ rec.key, rec.data);
+ assert(ret == 0);
+ }
+ }
+}
+
+void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1,
+ struct ctdb_rec_buffer *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->count == p2->count);
+ assert(p1->buflen == p2->buflen);
+ verify_buffer(p1->buf, p2->buf, p1->buflen);
+}
+
+void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start *p)
+{
+ p->db_id = rand32();
+ p->reqid = rand32();
+ p->srvid = rand64();
+}
+
+void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1,
+ struct ctdb_traverse_start *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->reqid == p2->reqid);
+ assert(p1->srvid == p2->srvid);
+}
+
+void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all *p)
+{
+ p->db_id = rand32();
+ p->reqid = rand32();
+ p->pnn = rand32();
+ p->client_reqid = rand32();
+ p->srvid = rand64();
+}
+
+void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1,
+ struct ctdb_traverse_all *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->reqid == p2->reqid);
+ assert(p1->pnn == p2->pnn);
+ assert(p1->client_reqid == p2->client_reqid);
+ assert(p1->srvid == p2->srvid);
+}
+
+void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start_ext *p)
+{
+ p->db_id = rand32();
+ p->reqid = rand32();
+ p->srvid = rand64();
+ p->withemptyrecords = rand_int(2);
+}
+
+void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1,
+ struct ctdb_traverse_start_ext *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->reqid == p2->reqid);
+ assert(p1->srvid == p2->srvid);
+ assert(p1->withemptyrecords == p2->withemptyrecords);
+}
+
+void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all_ext *p)
+{
+ p->db_id = rand32();
+ p->reqid = rand32();
+ p->pnn = rand32();
+ p->client_reqid = rand32();
+ p->srvid = rand64();
+ p->withemptyrecords = rand_int(2);
+}
+
+void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1,
+ struct ctdb_traverse_all_ext *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->reqid == p2->reqid);
+ assert(p1->pnn == p2->pnn);
+ assert(p1->client_reqid == p2->client_reqid);
+ assert(p1->srvid == p2->srvid);
+ assert(p1->withemptyrecords == p2->withemptyrecords);
+}
+
+void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p)
+{
+ if (rand_int(2) == 0) {
+ p->ip.sin_family = AF_INET;
+ p->ip.sin_port = rand_int(65535);
+ fill_buffer(&p->ip.sin_addr, sizeof(struct in_addr));
+ } else {
+ p->ip6.sin6_family = AF_INET6;
+ p->ip6.sin6_port = rand_int(65535);
+ fill_buffer(&p->ip6.sin6_addr, sizeof(struct in6_addr));
+ }
+}
+
+void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2)
+{
+ assert(p1->sa.sa_family == p2->sa.sa_family);
+ if (p1->sa.sa_family == AF_INET) {
+ assert(p1->ip.sin_port == p2->ip.sin_port);
+ verify_buffer(&p1->ip.sin_addr, &p2->ip.sin_addr,
+ sizeof(struct in_addr));
+ } else {
+ assert(p1->ip6.sin6_port == p2->ip6.sin6_port);
+ verify_buffer(&p1->ip6.sin6_addr, &p2->ip6.sin6_addr,
+ sizeof(struct in6_addr));
+ }
+}
+
+void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p)
+{
+ fill_ctdb_sock_addr(mem_ctx, &p->src);
+ fill_ctdb_sock_addr(mem_ctx, &p->dst);
+}
+
+void verify_ctdb_connection(struct ctdb_connection *p1,
+ struct ctdb_connection *p2)
+{
+ verify_ctdb_sock_addr(&p1->src, &p2->src);
+ verify_ctdb_sock_addr(&p1->dst, &p2->dst);
+}
+
+void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_connection_list *p)
+{
+ uint32_t i;
+
+ p->num = rand_int(1000);
+ if (p->num > 0) {
+ p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num);
+ assert(p->conn != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_connection(mem_ctx, &p->conn[i]);
+ }
+ } else {
+ p->conn = NULL;
+ }
+}
+
+void verify_ctdb_connection_list(struct ctdb_connection_list *p1,
+ struct ctdb_connection_list *p2)
+{
+ uint32_t i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_connection(&p1->conn[i], &p2->conn[i]);
+ }
+}
+
+void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p)
+{
+ fill_ctdb_string(mem_ctx, &p->name);
+ p->value = rand32();
+}
+
+void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2)
+{
+ verify_ctdb_string(&p1->name, &p2->name);
+ assert(p1->value == p2->value);
+}
+
+void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx,
+ struct ctdb_node_flag_change *p)
+{
+ p->pnn = rand32();
+ p->new_flags = rand32();
+ p->old_flags = rand32();
+}
+
+void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1,
+ struct ctdb_node_flag_change *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ assert(p1->new_flags == p2->new_flags);
+ assert(p1->old_flags == p2->old_flags);
+}
+
+void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p)
+{
+ int i;
+
+ p->count = rand_int(100) + 1;
+ p->var = talloc_array(mem_ctx, const char *, p->count);
+ for (i=0; i<p->count; i++) {
+ fill_ctdb_string(p->var, &p->var[i]);
+ }
+}
+
+void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2)
+{
+ int i;
+
+ assert(p1->count == p2->count);
+ for (i=0; i<p1->count; i++) {
+ verify_ctdb_string(&p1->var[i], &p2->var[i]);
+ }
+}
+
+void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p)
+{
+ p->max_redirect_count = rand32();
+ p->seqnum_interval = rand32();
+ p->control_timeout = rand32();
+ p->traverse_timeout = rand32();
+ p->keepalive_interval = rand32();
+ p->keepalive_limit = rand32();
+ p->recover_timeout = rand32();
+ p->recover_interval = rand32();
+ p->election_timeout = rand32();
+ p->takeover_timeout = rand32();
+ p->monitor_interval = rand32();
+ p->tickle_update_interval = rand32();
+ p->script_timeout = rand32();
+ p->monitor_timeout_count = rand32();
+ p->script_unhealthy_on_timeout = rand32();
+ p->recovery_grace_period = rand32();
+ p->recovery_ban_period = rand32();
+ p->database_hash_size = rand32();
+ p->database_max_dead = rand32();
+ p->rerecovery_timeout = rand32();
+ p->enable_bans = rand32();
+ p->deterministic_public_ips = rand32();
+ p->reclock_ping_period = rand32();
+ p->no_ip_failback = rand32();
+ p->disable_ip_failover = rand32();
+ p->verbose_memory_names = rand32();
+ p->recd_ping_timeout = rand32();
+ p->recd_ping_failcount = rand32();
+ p->log_latency_ms = rand32();
+ p->reclock_latency_ms = rand32();
+ p->recovery_drop_all_ips = rand32();
+ p->verify_recovery_lock = rand32();
+ p->vacuum_interval = rand32();
+ p->vacuum_max_run_time = rand32();
+ p->repack_limit = rand32();
+ p->vacuum_limit = rand32();
+ p->max_queue_depth_drop_msg = rand32();
+ p->allow_unhealthy_db_read = rand32();
+ p->stat_history_interval = rand32();
+ p->deferred_attach_timeout = rand32();
+ p->vacuum_fast_path_count = rand32();
+ p->lcp2_public_ip_assignment = rand32();
+ p->allow_client_db_attach = rand32();
+ p->recover_pdb_by_seqnum = rand32();
+ p->deferred_rebalance_on_node_add = rand32();
+ p->fetch_collapse = rand32();
+ p->hopcount_make_sticky = rand32();
+ p->sticky_duration = rand32();
+ p->sticky_pindown = rand32();
+ p->no_ip_takeover = rand32();
+ p->db_record_count_warn = rand32();
+ p->db_record_size_warn = rand32();
+ p->db_size_warn = rand32();
+ p->pulldb_preallocation_size = rand32();
+ p->no_ip_host_on_all_disabled = rand32();
+ p->samba3_hack = rand32();
+ p->mutex_enabled = rand32();
+ p->lock_processes_per_db = rand32();
+ p->rec_buffer_size_limit = rand32();
+ p->queue_buffer_size = rand32();
+ p->ip_alloc_algorithm = rand32();
+ p->allow_mixed_versions = rand32();
+}
+
+void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1,
+ struct ctdb_tunable_list *p2)
+{
+ assert(p1->max_redirect_count == p2->max_redirect_count);
+ assert(p1->seqnum_interval == p2->seqnum_interval);
+ assert(p1->control_timeout == p2->control_timeout);
+ assert(p1->traverse_timeout == p2->traverse_timeout);
+ assert(p1->keepalive_interval == p2->keepalive_interval);
+ assert(p1->keepalive_limit == p2->keepalive_limit);
+ assert(p1->recover_timeout == p2->recover_timeout);
+ assert(p1->recover_interval == p2->recover_interval);
+ assert(p1->election_timeout == p2->election_timeout);
+ assert(p1->takeover_timeout == p2->takeover_timeout);
+ assert(p1->monitor_interval == p2->monitor_interval);
+ assert(p1->tickle_update_interval == p2->tickle_update_interval);
+ assert(p1->script_timeout == p2->script_timeout);
+ assert(p1->monitor_timeout_count == p2->monitor_timeout_count);
+ assert(p1->script_unhealthy_on_timeout == p2->script_unhealthy_on_timeout);
+ assert(p1->recovery_grace_period == p2->recovery_grace_period);
+ assert(p1->recovery_ban_period == p2->recovery_ban_period);
+ assert(p1->database_hash_size == p2->database_hash_size);
+ assert(p1->database_max_dead == p2->database_max_dead);
+ assert(p1->rerecovery_timeout == p2->rerecovery_timeout);
+ assert(p1->enable_bans == p2->enable_bans);
+ assert(p1->deterministic_public_ips == p2->deterministic_public_ips);
+ assert(p1->reclock_ping_period == p2->reclock_ping_period);
+ assert(p1->no_ip_failback == p2->no_ip_failback);
+ assert(p1->disable_ip_failover == p2->disable_ip_failover);
+ assert(p1->verbose_memory_names == p2->verbose_memory_names);
+ assert(p1->recd_ping_timeout == p2->recd_ping_timeout);
+ assert(p1->recd_ping_failcount == p2->recd_ping_failcount);
+ assert(p1->log_latency_ms == p2->log_latency_ms);
+ assert(p1->reclock_latency_ms == p2->reclock_latency_ms);
+ assert(p1->recovery_drop_all_ips == p2->recovery_drop_all_ips);
+ assert(p1->verify_recovery_lock == p2->verify_recovery_lock);
+ assert(p1->vacuum_interval == p2->vacuum_interval);
+ assert(p1->vacuum_max_run_time == p2->vacuum_max_run_time);
+ assert(p1->repack_limit == p2->repack_limit);
+ assert(p1->vacuum_limit == p2->vacuum_limit);
+ assert(p1->max_queue_depth_drop_msg == p2->max_queue_depth_drop_msg);
+ assert(p1->allow_unhealthy_db_read == p2->allow_unhealthy_db_read);
+ assert(p1->stat_history_interval == p2->stat_history_interval);
+ assert(p1->deferred_attach_timeout == p2->deferred_attach_timeout);
+ assert(p1->vacuum_fast_path_count == p2->vacuum_fast_path_count);
+ assert(p1->lcp2_public_ip_assignment == p2->lcp2_public_ip_assignment);
+ assert(p1->allow_client_db_attach == p2->allow_client_db_attach);
+ assert(p1->recover_pdb_by_seqnum == p2->recover_pdb_by_seqnum);
+ assert(p1->deferred_rebalance_on_node_add == p2->deferred_rebalance_on_node_add);
+ assert(p1->fetch_collapse == p2->fetch_collapse);
+ assert(p1->hopcount_make_sticky == p2->hopcount_make_sticky);
+ assert(p1->sticky_duration == p2->sticky_duration);
+ assert(p1->sticky_pindown == p2->sticky_pindown);
+ assert(p1->no_ip_takeover == p2->no_ip_takeover);
+ assert(p1->db_record_count_warn == p2->db_record_count_warn);
+ assert(p1->db_record_size_warn == p2->db_record_size_warn);
+ assert(p1->db_size_warn == p2->db_size_warn);
+ assert(p1->pulldb_preallocation_size == p2->pulldb_preallocation_size);
+ assert(p1->no_ip_host_on_all_disabled == p2->no_ip_host_on_all_disabled);
+ assert(p1->samba3_hack == p2->samba3_hack);
+ assert(p1->mutex_enabled == p2->mutex_enabled);
+ assert(p1->lock_processes_per_db == p2->lock_processes_per_db);
+ assert(p1->rec_buffer_size_limit == p2->rec_buffer_size_limit);
+ assert(p1->queue_buffer_size == p2->queue_buffer_size);
+ assert(p1->ip_alloc_algorithm == p2->ip_alloc_algorithm);
+ assert(p1->allow_mixed_versions == p2->allow_mixed_versions);
+}
+
+void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p)
+{
+ unsigned int i;
+
+ fill_ctdb_sock_addr(mem_ctx, &p->addr);
+ p->num = rand_int(1000);
+ if (p->num > 0) {
+ p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num);
+ assert(p->conn != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_connection(mem_ctx, &p->conn[i]);
+ }
+ } else {
+ p->conn = NULL;
+ }
+}
+
+void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1,
+ struct ctdb_tickle_list *p2)
+{
+ unsigned int i;
+
+ verify_ctdb_sock_addr(&p1->addr, &p2->addr);
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_connection(&p1->conn[i], &p2->conn[i]);
+ }
+}
+
+void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p)
+{
+ fill_ctdb_sock_addr(mem_ctx, &p->addr);
+ p->mask = rand_int(33);
+ if (rand_int(2) == 0) {
+ p->iface = NULL;
+ } else {
+ fill_ctdb_string(mem_ctx, &p->iface);
+ }
+}
+
+void verify_ctdb_addr_info(struct ctdb_addr_info *p1,
+ struct ctdb_addr_info *p2)
+{
+ verify_ctdb_sock_addr(&p1->addr, &p2->addr);
+ assert(p1->mask == p2->mask);
+ verify_ctdb_string(&p1->iface, &p2->iface);
+}
+
+void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p)
+{
+ p->db_id = rand32();
+ p->tid = rand32();
+}
+
+void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ assert(p1->tid == p2->tid);
+}
+
+void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p)
+{
+ fill_ctdb_timeval(&p->current_time);
+ fill_ctdb_timeval(&p->ctdbd_start_time);
+ fill_ctdb_timeval(&p->last_recovery_started);
+ fill_ctdb_timeval(&p->last_recovery_finished);
+}
+
+void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2)
+{
+ verify_ctdb_timeval(&p1->current_time, &p2->current_time);
+ verify_ctdb_timeval(&p1->ctdbd_start_time, &p2->ctdbd_start_time);
+ verify_ctdb_timeval(&p1->last_recovery_started,
+ &p2->last_recovery_started);
+ verify_ctdb_timeval(&p1->last_recovery_finished,
+ &p2->last_recovery_finished);
+}
+
+void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p)
+{
+ p->pnn = rand32();
+ fill_ctdb_sock_addr(mem_ctx, &p->addr);
+}
+
+void verify_ctdb_public_ip(struct ctdb_public_ip *p1,
+ struct ctdb_public_ip *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ verify_ctdb_sock_addr(&p1->addr, &p2->addr);
+}
+
+void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list *p)
+{
+ unsigned int i;
+
+ p->num = rand_int(32);
+ if (p->num > 0) {
+ p->ip = talloc_array(mem_ctx, struct ctdb_public_ip, p->num);
+ assert(p->ip != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_public_ip(mem_ctx, &p->ip[i]);
+ }
+ } else {
+ p->ip = NULL;
+ }
+}
+
+void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1,
+ struct ctdb_public_ip_list *p2)
+{
+ unsigned int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_public_ip(&p1->ip[i], &p2->ip[i]);
+ }
+}
+
+void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx,
+ struct ctdb_node_and_flags *p)
+{
+ p->pnn = rand32();
+ p->flags = rand32();
+ fill_ctdb_sock_addr(mem_ctx, &p->addr);
+}
+
+void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1,
+ struct ctdb_node_and_flags *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ assert(p1->flags == p2->flags);
+ verify_ctdb_sock_addr(&p1->addr, &p2->addr);
+}
+
+void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p)
+{
+ unsigned int i;
+
+ p->num = rand_int(32);
+ if (p->num > 0) {
+ p->node = talloc_array(mem_ctx, struct ctdb_node_and_flags,
+ p->num);
+ assert(p->node != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_node_and_flags(mem_ctx, &p->node[i]);
+ }
+ } else {
+ p->node = NULL;
+ }
+}
+
+void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2)
+{
+ unsigned int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_node_and_flags(&p1->node[i], &p2->node[i]);
+ }
+}
+
+void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p)
+{
+ fill_string(p->name, MAX_SCRIPT_NAME+1);
+ fill_ctdb_timeval(&p->start);
+ fill_ctdb_timeval(&p->finished);
+ p->status = rand32i();
+ fill_string(p->output, MAX_SCRIPT_OUTPUT+1);
+}
+
+void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2)
+{
+ verify_string(p1->name, p2->name);
+ verify_ctdb_timeval(&p1->start, &p2->start);
+ verify_ctdb_timeval(&p1->finished, &p2->finished);
+ assert(p1->status == p2->status);
+ verify_string(p1->output, p2->output);
+}
+
+void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p)
+{
+ unsigned int i;
+
+ p->num_scripts = rand_int(32);
+ if (p->num_scripts > 0) {
+ p->script = talloc_zero_array(mem_ctx, struct ctdb_script,
+ p->num_scripts);
+ assert(p->script != NULL);
+ for (i=0; i<p->num_scripts; i++) {
+ fill_ctdb_script(mem_ctx, &p->script[i]);
+ }
+ } else {
+ p->script = NULL;
+ }
+}
+
+void verify_ctdb_script_list(struct ctdb_script_list *p1,
+ struct ctdb_script_list *p2)
+{
+ unsigned int i;
+
+ assert(p1->num_scripts == p2->num_scripts);
+ for (i=0; i<p1->num_scripts; i++) {
+ verify_ctdb_script(&p1->script[i], &p2->script[i]);
+ }
+}
+
+void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p)
+{
+ p->pnn = rand32();
+ p->time = rand32();
+}
+
+void verify_ctdb_ban_state(struct ctdb_ban_state *p1,
+ struct ctdb_ban_state *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ assert(p1->time == p2->time);
+}
+
+void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p)
+{
+ p->srvid = rand64();
+ fill_tdb_data(mem_ctx, &p->data);
+}
+
+void verify_ctdb_notify_data(struct ctdb_notify_data *p1,
+ struct ctdb_notify_data *p2)
+{
+ assert(p1->srvid == p2->srvid);
+ verify_tdb_data(&p1->data, &p2->data);
+}
+
+void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p)
+{
+ fill_string(p->name, CTDB_IFACE_SIZE+2);
+ p->link_state = rand16();
+ p->references = rand32();
+}
+
+void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2)
+{
+ verify_string(p1->name, p2->name);
+ assert(p1->link_state == p2->link_state);
+ assert(p1->references == p2->references);
+}
+
+void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p)
+{
+ unsigned int i;
+
+ p->num = rand_int(32);
+ if (p->num > 0) {
+ p->iface = talloc_array(mem_ctx, struct ctdb_iface, p->num);
+ assert(p->iface != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_iface(mem_ctx, &p->iface[i]);
+ }
+ } else {
+ p->iface = NULL;
+ }
+}
+
+void verify_ctdb_iface_list(struct ctdb_iface_list *p1,
+ struct ctdb_iface_list *p2)
+{
+ unsigned int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_iface(&p1->iface[i], &p2->iface[i]);
+ }
+}
+
+void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_info *p)
+{
+ fill_ctdb_public_ip(mem_ctx, &p->ip);
+ p->active_idx = rand_int(32) + 1;
+ p->ifaces = talloc(mem_ctx, struct ctdb_iface_list);
+ assert(p->ifaces != NULL);
+ fill_ctdb_iface_list(mem_ctx, p->ifaces);
+}
+
+void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1,
+ struct ctdb_public_ip_info *p2)
+{
+ verify_ctdb_public_ip(&p1->ip, &p2->ip);
+ assert(p1->active_idx == p2->active_idx);
+ verify_ctdb_iface_list(p1->ifaces, p2->ifaces);
+}
+
+void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_statistics_list *p)
+{
+ int i;
+
+ p->num = rand_int(10);
+ if (p->num > 0) {
+ p->stats = talloc_zero_array(mem_ctx, struct ctdb_statistics,
+ p->num);
+ assert(p->stats != NULL);
+
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_statistics(mem_ctx, &p->stats[i]);
+ }
+ } else {
+ p->stats = NULL;
+ }
+}
+
+void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1,
+ struct ctdb_statistics_list *p2)
+{
+ int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_statistics(&p1->stats[i], &p2->stats[i]);
+ }
+}
+
+void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p)
+{
+ p->db_id = rand32();
+ fill_ctdb_ltdb_header(&p->header);
+ fill_tdb_data_nonnull(mem_ctx, &p->key);
+}
+
+void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2)
+{
+ assert(p1->db_id == p2->db_id);
+ verify_ctdb_ltdb_header(&p1->header, &p2->header);
+ verify_tdb_data(&p1->key, &p2->key);
+}
+
+void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx,
+ struct ctdb_db_statistics *p)
+{
+ unsigned int i;
+
+ p->locks.num_calls = rand32();
+ p->locks.num_current = rand32();
+ p->locks.num_pending = rand32();
+ p->locks.num_failed = rand32();
+ fill_ctdb_latency_counter(&p->locks.latency);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ p->locks.buckets[i] = rand32();
+ }
+
+ fill_ctdb_latency_counter(&p->vacuum.latency);
+
+ p->db_ro_delegations = rand32();
+ p->db_ro_revokes = rand32();
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ p->hop_count_bucket[i] = rand32();
+ }
+
+ p->num_hot_keys = MAX_HOT_KEYS;
+ for (i=0; i<p->num_hot_keys; i++) {
+ p->hot_keys[i].count = rand32();
+ fill_tdb_data(mem_ctx, &p->hot_keys[i].key);
+ }
+}
+
+void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1,
+ struct ctdb_db_statistics *p2)
+{
+ unsigned int i;
+
+ assert(p1->locks.num_calls == p2->locks.num_calls);
+ assert(p1->locks.num_current == p2->locks.num_current);
+ assert(p1->locks.num_pending == p2->locks.num_pending);
+ assert(p1->locks.num_failed == p2->locks.num_failed);
+ verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ assert(p1->locks.buckets[i] == p2->locks.buckets[i]);
+ }
+
+ verify_ctdb_latency_counter(&p1->vacuum.latency, &p2->vacuum.latency);
+
+ assert(p1->db_ro_delegations == p2->db_ro_delegations);
+ assert(p1->db_ro_revokes == p2->db_ro_revokes);
+ for (i=0; i<MAX_COUNT_BUCKETS; i++) {
+ assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]);
+ }
+
+ assert(p1->num_hot_keys == p2->num_hot_keys);
+ for (i=0; i<p1->num_hot_keys; i++) {
+ assert(p1->hot_keys[i].count == p2->hot_keys[i].count);
+ verify_tdb_data(&p1->hot_keys[i].key, &p2->hot_keys[i].key);
+ }
+}
+
+void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p)
+{
+ p->pid = rand32();
+ p->srvid = rand64();
+}
+
+void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1,
+ struct ctdb_pid_srvid *p2)
+{
+ assert(p1->pid == p2->pid);
+ assert(p1->srvid == p2->srvid);
+}
+
+void fill_ctdb_election_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_election_message *p)
+{
+ p->num_connected = rand_int(32);
+ fill_ctdb_timeval(&p->priority_time);
+ p->pnn = rand_int(32);
+ p->node_flags = rand32();
+}
+
+void verify_ctdb_election_message(struct ctdb_election_message *p1,
+ struct ctdb_election_message *p2)
+{
+ assert(p1->num_connected == p2->num_connected);
+ verify_ctdb_timeval(&p1->priority_time, &p2->priority_time);
+ assert(p1->pnn == p2->pnn);
+ assert(p1->node_flags == p2->node_flags);
+}
+
+void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_srvid_message *p)
+{
+ p->pnn = rand_int(32);
+ p->srvid = rand64();
+}
+
+void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1,
+ struct ctdb_srvid_message *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ assert(p1->srvid == p2->srvid);
+}
+
+void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_disable_message *p)
+{
+ p->pnn = rand_int(32);
+ p->srvid = rand64();
+ p->timeout = rand32();
+}
+
+void verify_ctdb_disable_message(struct ctdb_disable_message *p1,
+ struct ctdb_disable_message *p2)
+{
+ assert(p1->pnn == p2->pnn);
+ assert(p1->srvid == p2->srvid);
+ assert(p1->timeout == p2->timeout);
+}
+
+void fill_ctdb_server_id(struct ctdb_server_id *p)
+{
+ p->pid = rand64();
+ p->task_id = rand32();
+ p->vnn = rand_int(32);
+ p->unique_id = rand64();
+}
+
+void verify_ctdb_server_id(struct ctdb_server_id *p1,
+ struct ctdb_server_id *p2)
+{
+ assert(p1->pid == p2->pid);
+ assert(p1->task_id == p2->task_id);
+ assert(p1->vnn == p2->vnn);
+ assert(p1->unique_id == p2->unique_id);
+}
+
+void fill_ctdb_g_lock(struct ctdb_g_lock *p)
+{
+ p->type = rand_int(2);
+ fill_ctdb_server_id(&p->sid);
+}
+
+void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2)
+{
+ assert(p1->type == p2->type);
+ verify_ctdb_server_id(&p1->sid, &p2->sid);
+}
+
+void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p)
+{
+ unsigned int i;
+
+ p->num = rand_int(20) + 1;
+ p->lock = talloc_zero_array(mem_ctx, struct ctdb_g_lock, p->num);
+ assert(p->lock != NULL);
+ for (i=0; i<p->num; i++) {
+ fill_ctdb_g_lock(&p->lock[i]);
+ }
+}
+
+void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1,
+ struct ctdb_g_lock_list *p2)
+{
+ unsigned int i;
+
+ assert(p1->num == p2->num);
+ for (i=0; i<p1->num; i++) {
+ verify_ctdb_g_lock(&p1->lock[i], &p2->lock[i]);
+ }
+}
+
+void fill_sock_packet_header(struct sock_packet_header *p)
+{
+ p->length = rand32();
+ p->reqid = rand32();
+}
+
+void verify_sock_packet_header(struct sock_packet_header *p1,
+ struct sock_packet_header *p2)
+{
+ assert(p1->length == p2->length);
+ assert(p1->reqid == p2->reqid);
+}
diff --git a/ctdb/tests/src/protocol_common.h b/ctdb/tests/src/protocol_common.h
new file mode 100644
index 0000000..171b19b
--- /dev/null
+++ b/ctdb/tests/src/protocol_common.h
@@ -0,0 +1,238 @@
+/*
+ protocol tests - common functions
+
+ Copyright (C) Amitay Isaacs 2015-2017
+
+ 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/>.
+*/
+
+#ifndef __CTDB_PROTOCOL_COMMON_H__
+#define __CTDB_PROTOCOL_COMMON_H__
+
+#include "replace.h"
+#include "system/network.h"
+
+#include <talloc.h>
+#include <tdb.h>
+
+#include "protocol/protocol.h"
+
+#include "tests/src/protocol_common_basic.h"
+
+void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p);
+void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p);
+void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2);
+
+void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p);
+void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2);
+
+void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p);
+void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2);
+
+void fill_ctdb_latency_counter(struct ctdb_latency_counter *p);
+void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1,
+ struct ctdb_latency_counter *p2);
+
+void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p);
+void verify_ctdb_statistics(struct ctdb_statistics *p1,
+ struct ctdb_statistics *p2);
+
+void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p);
+void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2);
+
+void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p);
+void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2);
+
+void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p);
+void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2);
+
+void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p);
+void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2);
+
+void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p);
+void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1,
+ struct ctdb_pulldb_ext *p2);
+
+void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p);
+void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1,
+ struct ctdb_db_vacuum *p2);
+
+void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p);
+void verify_ctdb_echo_data(struct ctdb_echo_data *p1,
+ struct ctdb_echo_data *p2);
+
+void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p);
+void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1,
+ struct ctdb_ltdb_header *p2);
+
+void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p);
+void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2);
+
+void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p);
+void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1,
+ struct ctdb_rec_buffer *p2);
+
+void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start *p);
+void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1,
+ struct ctdb_traverse_start *p2);
+
+void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all *p);
+void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1,
+ struct ctdb_traverse_all *p2);
+
+void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start_ext *p);
+void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1,
+ struct ctdb_traverse_start_ext *p2);
+
+void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all_ext *p);
+void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1,
+ struct ctdb_traverse_all_ext *p2);
+
+void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p);
+void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2);
+
+void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p);
+void verify_ctdb_connection(struct ctdb_connection *p1,
+ struct ctdb_connection *p2);
+
+void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_connection_list *p);
+void verify_ctdb_connection_list(struct ctdb_connection_list *p1,
+ struct ctdb_connection_list *p2);
+
+void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p);
+void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2);
+
+void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx,
+ struct ctdb_node_flag_change *p);
+void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1,
+ struct ctdb_node_flag_change *p2);
+
+void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p);
+void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2);
+
+void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p);
+void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1,
+ struct ctdb_tunable_list *p2);
+
+void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p);
+void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1,
+ struct ctdb_tickle_list *p2);
+
+void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p);
+void verify_ctdb_addr_info(struct ctdb_addr_info *p1,
+ struct ctdb_addr_info *p2);
+
+void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p);
+void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2);
+
+void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p);
+void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2);
+
+void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p);
+void verify_ctdb_public_ip(struct ctdb_public_ip *p1,
+ struct ctdb_public_ip *p2);
+
+void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list *p);
+void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1,
+ struct ctdb_public_ip_list *p2);
+
+void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx,
+ struct ctdb_node_and_flags *p);
+void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1,
+ struct ctdb_node_and_flags *p2);
+
+void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p);
+void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2);
+
+void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p);
+void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2);
+
+void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p);
+void verify_ctdb_script_list(struct ctdb_script_list *p1,
+ struct ctdb_script_list *p2);
+
+void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p);
+void verify_ctdb_ban_state(struct ctdb_ban_state *p1,
+ struct ctdb_ban_state *p2);
+
+void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p);
+void verify_ctdb_notify_data(struct ctdb_notify_data *p1,
+ struct ctdb_notify_data *p2);
+
+void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p);
+void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2);
+
+void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p);
+void verify_ctdb_iface_list(struct ctdb_iface_list *p1,
+ struct ctdb_iface_list *p2);
+
+void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_info *p);
+void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1,
+ struct ctdb_public_ip_info *p2);
+
+void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx,
+ struct ctdb_statistics_list *p);
+void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1,
+ struct ctdb_statistics_list *p2);
+
+void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p);
+void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2);
+
+void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx,
+ struct ctdb_db_statistics *p);
+void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1,
+ struct ctdb_db_statistics *p2);
+
+void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p);
+void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1,
+ struct ctdb_pid_srvid *p2);
+
+void fill_ctdb_election_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_election_message *p);
+void verify_ctdb_election_message(struct ctdb_election_message *p1,
+ struct ctdb_election_message *p2);
+
+void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_srvid_message *p);
+void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1,
+ struct ctdb_srvid_message *p2);
+
+void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx,
+ struct ctdb_disable_message *p);
+void verify_ctdb_disable_message(struct ctdb_disable_message *p1,
+ struct ctdb_disable_message *p2);
+
+void fill_ctdb_server_id(struct ctdb_server_id *p);
+void verify_ctdb_server_id(struct ctdb_server_id *p1,
+ struct ctdb_server_id *p2);
+
+void fill_ctdb_g_lock(struct ctdb_g_lock *p);
+void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2);
+
+void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p);
+void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1,
+ struct ctdb_g_lock_list *p2);
+
+void fill_sock_packet_header(struct sock_packet_header *p);
+void verify_sock_packet_header(struct sock_packet_header *p1,
+ struct sock_packet_header *p2);
+
+#endif /* __CTDB_PROTOCOL_COMMON_H__ */
diff --git a/ctdb/tests/src/protocol_common_basic.c b/ctdb/tests/src/protocol_common_basic.c
new file mode 100644
index 0000000..7567f7b
--- /dev/null
+++ b/ctdb/tests/src/protocol_common_basic.c
@@ -0,0 +1,305 @@
+/*
+ protocol tests - common functions - basic types
+
+ Copyright (C) Amitay Isaacs 2015-2017
+
+ 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/wait.h"
+
+#include <assert.h>
+
+#include "lib/util/fault.h"
+
+#include "tests/src/protocol_common_basic.h"
+
+uint8_t BUFFER[1024*1024];
+
+/*
+ * Functions to generation random data
+ */
+
+int rand_int(int max)
+{
+ return random() % max;
+}
+
+uint8_t rand8(void)
+{
+ uint8_t val = rand_int(256) & 0xff;
+ return val;
+}
+
+uint16_t rand16(void)
+{
+ uint16_t val = rand_int(0xffff) & 0xffff;
+ return val;
+}
+
+int32_t rand32i(void)
+{
+ return INT_MIN + random();
+}
+
+uint32_t rand32(void)
+{
+ return random();
+}
+
+uint64_t rand64(void)
+{
+ uint64_t t = random();
+ t = (t << 32) | random();
+ return t;
+}
+
+double rand_double(void)
+{
+ return 1.0 / rand64();
+}
+
+void fill_buffer(void *p, size_t len)
+{
+ size_t i;
+ uint8_t *ptr = p;
+
+ for (i=0; i<len; i++) {
+ ptr[i] = rand8();
+ }
+}
+
+void verify_buffer(void *p1, void *p2, size_t len)
+{
+ if (len > 0) {
+ assert(memcmp(p1, p2, len) == 0);
+ }
+}
+
+void fill_string(char *p, size_t len)
+{
+ size_t i;
+
+ for (i=0; i<len-1; i++) {
+ p[i] = 'A' + rand_int(26);
+ }
+ p[len-1] = '\0';
+}
+
+void verify_string(const char *p1, const char *p2)
+{
+ assert(strlen(p1) == strlen(p2));
+ assert(strcmp(p1, p2) == 0);
+}
+
+void fill_ctdb_uint8(uint8_t *p)
+{
+ *p = rand8();
+}
+
+void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_uint16(uint16_t *p)
+{
+ *p = rand16();
+}
+
+void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_int32(int32_t *p)
+{
+ *p = rand32i();
+}
+
+void verify_ctdb_int32(int32_t *p1, int32_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_uint32(uint32_t *p)
+{
+ *p = rand32();
+}
+
+void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_uint64(uint64_t *p)
+{
+ *p = rand64();
+}
+
+void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_double(double *p)
+{
+ *p = rand_double();
+}
+
+void verify_ctdb_double(double *p1, double *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_bool(bool *p)
+{
+ if (rand_int(2) == 0) {
+ *p = true;
+ } else {
+ *p = false;
+ }
+}
+
+void verify_ctdb_bool(bool *p1, bool *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p)
+{
+ char *str;
+ int len;
+
+ len = rand_int(1024) + 2;
+ str = talloc_size(mem_ctx, len+1);
+ assert(str != NULL);
+
+ fill_string(str, len);
+ *p = str;
+}
+
+void verify_ctdb_string(const char **p1, const char **p2)
+{
+ if (*p1 == NULL || *p2 == NULL) {
+ assert(*p1 == *p2);
+ } else {
+ verify_string(*p1, *p2);
+ }
+}
+
+void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p)
+{
+ fill_ctdb_string(mem_ctx, p);
+}
+
+void verify_ctdb_stringn(const char **p1, const char **p2)
+{
+ verify_ctdb_string(p1, p2);
+}
+
+void fill_ctdb_pid(pid_t *p)
+{
+ *p = rand32();
+}
+
+void verify_ctdb_pid(pid_t *p1, pid_t *p2)
+{
+ assert(*p1 == *p2);
+}
+
+void fill_ctdb_timeval(struct timeval *p)
+{
+ p->tv_sec = rand32();
+ p->tv_usec = rand_int(1000000);
+}
+
+void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2)
+{
+ assert(p1->tv_sec == p2->tv_sec);
+ assert(p1->tv_usec == p2->tv_usec);
+}
+
+static unsigned int seed;
+static char protocol_test_iterate_buf[1024];
+
+static void protocol_test_iterate_abort_handler(int sig)
+{
+ struct sigaction act = {
+ .sa_handler = SIG_DFL,
+ };
+
+ fprintf(stderr, "Failed with seed: %d\n", seed);
+ if (protocol_test_iterate_buf[0] != '\0') {
+ fprintf(stderr, " tag: %s\n", protocol_test_iterate_buf);
+ }
+ log_stack_trace();
+ sigaction(SIGABRT, &act, NULL);
+ abort();
+}
+
+void protocol_test_iterate_tag(const char *fmt, ...)
+{
+ va_list ap;
+ int count;
+
+ va_start(ap,fmt);
+ count = vsnprintf(protocol_test_iterate_buf,
+ sizeof(protocol_test_iterate_buf),
+ fmt,
+ ap);
+ va_end(ap);
+
+ assert(count >= 0);
+ protocol_test_iterate_buf[sizeof(protocol_test_iterate_buf) - 1] = '\0';
+}
+
+void protocol_test_iterate(int argc,
+ const char *argv[],
+ void (*test_func)(void))
+{
+ struct sigaction act = {
+ .sa_handler = protocol_test_iterate_abort_handler,
+ };
+ unsigned int min, max;
+
+ if (argc == 2 || argc == 3) {
+ min = atoi(argv[1]);
+
+ if (argc == 3) {
+ max = atoi(argv[2]);
+ if (min >= max) {
+ fprintf(stderr,
+ "%s: min must be less than max\n",
+ argv[0]);
+ exit(1);
+ }
+
+ } else {
+ max = min;
+ }
+ } else {
+ fprintf(stderr, "usage: %s min [max]\n", argv[0]);
+ exit(1);
+ }
+
+ sigaction(SIGABRT, &act, NULL);
+
+ for (seed = min; seed <= max ; seed++) {
+ srandom(seed);
+
+ test_func();
+ }
+}
diff --git a/ctdb/tests/src/protocol_common_basic.h b/ctdb/tests/src/protocol_common_basic.h
new file mode 100644
index 0000000..22a11b3
--- /dev/null
+++ b/ctdb/tests/src/protocol_common_basic.h
@@ -0,0 +1,175 @@
+/*
+ protocol tests - common functions - basic types
+
+ Copyright (C) Amitay Isaacs 2015-2017
+
+ 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/>.
+*/
+
+#ifndef __CTDB_PROTOCOL_COMMON_BASIC_H__
+#define __CTDB_PROTOCOL_COMMON_BASIC_H__
+
+#include "replace.h"
+
+#include <talloc.h>
+
+/*
+ * Generate test routines
+ */
+
+#define TEST_FUNC(NAME) test_ ##NAME
+#define FILL_FUNC(NAME) fill_ ##NAME
+#define VERIFY_FUNC(NAME) verify_ ##NAME
+#define LEN_FUNC(NAME) NAME## _len
+#define PUSH_FUNC(NAME) NAME## _push
+#define PULL_FUNC(NAME) NAME## _pull
+
+/*
+ * Test for basic data types that do not need memory allocation
+ * For example - int32_t, uint32_t, uint64_t
+ */
+#define PROTOCOL_TYPE1_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(void) \
+{ \
+ TYPE p1; \
+ TYPE p2; \
+ size_t buflen, np = 0; \
+ int ret; \
+\
+ FILL_FUNC(NAME)(&p1); \
+ buflen = LEN_FUNC(NAME)(&p1); \
+ assert(buflen < sizeof(BUFFER)); \
+ PUSH_FUNC(NAME)(&p1, BUFFER, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(BUFFER, buflen, &p2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+}
+
+/*
+ * Test for container data types that need memory allocation for sub-elements
+ * For example - TDB_DATA
+ */
+#define PROTOCOL_TYPE2_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ TYPE p1; \
+ TYPE p2; \
+ size_t buflen, np = 0; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(mem_ctx, &p1); \
+ buflen = LEN_FUNC(NAME)(&p1); \
+ assert(buflen < sizeof(BUFFER)); \
+ PUSH_FUNC(NAME)(&p1, BUFFER, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+/*
+ * Test for derived data types that need memory allocation
+ * For example - most ctdb structures
+ */
+#define PROTOCOL_TYPE3_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ TYPE *p1, *p2; \
+ size_t buflen, np = 0; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ p1 = talloc_zero(mem_ctx, TYPE); \
+ assert(p1 != NULL); \
+ FILL_FUNC(NAME)(p1, p1); \
+ buflen = LEN_FUNC(NAME)(p1); \
+ assert(buflen < sizeof(BUFFER)); \
+ PUSH_FUNC(NAME)(p1, BUFFER, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(p1, p2); \
+ talloc_free(mem_ctx); \
+}
+
+extern uint8_t BUFFER[1024*1024];
+
+int rand_int(int max);
+uint8_t rand8(void);
+uint16_t rand16(void);
+int32_t rand32i(void);
+uint32_t rand32(void);
+uint64_t rand64(void);
+double rand_double(void);
+
+void fill_buffer(void *p, size_t len);
+void verify_buffer(void *p1, void *p2, size_t len);
+
+void fill_string(char *p, size_t len);
+void verify_string(const char *p1, const char *p2);
+
+void fill_ctdb_uint8(uint8_t *p);
+void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2);
+
+void fill_ctdb_uint16(uint16_t *p);
+void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2);
+
+void fill_ctdb_int32(int32_t *p);
+void verify_ctdb_int32(int32_t *p1, int32_t *p2);
+
+void fill_ctdb_uint32(uint32_t *p);
+void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2);
+
+void fill_ctdb_uint64(uint64_t *p);
+void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2);
+
+void fill_ctdb_double(double *p);
+void verify_ctdb_double(double *p1, double *p2);
+
+void fill_ctdb_bool(bool *p);
+void verify_ctdb_bool(bool *p1, bool *p2);
+
+void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p);
+void verify_ctdb_string(const char **p1, const char **p2);
+
+void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p);
+void verify_ctdb_stringn(const char **p1, const char **p2);
+
+void fill_ctdb_pid(pid_t *p);
+void verify_ctdb_pid(pid_t *p1, pid_t *p2);
+
+void fill_ctdb_timeval(struct timeval *p);
+void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2);
+
+void protocol_test_iterate_tag(const char *fmt, ...) PRINTF_ATTRIBUTE(1,0);
+void protocol_test_iterate(int argc,
+ const char *argv[],
+ void (*test_func)(void));
+
+#endif /* __CTDB_PROTOCOL_COMMON_BASIC_H__ */
+
+
diff --git a/ctdb/tests/src/protocol_common_ctdb.c b/ctdb/tests/src/protocol_common_ctdb.c
new file mode 100644
index 0000000..3840768
--- /dev/null
+++ b/ctdb/tests/src/protocol_common_ctdb.c
@@ -0,0 +1,1934 @@
+/*
+ protocol tests - ctdb protocol
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <assert.h>
+
+#include "tests/src/protocol_common.h"
+#include "tests/src/protocol_common_ctdb.h"
+
+/*
+ * Functions to fill and verify protocol structures
+ */
+
+void fill_ctdb_req_header(struct ctdb_req_header *h)
+{
+ h->length = rand32();
+ h->ctdb_magic = rand32();
+ h->ctdb_version = rand32();
+ h->generation = rand32();
+ h->operation = rand32();
+ h->destnode = rand32();
+ h->srcnode = rand32();
+ h->reqid = rand32();
+}
+
+void verify_ctdb_req_header(struct ctdb_req_header *h,
+ struct ctdb_req_header *h2)
+{
+ assert(h->length == h2->length);
+ assert(h->ctdb_magic == h2->ctdb_magic);
+ assert(h->ctdb_version == h2->ctdb_version);
+ assert(h->generation == h2->generation);
+ assert(h->operation == h2->operation);
+ assert(h->destnode == h2->destnode);
+ assert(h->srcnode == h2->srcnode);
+ assert(h->reqid == h2->reqid);
+}
+
+void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c)
+{
+ c->flags = rand32();
+ c->db_id = rand32();
+ c->callid = rand32();
+ c->hopcount = rand32();
+ fill_tdb_data_nonnull(mem_ctx, &c->key);
+ fill_tdb_data(mem_ctx, &c->calldata);
+}
+
+void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2)
+{
+ assert(c->flags == c2->flags);
+ assert(c->db_id == c2->db_id);
+ assert(c->callid == c2->callid);
+ assert(c->hopcount == c2->hopcount);
+ verify_tdb_data(&c->key, &c2->key);
+ verify_tdb_data(&c->calldata, &c2->calldata);
+}
+
+void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c)
+{
+ c->status = rand32();
+ fill_tdb_data(mem_ctx, &c->data);
+}
+
+void verify_ctdb_reply_call(struct ctdb_reply_call *c,
+ struct ctdb_reply_call *c2)
+{
+ assert(c->status == c2->status);
+ verify_tdb_data(&c->data, &c2->data);
+}
+
+void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c)
+{
+ c->status = rand32();
+ fill_tdb_data(mem_ctx, &c->msg);
+}
+
+void verify_ctdb_reply_error(struct ctdb_reply_error *c,
+ struct ctdb_reply_error *c2)
+{
+ assert(c->status == c2->status);
+ verify_tdb_data(&c->msg, &c2->msg);
+}
+
+void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c)
+{
+ c->db_id = rand32();
+ c->rsn = rand64();
+ c->dmaster = rand32();
+ fill_tdb_data_nonnull(mem_ctx, &c->key);
+ fill_tdb_data(mem_ctx, &c->data);
+}
+
+void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c,
+ struct ctdb_req_dmaster *c2)
+{
+ assert(c->db_id == c2->db_id);
+ assert(c->rsn == c2->rsn);
+ assert(c->dmaster == c2->dmaster);
+ verify_tdb_data(&c->key, &c2->key);
+ verify_tdb_data(&c->data, &c2->data);
+}
+
+void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_dmaster *c)
+{
+ c->db_id = rand32();
+ c->rsn = rand64();
+ fill_tdb_data_nonnull(mem_ctx, &c->key);
+ fill_tdb_data(mem_ctx, &c->data);
+}
+
+void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c,
+ struct ctdb_reply_dmaster *c2)
+{
+ assert(c->db_id == c2->db_id);
+ assert(c->rsn == c2->rsn);
+ verify_tdb_data(&c->key, &c2->key);
+ verify_tdb_data(&c->data, &c2->data);
+}
+
+void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_control_data *cd,
+ uint32_t opcode)
+{
+ cd->opcode = opcode;
+ switch (opcode) {
+ case CTDB_CONTROL_PROCESS_EXISTS:
+ cd->data.pid = rand32();
+ break;
+
+ case CTDB_CONTROL_STATISTICS:
+ break;
+
+ case CTDB_CONTROL_PING:
+ break;
+
+ case CTDB_CONTROL_GETDBPATH:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_GETVNNMAP:
+ break;
+
+ case CTDB_CONTROL_SETVNNMAP:
+ cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map);
+ assert(cd->data.vnnmap != NULL);
+ fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap);
+ break;
+
+ case CTDB_CONTROL_GET_DEBUG:
+ break;
+
+ case CTDB_CONTROL_SET_DEBUG:
+ cd->data.loglevel = rand_int(5);
+ break;
+
+ case CTDB_CONTROL_GET_DBMAP:
+ break;
+
+ case CTDB_CONTROL_GET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMODE:
+ cd->data.recmode = rand_int(2);
+ break;
+
+ case CTDB_CONTROL_STATISTICS_RESET:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH:
+ fill_ctdb_string(mem_ctx, &cd->data.db_name);
+ assert(cd->data.db_name != NULL);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START:
+ cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start);
+ assert(cd->data.traverse_start != NULL);
+ fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL:
+ cd->data.traverse_all = talloc(mem_ctx, struct ctdb_traverse_all);
+ assert(cd->data.traverse_all != NULL);
+ fill_ctdb_traverse_all(mem_ctx, cd->data.traverse_all);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_DATA:
+ cd->data.rec_data = talloc(mem_ctx, struct ctdb_rec_data);
+ assert(cd->data.rec_data != NULL);
+ fill_ctdb_rec_data(mem_ctx, cd->data.rec_data);
+ break;
+
+ case CTDB_CONTROL_REGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_GET_DBNAME:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_ENABLE_SEQNUM:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_UPDATE_SEQNUM:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DUMP_MEMORY:
+ break;
+
+ case CTDB_CONTROL_GET_PID:
+ break;
+
+ case CTDB_CONTROL_FREEZE:
+ break;
+
+ case CTDB_CONTROL_GET_PNN:
+ break;
+
+ case CTDB_CONTROL_SHUTDOWN:
+ break;
+
+ case CTDB_CONTROL_TCP_CLIENT:
+ cd->data.conn = talloc(mem_ctx, struct ctdb_connection);
+ assert(cd->data.conn != NULL);
+ fill_ctdb_connection(mem_ctx, cd->data.conn);
+ break;
+
+ case CTDB_CONTROL_TCP_ADD:
+ cd->data.conn = talloc(mem_ctx, struct ctdb_connection);
+ assert(cd->data.conn != NULL);
+ fill_ctdb_connection(mem_ctx, cd->data.conn);
+ break;
+
+ case CTDB_CONTROL_TCP_REMOVE:
+ cd->data.conn = talloc(mem_ctx, struct ctdb_connection);
+ assert(cd->data.conn != NULL);
+ fill_ctdb_connection(mem_ctx, cd->data.conn);
+ break;
+
+ case CTDB_CONTROL_STARTUP:
+ break;
+
+ case CTDB_CONTROL_SET_TUNABLE:
+ cd->data.tunable = talloc(mem_ctx, struct ctdb_tunable);
+ assert(cd->data.tunable != NULL);
+ fill_ctdb_tunable(mem_ctx, cd->data.tunable);
+ break;
+
+ case CTDB_CONTROL_GET_TUNABLE:
+ fill_ctdb_string(mem_ctx, &cd->data.tun_var);
+ assert(cd->data.tun_var != NULL);
+ break;
+
+ case CTDB_CONTROL_LIST_TUNABLES:
+ break;
+
+ case CTDB_CONTROL_MODIFY_FLAGS:
+ cd->data.flag_change = talloc(mem_ctx, struct ctdb_node_flag_change);
+ assert(cd->data.flag_change != NULL);
+ fill_ctdb_node_flag_change(mem_ctx, cd->data.flag_change);
+ break;
+
+ case CTDB_CONTROL_GET_ALL_TUNABLES:
+ break;
+
+ case CTDB_CONTROL_GET_TCP_TICKLE_LIST:
+ cd->data.addr = talloc(mem_ctx, ctdb_sock_addr);
+ assert(cd->data.addr != NULL);
+ fill_ctdb_sock_addr(mem_ctx, cd->data.addr);
+ break;
+
+ case CTDB_CONTROL_SET_TCP_TICKLE_LIST:
+ cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list);
+ assert(cd->data.tickles != NULL);
+ fill_ctdb_tickle_list(mem_ctx, cd->data.tickles);
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_PERSISTENT:
+ fill_ctdb_string(mem_ctx, &cd->data.db_name);
+ assert(cd->data.db_name != NULL);
+ break;
+
+ case CTDB_CONTROL_UPDATE_RECORD:
+ cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(cd->data.recbuf != NULL);
+ fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_SEND_GRATUITOUS_ARP:
+ cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info);
+ assert(cd->data.addr_info != NULL);
+ fill_ctdb_addr_info(mem_ctx, cd->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_WIPE_DATABASE:
+ cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb);
+ assert(cd->data.transdb != NULL);
+ fill_ctdb_transdb(mem_ctx, cd->data.transdb);
+ break;
+
+ case CTDB_CONTROL_UPTIME:
+ break;
+
+ case CTDB_CONTROL_START_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_END_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_TRY_DELETE_RECORDS:
+ cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(cd->data.recbuf != NULL);
+ fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_ADD_PUBLIC_IP:
+ cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info);
+ assert(cd->data.addr_info != NULL);
+ fill_ctdb_addr_info(mem_ctx, cd->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_DEL_PUBLIC_IP:
+ cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info);
+ assert(cd->data.addr_info != NULL);
+ fill_ctdb_addr_info(mem_ctx, cd->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_GET_CAPABILITIES:
+ break;
+
+ case CTDB_CONTROL_RECD_PING:
+ break;
+
+ case CTDB_CONTROL_RELEASE_IP:
+ cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip);
+ assert(cd->data.pubip != NULL);
+ fill_ctdb_public_ip(mem_ctx, cd->data.pubip);
+ break;
+
+ case CTDB_CONTROL_TAKEOVER_IP:
+ cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip);
+ assert(cd->data.pubip != NULL);
+ fill_ctdb_public_ip(mem_ctx, cd->data.pubip);
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_GET_NODEMAP:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_KILL:
+ cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start);
+ assert(cd->data.traverse_start != NULL);
+ fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start);
+ break;
+
+ case CTDB_CONTROL_RECD_RECLOCK_LATENCY:
+ cd->data.reclock_latency = rand_double();
+ break;
+
+ case CTDB_CONTROL_GET_RECLOCK_FILE:
+ break;
+
+ case CTDB_CONTROL_STOP_NODE:
+ break;
+
+ case CTDB_CONTROL_CONTINUE_NODE:
+ break;
+
+ case CTDB_CONTROL_SET_LMASTERROLE:
+ cd->data.role = rand_int(2);
+ break;
+
+ case CTDB_CONTROL_SET_RECMASTERROLE:
+ cd->data.role = rand_int(2);
+ break;
+
+ case CTDB_CONTROL_SET_BAN_STATE:
+ cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state);
+ assert(cd->data.ban_state != NULL);
+ fill_ctdb_ban_state(mem_ctx, cd->data.ban_state);
+ break;
+
+ case CTDB_CONTROL_GET_BAN_STATE:
+ break;
+
+ case CTDB_CONTROL_REGISTER_NOTIFY:
+ cd->data.notify = talloc(mem_ctx, struct ctdb_notify_data);
+ assert(cd->data.notify != NULL);
+ fill_ctdb_notify_data(mem_ctx, cd->data.notify);
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_NOTIFY:
+ cd->data.srvid = rand64();
+ break;
+
+ case CTDB_CONTROL_TRANS3_COMMIT:
+ cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(cd->data.recbuf != NULL);
+ fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_GET_DB_SEQNUM:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_SET_HEALTHY:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_GET_HEALTH:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IP_INFO:
+ cd->data.addr = talloc(mem_ctx, ctdb_sock_addr);
+ assert(cd->data.addr != NULL);
+ fill_ctdb_sock_addr(mem_ctx, cd->data.addr);
+ break;
+
+ case CTDB_CONTROL_GET_IFACES:
+ break;
+
+ case CTDB_CONTROL_SET_IFACE_LINK_STATE:
+ cd->data.iface = talloc(mem_ctx, struct ctdb_iface);
+ assert(cd->data.iface != NULL);
+ fill_ctdb_iface(mem_ctx, cd->data.iface);
+ break;
+
+ case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE:
+ cd->data.conn = talloc(mem_ctx, struct ctdb_connection);
+ assert(cd->data.conn != NULL);
+ fill_ctdb_connection(mem_ctx, cd->data.conn);
+ break;
+
+ case CTDB_CONTROL_GET_STAT_HISTORY:
+ break;
+
+ case CTDB_CONTROL_SCHEDULE_FOR_DELETION:
+ cd->data.key = talloc(mem_ctx, struct ctdb_key_data);
+ assert(cd->data.key != NULL);
+ fill_ctdb_key_data(mem_ctx, cd->data.key);
+ break;
+
+ case CTDB_CONTROL_SET_DB_READONLY:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START_EXT:
+ cd->data.traverse_start_ext = talloc(mem_ctx, struct ctdb_traverse_start_ext);
+ assert(cd->data.traverse_start_ext != NULL);
+ fill_ctdb_traverse_start_ext(mem_ctx, cd->data.traverse_start_ext);
+ break;
+
+ case CTDB_CONTROL_GET_DB_STATISTICS:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_SET_DB_STICKY:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_RELOAD_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL_EXT:
+ cd->data.traverse_all_ext = talloc(mem_ctx, struct ctdb_traverse_all_ext);
+ assert(cd->data.traverse_all_ext != NULL);
+ fill_ctdb_traverse_all_ext(mem_ctx, cd->data.traverse_all_ext);
+ break;
+
+ case CTDB_CONTROL_IPREALLOCATED:
+ break;
+
+ case CTDB_CONTROL_GET_RUNSTATE:
+ break;
+
+ case CTDB_CONTROL_DB_DETACH:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_GET_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_DB_FREEZE:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_THAW:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_START:
+ cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb);
+ assert(cd->data.transdb != NULL);
+ fill_ctdb_transdb(mem_ctx, cd->data.transdb);
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_COMMIT:
+ cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb);
+ assert(cd->data.transdb != NULL);
+ fill_ctdb_transdb(mem_ctx, cd->data.transdb);
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_CANCEL:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_PULL:
+ cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext);
+ assert(cd->data.pulldb_ext != NULL);
+ fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext);
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_START:
+ cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext);
+ assert(cd->data.pulldb_ext != NULL);
+ fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext);
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_CONFIRM:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_OPEN_FLAGS:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_REPLICATED:
+ fill_ctdb_string(mem_ctx, &cd->data.db_name);
+ assert(cd->data.db_name != NULL);
+ break;
+
+ case CTDB_CONTROL_CHECK_PID_SRVID:
+ cd->data.pid_srvid = talloc(mem_ctx, struct ctdb_pid_srvid);
+ assert(cd->data.pid_srvid != NULL);
+ fill_ctdb_pid_srvid(mem_ctx, cd->data.pid_srvid);
+ break;
+
+ case CTDB_CONTROL_TUNNEL_REGISTER:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_DEREGISTER:
+ break;
+
+ case CTDB_CONTROL_VACUUM_FETCH:
+ cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(cd->data.recbuf != NULL);
+ fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_DB_VACUUM:
+ cd->data.db_vacuum = talloc(mem_ctx, struct ctdb_db_vacuum);
+ assert(cd->data.db_vacuum != NULL);
+ fill_ctdb_db_vacuum(mem_ctx, cd->data.db_vacuum);
+ break;
+
+ case CTDB_CONTROL_ECHO_DATA:
+ cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data);
+ assert(cd->data.echo_data != NULL);
+ fill_ctdb_echo_data(mem_ctx, cd->data.echo_data);
+ break;
+
+ case CTDB_CONTROL_DISABLE_NODE:
+ break;
+
+ case CTDB_CONTROL_ENABLE_NODE:
+ break;
+ }
+}
+
+void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd,
+ struct ctdb_req_control_data *cd2)
+{
+ assert(cd->opcode == cd2->opcode);
+
+ switch (cd->opcode) {
+ case CTDB_CONTROL_PROCESS_EXISTS:
+ assert(cd->data.pid == cd2->data.pid);
+ break;
+
+ case CTDB_CONTROL_STATISTICS:
+ break;
+
+ case CTDB_CONTROL_PING:
+ break;
+
+ case CTDB_CONTROL_GETDBPATH:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_GETVNNMAP:
+ break;
+
+ case CTDB_CONTROL_SETVNNMAP:
+ verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap);
+ break;
+
+ case CTDB_CONTROL_GET_DEBUG:
+ break;
+
+ case CTDB_CONTROL_SET_DEBUG:
+ assert(cd->data.loglevel == cd2->data.loglevel);
+ break;
+
+ case CTDB_CONTROL_GET_DBMAP:
+ break;
+
+ case CTDB_CONTROL_GET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMODE:
+ assert(cd->data.recmode == cd2->data.recmode);
+ break;
+
+ case CTDB_CONTROL_STATISTICS_RESET:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH:
+ verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START:
+ verify_ctdb_traverse_start(cd->data.traverse_start,
+ cd2->data.traverse_start);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL:
+ verify_ctdb_traverse_all(cd->data.traverse_all,
+ cd2->data.traverse_all);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_DATA:
+ verify_ctdb_rec_data(cd->data.rec_data, cd2->data.rec_data);
+ break;
+
+ case CTDB_CONTROL_REGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_GET_DBNAME:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_ENABLE_SEQNUM:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_UPDATE_SEQNUM:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DUMP_MEMORY:
+ break;
+
+ case CTDB_CONTROL_GET_PID:
+ break;
+
+ case CTDB_CONTROL_FREEZE:
+ break;
+
+ case CTDB_CONTROL_GET_PNN:
+ break;
+
+ case CTDB_CONTROL_SHUTDOWN:
+ break;
+
+ case CTDB_CONTROL_TCP_CLIENT:
+ verify_ctdb_connection(cd->data.conn, cd2->data.conn);
+ break;
+
+ case CTDB_CONTROL_TCP_ADD:
+ verify_ctdb_connection(cd->data.conn, cd2->data.conn);
+ break;
+
+ case CTDB_CONTROL_TCP_REMOVE:
+ verify_ctdb_connection(cd->data.conn, cd2->data.conn);
+ break;
+
+ case CTDB_CONTROL_STARTUP:
+ break;
+
+ case CTDB_CONTROL_SET_TUNABLE:
+ verify_ctdb_tunable(cd->data.tunable, cd2->data.tunable);
+ break;
+
+ case CTDB_CONTROL_GET_TUNABLE:
+ verify_ctdb_string(&cd->data.tun_var, &cd2->data.tun_var);
+ break;
+
+ case CTDB_CONTROL_LIST_TUNABLES:
+ break;
+
+ case CTDB_CONTROL_MODIFY_FLAGS:
+ verify_ctdb_node_flag_change(cd->data.flag_change,
+ cd2->data.flag_change);
+ break;
+
+ case CTDB_CONTROL_GET_ALL_TUNABLES:
+ break;
+
+ case CTDB_CONTROL_GET_TCP_TICKLE_LIST:
+ verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr);
+ break;
+
+ case CTDB_CONTROL_SET_TCP_TICKLE_LIST:
+ verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles);
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_PERSISTENT:
+ verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name);
+ break;
+
+ case CTDB_CONTROL_UPDATE_RECORD:
+ verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_SEND_GRATUITOUS_ARP:
+ verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_WIPE_DATABASE:
+ verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb);
+ break;
+
+ case CTDB_CONTROL_UPTIME:
+ break;
+
+ case CTDB_CONTROL_START_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_END_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_TRY_DELETE_RECORDS:
+ verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_ADD_PUBLIC_IP:
+ verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_DEL_PUBLIC_IP:
+ verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info);
+ break;
+
+ case CTDB_CONTROL_GET_CAPABILITIES:
+ break;
+
+ case CTDB_CONTROL_RECD_PING:
+ break;
+
+ case CTDB_CONTROL_RELEASE_IP:
+ verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip);
+ break;
+
+ case CTDB_CONTROL_TAKEOVER_IP:
+ verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip);
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_GET_NODEMAP:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_KILL:
+ verify_ctdb_traverse_start(cd->data.traverse_start,
+ cd2->data.traverse_start);
+ break;
+
+ case CTDB_CONTROL_RECD_RECLOCK_LATENCY:
+ assert(cd->data.reclock_latency == cd2->data.reclock_latency);
+ break;
+
+ case CTDB_CONTROL_GET_RECLOCK_FILE:
+ break;
+
+ case CTDB_CONTROL_STOP_NODE:
+ break;
+
+ case CTDB_CONTROL_CONTINUE_NODE:
+ break;
+
+ case CTDB_CONTROL_SET_LMASTERROLE:
+ assert(cd->data.role == cd2->data.role);
+ break;
+
+ case CTDB_CONTROL_SET_RECMASTERROLE:
+ assert(cd->data.role == cd2->data.role);
+ break;
+
+ case CTDB_CONTROL_SET_BAN_STATE:
+ verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state);
+ break;
+
+ case CTDB_CONTROL_GET_BAN_STATE:
+ break;
+
+ case CTDB_CONTROL_REGISTER_NOTIFY:
+ verify_ctdb_notify_data(cd->data.notify, cd2->data.notify);
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_NOTIFY:
+ assert(cd->data.srvid == cd2->data.srvid);
+ break;
+
+ case CTDB_CONTROL_TRANS3_COMMIT:
+ verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_GET_DB_SEQNUM:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_SET_HEALTHY:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_GET_HEALTH:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IP_INFO:
+ verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr);
+ break;
+
+ case CTDB_CONTROL_GET_IFACES:
+ break;
+
+ case CTDB_CONTROL_SET_IFACE_LINK_STATE:
+ verify_ctdb_iface(cd->data.iface, cd2->data.iface);
+ break;
+
+ case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE:
+ verify_ctdb_connection(cd->data.conn, cd2->data.conn);
+ break;
+
+ case CTDB_CONTROL_GET_STAT_HISTORY:
+ break;
+
+ case CTDB_CONTROL_SCHEDULE_FOR_DELETION:
+ verify_ctdb_key_data(cd->data.key, cd2->data.key);
+ break;
+
+ case CTDB_CONTROL_SET_DB_READONLY:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START_EXT:
+ verify_ctdb_traverse_start_ext(cd->data.traverse_start_ext,
+ cd2->data.traverse_start_ext);
+ break;
+
+ case CTDB_CONTROL_GET_DB_STATISTICS:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_SET_DB_STICKY:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_RELOAD_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL_EXT:
+ verify_ctdb_traverse_all_ext(cd->data.traverse_all_ext,
+ cd2->data.traverse_all_ext);
+ break;
+
+ case CTDB_CONTROL_IPREALLOCATED:
+ break;
+
+ case CTDB_CONTROL_GET_RUNSTATE:
+ break;
+
+ case CTDB_CONTROL_DB_DETACH:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_GET_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_DB_FREEZE:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_THAW:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_START:
+ verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb);
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_COMMIT:
+ verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb);
+ break;
+
+ case CTDB_CONTROL_DB_TRANSACTION_CANCEL:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_PULL:
+ verify_ctdb_pulldb_ext(cd->data.pulldb_ext,
+ cd2->data.pulldb_ext);
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_START:
+ verify_ctdb_pulldb_ext(cd->data.pulldb_ext,
+ cd2->data.pulldb_ext);
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_CONFIRM:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_OPEN_FLAGS:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_REPLICATED:
+ verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name);
+ break;
+
+ case CTDB_CONTROL_CHECK_PID_SRVID:
+ verify_ctdb_pid_srvid(cd->data.pid_srvid, cd2->data.pid_srvid);
+ break;
+
+ case CTDB_CONTROL_TUNNEL_REGISTER:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_DEREGISTER:
+ break;
+
+ case CTDB_CONTROL_VACUUM_FETCH:
+ verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_DB_VACUUM:
+ verify_ctdb_db_vacuum(cd->data.db_vacuum, cd2->data.db_vacuum);
+ break;
+
+ case CTDB_CONTROL_ECHO_DATA:
+ verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data);
+ break;
+
+ case CTDB_CONTROL_DISABLE_NODE:
+ break;
+
+ case CTDB_CONTROL_ENABLE_NODE:
+ break;
+ }
+}
+
+void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c,
+ uint32_t opcode)
+{
+ c->opcode = opcode;
+ c->pad = rand32();
+ c->srvid = rand64();
+ c->client_id = rand32();
+ c->flags = rand32();
+
+ fill_ctdb_req_control_data(mem_ctx, &c->rdata, opcode);
+}
+
+void verify_ctdb_req_control(struct ctdb_req_control *c,
+ struct ctdb_req_control *c2)
+{
+ assert(c->opcode == c2->opcode);
+ assert(c->pad == c2->pad);
+ assert(c->srvid == c2->srvid);
+ assert(c->client_id == c2->client_id);
+ assert(c->flags == c2->flags);
+
+ verify_ctdb_req_control_data(&c->rdata, &c2->rdata);
+}
+
+void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_control_data *cd,
+ uint32_t opcode)
+{
+ cd->opcode = opcode;
+
+ switch (opcode) {
+ case CTDB_CONTROL_PROCESS_EXISTS:
+ break;
+
+ case CTDB_CONTROL_STATISTICS:
+ cd->data.stats = talloc(mem_ctx, struct ctdb_statistics);
+ assert(cd->data.stats != NULL);
+ fill_ctdb_statistics(mem_ctx, cd->data.stats);
+ break;
+
+ case CTDB_CONTROL_PING:
+ break;
+
+ case CTDB_CONTROL_GETDBPATH:
+ fill_ctdb_string(mem_ctx, &cd->data.db_path);
+ assert(cd->data.db_path != NULL);
+ break;
+
+ case CTDB_CONTROL_GETVNNMAP:
+ cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map);
+ assert(cd->data.vnnmap != NULL);
+ fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap);
+ break;
+
+ case CTDB_CONTROL_SETVNNMAP:
+ break;
+
+ case CTDB_CONTROL_GET_DEBUG:
+ cd->data.loglevel = rand_int(5);
+ break;
+
+ case CTDB_CONTROL_SET_DEBUG:
+ break;
+
+ case CTDB_CONTROL_GET_DBMAP:
+ cd->data.dbmap = talloc(mem_ctx, struct ctdb_dbid_map);
+ assert(cd->data.dbmap != NULL);
+ fill_ctdb_dbid_map(mem_ctx, cd->data.dbmap);
+ break;
+
+ case CTDB_CONTROL_GET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_STATISTICS_RESET:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_DATA:
+ break;
+
+ case CTDB_CONTROL_REGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_GET_DBNAME:
+ fill_ctdb_string(mem_ctx, &cd->data.db_name);
+ assert(cd->data.db_name);
+ break;
+
+ case CTDB_CONTROL_ENABLE_SEQNUM:
+ break;
+
+ case CTDB_CONTROL_UPDATE_SEQNUM:
+ break;
+
+ case CTDB_CONTROL_DUMP_MEMORY:
+ fill_ctdb_string(mem_ctx, &cd->data.mem_str);
+ assert(cd->data.mem_str);
+ break;
+
+ case CTDB_CONTROL_GET_PID:
+ break;
+
+ case CTDB_CONTROL_FREEZE:
+ break;
+
+ case CTDB_CONTROL_GET_PNN:
+ break;
+
+ case CTDB_CONTROL_SHUTDOWN:
+ break;
+
+ case CTDB_CONTROL_TCP_CLIENT:
+ break;
+
+ case CTDB_CONTROL_TCP_ADD:
+ break;
+
+ case CTDB_CONTROL_TCP_REMOVE:
+ break;
+
+ case CTDB_CONTROL_STARTUP:
+ break;
+
+ case CTDB_CONTROL_SET_TUNABLE:
+ break;
+
+ case CTDB_CONTROL_GET_TUNABLE:
+ cd->data.tun_value = rand32();
+ break;
+
+ case CTDB_CONTROL_LIST_TUNABLES:
+ cd->data.tun_var_list = talloc(mem_ctx, struct ctdb_var_list);
+ assert(cd->data.tun_var_list != NULL);
+ fill_ctdb_var_list(mem_ctx, cd->data.tun_var_list);
+ break;
+
+ case CTDB_CONTROL_MODIFY_FLAGS:
+ break;
+
+ case CTDB_CONTROL_GET_ALL_TUNABLES:
+ cd->data.tun_list = talloc(mem_ctx, struct ctdb_tunable_list);
+ assert(cd->data.tun_list != NULL);
+ fill_ctdb_tunable_list(mem_ctx, cd->data.tun_list);
+ break;
+
+ case CTDB_CONTROL_GET_TCP_TICKLE_LIST:
+ cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list);
+ assert(cd->data.tickles != NULL);
+ fill_ctdb_tickle_list(mem_ctx, cd->data.tickles);
+ break;
+
+ case CTDB_CONTROL_SET_TCP_TICKLE_LIST:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_PERSISTENT:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_UPDATE_RECORD:
+ break;
+
+ case CTDB_CONTROL_SEND_GRATUITOUS_ARP:
+ break;
+
+ case CTDB_CONTROL_WIPE_DATABASE:
+ break;
+
+ case CTDB_CONTROL_UPTIME:
+ cd->data.uptime = talloc(mem_ctx, struct ctdb_uptime);
+ assert(cd->data.uptime != NULL);
+ fill_ctdb_uptime(mem_ctx, cd->data.uptime);
+ break;
+
+ case CTDB_CONTROL_START_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_END_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_TRY_DELETE_RECORDS:
+ cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(cd->data.recbuf != NULL);
+ fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_ADD_PUBLIC_IP:
+ break;
+
+ case CTDB_CONTROL_DEL_PUBLIC_IP:
+ break;
+
+ case CTDB_CONTROL_GET_CAPABILITIES:
+ cd->data.caps = rand32();
+ break;
+
+ case CTDB_CONTROL_RECD_PING:
+ break;
+
+ case CTDB_CONTROL_RELEASE_IP:
+ break;
+
+ case CTDB_CONTROL_TAKEOVER_IP:
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IPS:
+ cd->data.pubip_list = talloc(mem_ctx, struct ctdb_public_ip_list);
+ assert(cd->data.pubip_list != NULL);
+ fill_ctdb_public_ip_list(mem_ctx, cd->data.pubip_list);
+ break;
+
+ case CTDB_CONTROL_GET_NODEMAP:
+ cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map);
+ assert(cd->data.nodemap != NULL);
+ fill_ctdb_node_map(mem_ctx, cd->data.nodemap);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_KILL:
+ break;
+
+ case CTDB_CONTROL_RECD_RECLOCK_LATENCY:
+ break;
+
+ case CTDB_CONTROL_GET_RECLOCK_FILE:
+ fill_ctdb_string(mem_ctx, &cd->data.reclock_file);
+ assert(cd->data.reclock_file != NULL);
+ break;
+
+ case CTDB_CONTROL_STOP_NODE:
+ break;
+
+ case CTDB_CONTROL_CONTINUE_NODE:
+ break;
+
+ case CTDB_CONTROL_SET_LMASTERROLE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMASTERROLE:
+ break;
+
+ case CTDB_CONTROL_SET_BAN_STATE:
+ break;
+
+ case CTDB_CONTROL_GET_BAN_STATE:
+ cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state);
+ assert(cd->data.ban_state != NULL);
+ fill_ctdb_ban_state(mem_ctx, cd->data.ban_state);
+ break;
+
+ case CTDB_CONTROL_REGISTER_NOTIFY:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_NOTIFY:
+ break;
+
+ case CTDB_CONTROL_TRANS3_COMMIT:
+ break;
+
+ case CTDB_CONTROL_GET_DB_SEQNUM:
+ cd->data.seqnum = rand64();
+ break;
+
+ case CTDB_CONTROL_DB_SET_HEALTHY:
+ break;
+
+ case CTDB_CONTROL_DB_GET_HEALTH:
+ fill_ctdb_string(mem_ctx, &cd->data.reason);
+ assert(cd->data.reason != NULL);
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IP_INFO:
+ cd->data.ipinfo = talloc(mem_ctx, struct ctdb_public_ip_info);
+ assert(cd->data.ipinfo != NULL);
+ fill_ctdb_public_ip_info(mem_ctx, cd->data.ipinfo);
+ break;
+
+ case CTDB_CONTROL_GET_IFACES:
+ cd->data.iface_list = talloc(mem_ctx, struct ctdb_iface_list);
+ assert(cd->data.iface_list != NULL);
+ fill_ctdb_iface_list(mem_ctx, cd->data.iface_list);
+ break;
+
+ case CTDB_CONTROL_SET_IFACE_LINK_STATE:
+ break;
+
+ case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE:
+ break;
+
+ case CTDB_CONTROL_GET_STAT_HISTORY:
+ cd->data.stats_list = talloc(mem_ctx, struct ctdb_statistics_list);
+ assert(cd->data.stats_list != NULL);
+ fill_ctdb_statistics_list(mem_ctx, cd->data.stats_list);
+ break;
+
+ case CTDB_CONTROL_SCHEDULE_FOR_DELETION:
+ break;
+
+ case CTDB_CONTROL_SET_DB_READONLY:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START_EXT:
+ break;
+
+ case CTDB_CONTROL_GET_DB_STATISTICS:
+ cd->data.dbstats = talloc(mem_ctx, struct ctdb_db_statistics);
+ assert(cd->data.dbstats != NULL);
+ fill_ctdb_db_statistics(mem_ctx, cd->data.dbstats);
+ break;
+
+ case CTDB_CONTROL_SET_DB_STICKY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL_EXT:
+ break;
+
+ case CTDB_CONTROL_IPREALLOCATED:
+ break;
+
+ case CTDB_CONTROL_GET_RUNSTATE:
+ cd->data.runstate = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_DETACH:
+ break;
+
+ case CTDB_CONTROL_GET_NODES_FILE:
+ cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map);
+ assert(cd->data.nodemap != NULL);
+ fill_ctdb_node_map(mem_ctx, cd->data.nodemap);
+ break;
+
+ case CTDB_CONTROL_DB_PULL:
+ cd->data.num_records = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_CONFIRM:
+ cd->data.num_records = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_OPEN_FLAGS:
+ cd->data.tdb_flags = rand32();
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_REPLICATED:
+ cd->data.db_id = rand32();
+ break;
+
+ case CTDB_CONTROL_CHECK_PID_SRVID:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_REGISTER:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_DEREGISTER:
+ break;
+
+ case CTDB_CONTROL_VACUUM_FETCH:
+ break;
+
+ case CTDB_CONTROL_DB_VACUUM:
+ break;
+
+ case CTDB_CONTROL_ECHO_DATA:
+ cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data);
+ assert(cd->data.echo_data != NULL);
+ fill_ctdb_echo_data(mem_ctx, cd->data.echo_data);
+ break;
+
+ case CTDB_CONTROL_DISABLE_NODE:
+ break;
+
+ case CTDB_CONTROL_ENABLE_NODE:
+ break;
+ }
+}
+
+void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd,
+ struct ctdb_reply_control_data *cd2)
+{
+ assert(cd->opcode == cd2->opcode);
+
+ switch (cd->opcode) {
+ case CTDB_CONTROL_PROCESS_EXISTS:
+ break;
+
+ case CTDB_CONTROL_STATISTICS:
+ verify_ctdb_statistics(cd->data.stats, cd2->data.stats);
+ break;
+
+ case CTDB_CONTROL_PING:
+ break;
+
+ case CTDB_CONTROL_GETDBPATH:
+ verify_ctdb_string(&cd->data.db_path, &cd2->data.db_path);
+ break;
+
+ case CTDB_CONTROL_GETVNNMAP:
+ verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap);
+ break;
+
+ case CTDB_CONTROL_SETVNNMAP:
+ break;
+
+ case CTDB_CONTROL_GET_DEBUG:
+ assert(cd->data.loglevel == cd2->data.loglevel);
+ break;
+
+ case CTDB_CONTROL_SET_DEBUG:
+ break;
+
+ case CTDB_CONTROL_GET_DBMAP:
+ verify_ctdb_dbid_map(cd->data.dbmap, cd2->data.dbmap);
+ break;
+
+ case CTDB_CONTROL_GET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMODE:
+ break;
+
+ case CTDB_CONTROL_STATISTICS_RESET:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_DATA:
+ break;
+
+ case CTDB_CONTROL_REGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_SRVID:
+ break;
+
+ case CTDB_CONTROL_GET_DBNAME:
+ verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name);
+ break;
+
+ case CTDB_CONTROL_ENABLE_SEQNUM:
+ break;
+
+ case CTDB_CONTROL_UPDATE_SEQNUM:
+ break;
+
+ case CTDB_CONTROL_DUMP_MEMORY:
+ verify_ctdb_string(&cd->data.mem_str, &cd2->data.mem_str);
+ break;
+
+ case CTDB_CONTROL_GET_PID:
+ break;
+
+ case CTDB_CONTROL_FREEZE:
+ break;
+
+ case CTDB_CONTROL_GET_PNN:
+ break;
+
+ case CTDB_CONTROL_SHUTDOWN:
+ break;
+
+ case CTDB_CONTROL_TCP_CLIENT:
+ break;
+
+ case CTDB_CONTROL_TCP_ADD:
+ break;
+
+ case CTDB_CONTROL_TCP_REMOVE:
+ break;
+
+ case CTDB_CONTROL_STARTUP:
+ break;
+
+ case CTDB_CONTROL_SET_TUNABLE:
+ break;
+
+ case CTDB_CONTROL_GET_TUNABLE:
+ assert(cd->data.tun_value == cd2->data.tun_value);
+ break;
+
+ case CTDB_CONTROL_LIST_TUNABLES:
+ verify_ctdb_var_list(cd->data.tun_var_list,
+ cd2->data.tun_var_list);
+ break;
+
+ case CTDB_CONTROL_MODIFY_FLAGS:
+ break;
+
+ case CTDB_CONTROL_GET_ALL_TUNABLES:
+ verify_ctdb_tunable_list(cd->data.tun_list, cd2->data.tun_list);
+ break;
+
+ case CTDB_CONTROL_GET_TCP_TICKLE_LIST:
+ verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles);
+ break;
+
+ case CTDB_CONTROL_SET_TCP_TICKLE_LIST:
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_PERSISTENT:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_UPDATE_RECORD:
+ break;
+
+ case CTDB_CONTROL_SEND_GRATUITOUS_ARP:
+ break;
+
+ case CTDB_CONTROL_WIPE_DATABASE:
+ break;
+
+ case CTDB_CONTROL_UPTIME:
+ verify_ctdb_uptime(cd->data.uptime, cd2->data.uptime);
+ break;
+
+ case CTDB_CONTROL_START_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_END_RECOVERY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_NODES_FILE:
+ break;
+
+ case CTDB_CONTROL_TRY_DELETE_RECORDS:
+ verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf);
+ break;
+
+ case CTDB_CONTROL_ADD_PUBLIC_IP:
+ break;
+
+ case CTDB_CONTROL_DEL_PUBLIC_IP:
+ break;
+
+ case CTDB_CONTROL_GET_CAPABILITIES:
+ assert(cd->data.caps == cd2->data.caps);
+ break;
+
+ case CTDB_CONTROL_RECD_PING:
+ break;
+
+ case CTDB_CONTROL_RELEASE_IP:
+ break;
+
+ case CTDB_CONTROL_TAKEOVER_IP:
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IPS:
+ verify_ctdb_public_ip_list(cd->data.pubip_list,
+ cd2->data.pubip_list);
+ break;
+
+ case CTDB_CONTROL_GET_NODEMAP:
+ verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap);
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_KILL:
+ break;
+
+ case CTDB_CONTROL_RECD_RECLOCK_LATENCY:
+ break;
+
+ case CTDB_CONTROL_GET_RECLOCK_FILE:
+ verify_ctdb_string(&cd->data.reclock_file,
+ &cd2->data.reclock_file);
+ break;
+
+ case CTDB_CONTROL_STOP_NODE:
+ break;
+
+ case CTDB_CONTROL_CONTINUE_NODE:
+ break;
+
+ case CTDB_CONTROL_SET_LMASTERROLE:
+ break;
+
+ case CTDB_CONTROL_SET_RECMASTERROLE:
+ break;
+
+ case CTDB_CONTROL_SET_BAN_STATE:
+ break;
+
+ case CTDB_CONTROL_GET_BAN_STATE:
+ verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state);
+ break;
+
+ case CTDB_CONTROL_REGISTER_NOTIFY:
+ break;
+
+ case CTDB_CONTROL_DEREGISTER_NOTIFY:
+ break;
+
+ case CTDB_CONTROL_TRANS3_COMMIT:
+ break;
+
+ case CTDB_CONTROL_GET_DB_SEQNUM:
+ assert(cd->data.seqnum == cd2->data.seqnum);
+ break;
+
+ case CTDB_CONTROL_DB_SET_HEALTHY:
+ break;
+
+ case CTDB_CONTROL_DB_GET_HEALTH:
+ verify_ctdb_string(&cd->data.reason, &cd2->data.reason);
+ break;
+
+ case CTDB_CONTROL_GET_PUBLIC_IP_INFO:
+ verify_ctdb_public_ip_info(cd->data.ipinfo, cd2->data.ipinfo);
+ break;
+
+ case CTDB_CONTROL_GET_IFACES:
+ verify_ctdb_iface_list(cd->data.iface_list,
+ cd2->data.iface_list);
+ break;
+
+ case CTDB_CONTROL_SET_IFACE_LINK_STATE:
+ break;
+
+ case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE:
+ break;
+
+ case CTDB_CONTROL_GET_STAT_HISTORY:
+ verify_ctdb_statistics_list(cd->data.stats_list,
+ cd2->data.stats_list);
+ break;
+
+ case CTDB_CONTROL_SCHEDULE_FOR_DELETION:
+ break;
+
+ case CTDB_CONTROL_SET_DB_READONLY:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_START_EXT:
+ break;
+
+ case CTDB_CONTROL_GET_DB_STATISTICS:
+ verify_ctdb_db_statistics(cd->data.dbstats, cd2->data.dbstats);
+ break;
+
+ case CTDB_CONTROL_SET_DB_STICKY:
+ break;
+
+ case CTDB_CONTROL_RELOAD_PUBLIC_IPS:
+ break;
+
+ case CTDB_CONTROL_TRAVERSE_ALL_EXT:
+ break;
+
+ case CTDB_CONTROL_IPREALLOCATED:
+ break;
+
+ case CTDB_CONTROL_GET_RUNSTATE:
+ assert(cd->data.runstate == cd2->data.runstate);
+ break;
+
+ case CTDB_CONTROL_DB_DETACH:
+ break;
+
+ case CTDB_CONTROL_GET_NODES_FILE:
+ verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap);
+ break;
+
+ case CTDB_CONTROL_DB_PULL:
+ assert(cd->data.num_records == cd2->data.num_records);
+ break;
+
+ case CTDB_CONTROL_DB_PUSH_CONFIRM:
+ assert(cd->data.num_records == cd2->data.num_records);
+ break;
+
+ case CTDB_CONTROL_DB_OPEN_FLAGS:
+ assert(cd->data.tdb_flags == cd2->data.tdb_flags);
+ break;
+
+ case CTDB_CONTROL_DB_ATTACH_REPLICATED:
+ assert(cd->data.db_id == cd2->data.db_id);
+ break;
+
+ case CTDB_CONTROL_CHECK_PID_SRVID:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_REGISTER:
+ break;
+
+ case CTDB_CONTROL_TUNNEL_DEREGISTER:
+ break;
+
+ case CTDB_CONTROL_VACUUM_FETCH:
+ break;
+
+ case CTDB_CONTROL_DB_VACUUM:
+ break;
+
+ case CTDB_CONTROL_ECHO_DATA:
+ verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data);
+ break;
+
+ case CTDB_CONTROL_DISABLE_NODE:
+ break;
+
+ case CTDB_CONTROL_ENABLE_NODE:
+ break;
+ }
+}
+
+void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_control *c, uint32_t opcode)
+{
+ c->status = -rand_int(2);
+ if (c->status == 0) {
+ c->errmsg = NULL;
+ fill_ctdb_reply_control_data(mem_ctx, &c->rdata, opcode);
+ } else {
+ fill_ctdb_string(mem_ctx, &c->errmsg);
+ }
+}
+
+void verify_ctdb_reply_control(struct ctdb_reply_control *c,
+ struct ctdb_reply_control *c2)
+{
+ assert(c->status == c2->status);
+ verify_ctdb_string(&c->errmsg, &c2->errmsg);
+ if (c->status == 0) {
+ verify_ctdb_reply_control_data(&c->rdata, &c2->rdata);
+ }
+}
+
+void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md,
+ uint64_t srvid)
+{
+ switch (srvid) {
+ case CTDB_SRVID_RECONFIGURE:
+ case CTDB_SRVID_GETLOG:
+ case CTDB_SRVID_CLEARLOG:
+ case CTDB_SRVID_RELOAD_NODES:
+ break;
+
+ case CTDB_SRVID_ELECTION:
+ md->election = talloc(mem_ctx, struct ctdb_election_message);
+ assert(md->election != NULL);
+ fill_ctdb_election_message(md->election, md->election);
+ break;
+
+ case CTDB_SRVID_RELEASE_IP:
+ case CTDB_SRVID_TAKE_IP:
+ fill_ctdb_string(mem_ctx, &md->ipaddr);
+ break;
+
+ case CTDB_SRVID_SET_NODE_FLAGS:
+ case CTDB_SRVID_PUSH_NODE_FLAGS:
+ md->flag_change = talloc(mem_ctx,
+ struct ctdb_node_flag_change);
+ assert(md->flag_change != NULL);
+ fill_ctdb_node_flag_change(md->flag_change, md->flag_change);
+ break;
+
+ case CTDB_SRVID_RECD_UPDATE_IP:
+ md->pubip = talloc(mem_ctx, struct ctdb_public_ip);
+ assert(md->pubip != NULL);
+ fill_ctdb_public_ip(md->pubip, md->pubip);
+ break;
+
+ case CTDB_SRVID_VACUUM_FETCH:
+ md->recbuf = talloc(mem_ctx, struct ctdb_rec_buffer);
+ assert(md->recbuf != NULL);
+ fill_ctdb_rec_buffer(md->recbuf, md->recbuf);
+ break;
+
+ case CTDB_SRVID_DETACH_DATABASE:
+ md->db_id = rand32();
+ break;
+
+ case CTDB_SRVID_MEM_DUMP:
+ case CTDB_SRVID_TAKEOVER_RUN:
+ md->msg = talloc(mem_ctx, struct ctdb_srvid_message);
+ assert(md->msg != NULL);
+ fill_ctdb_srvid_message(md->msg, md->msg);
+ break;
+
+ case CTDB_SRVID_LEADER:
+ case CTDB_SRVID_BANNING:
+ case CTDB_SRVID_REBALANCE_NODE:
+ md->pnn = rand32();
+ break;
+
+ case CTDB_SRVID_DISABLE_TAKEOVER_RUNS:
+ case CTDB_SRVID_DISABLE_RECOVERIES:
+ md->disable = talloc(mem_ctx, struct ctdb_disable_message);
+ assert(md->disable != NULL);
+ fill_ctdb_disable_message(md->disable, md->disable);
+ break;
+
+ case CTDB_SRVID_DISABLE_IP_CHECK:
+ md->timeout = rand32();
+ break;
+
+ default:
+ abort();
+ }
+}
+
+void verify_ctdb_message_data(union ctdb_message_data *md,
+ union ctdb_message_data *md2, uint64_t srvid)
+{
+ switch (srvid) {
+ case CTDB_SRVID_RECONFIGURE:
+ case CTDB_SRVID_GETLOG:
+ case CTDB_SRVID_CLEARLOG:
+ case CTDB_SRVID_RELOAD_NODES:
+ break;
+
+ case CTDB_SRVID_ELECTION:
+ verify_ctdb_election_message(md->election, md2->election);
+ break;
+
+ case CTDB_SRVID_RELEASE_IP:
+ case CTDB_SRVID_TAKE_IP:
+ verify_ctdb_string(&md->ipaddr, &md2->ipaddr);
+ break;
+
+ case CTDB_SRVID_SET_NODE_FLAGS:
+ case CTDB_SRVID_PUSH_NODE_FLAGS:
+ verify_ctdb_node_flag_change(md->flag_change,
+ md2->flag_change);
+ break;
+
+ case CTDB_SRVID_RECD_UPDATE_IP:
+ verify_ctdb_public_ip(md->pubip, md2->pubip);
+ break;
+
+ case CTDB_SRVID_VACUUM_FETCH:
+ verify_ctdb_rec_buffer(md->recbuf, md2->recbuf);
+ break;
+
+ case CTDB_SRVID_DETACH_DATABASE:
+ assert(md->db_id == md2->db_id);
+ break;
+
+ case CTDB_SRVID_MEM_DUMP:
+ case CTDB_SRVID_TAKEOVER_RUN:
+ verify_ctdb_srvid_message(md->msg, md2->msg);
+ break;
+
+ case CTDB_SRVID_LEADER:
+ case CTDB_SRVID_BANNING:
+ case CTDB_SRVID_REBALANCE_NODE:
+ assert(md->pnn == md2->pnn);
+ break;
+
+ case CTDB_SRVID_DISABLE_TAKEOVER_RUNS:
+ case CTDB_SRVID_DISABLE_RECOVERIES:
+ verify_ctdb_disable_message(md->disable, md2->disable);
+ break;
+
+ case CTDB_SRVID_DISABLE_IP_CHECK:
+ assert(md->timeout == md2->timeout);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c,
+ uint64_t srvid)
+{
+ c->srvid = srvid;
+ fill_ctdb_message_data(mem_ctx, &c->data, srvid);
+}
+
+void verify_ctdb_req_message(struct ctdb_req_message *c,
+ struct ctdb_req_message *c2)
+{
+ assert(c->srvid == c2->srvid);
+ verify_ctdb_message_data(&c->data, &c2->data, c->srvid);
+}
+
+void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_message_data *c)
+{
+ c->srvid = rand64();
+ fill_tdb_data(mem_ctx, &c->data);
+}
+
+void verify_ctdb_req_message_data(struct ctdb_req_message_data *c,
+ struct ctdb_req_message_data *c2)
+{
+ assert(c->srvid == c2->srvid);
+ verify_tdb_data(&c->data, &c2->data);
+}
+
+void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_keepalive *c)
+{
+ c->version = rand32();
+ c->uptime = rand32();
+}
+
+void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c,
+ struct ctdb_req_keepalive *c2)
+{
+ assert(c->version == c2->version);
+ assert(c->uptime == c2->uptime);
+}
+
+void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c)
+{
+ c->tunnel_id = rand64();
+ c->flags = rand32();
+ fill_tdb_data(mem_ctx, &c->data);
+}
+
+void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c,
+ struct ctdb_req_tunnel *c2)
+{
+ assert(c->tunnel_id == c2->tunnel_id);
+ assert(c->flags == c2->flags);
+ verify_tdb_data(&c->data, &c2->data);
+}
diff --git a/ctdb/tests/src/protocol_common_ctdb.h b/ctdb/tests/src/protocol_common_ctdb.h
new file mode 100644
index 0000000..0681089
--- /dev/null
+++ b/ctdb/tests/src/protocol_common_ctdb.h
@@ -0,0 +1,101 @@
+/*
+ protocol tests - ctdb protocol
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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/>.
+*/
+
+#ifndef __CTDB_PROTOCOL_COMMON_CTDB_H__
+#define __CTDB_PROTOCOL_COMMON_CTDB_H__
+
+#include "replace.h"
+#include "system/network.h"
+
+#include <talloc.h>
+#include <tdb.h>
+
+#include "protocol/protocol.h"
+
+void fill_ctdb_req_header(struct ctdb_req_header *h);
+void verify_ctdb_req_header(struct ctdb_req_header *h,
+ struct ctdb_req_header *h2);
+
+void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c);
+void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2);
+
+void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c);
+void verify_ctdb_reply_call(struct ctdb_reply_call *c,
+ struct ctdb_reply_call *c2);
+
+void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c);
+void verify_ctdb_reply_error(struct ctdb_reply_error *c,
+ struct ctdb_reply_error *c2);
+
+void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c);
+void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c,
+ struct ctdb_req_dmaster *c2);
+
+void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_dmaster *c);
+void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c,
+ struct ctdb_reply_dmaster *c2);
+
+void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_control_data *cd,
+ uint32_t opcode);
+void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd,
+ struct ctdb_req_control_data *cd2);
+
+void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c,
+ uint32_t opcode);
+void verify_ctdb_req_control(struct ctdb_req_control *c,
+ struct ctdb_req_control *c2);
+
+void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_control_data *cd,
+ uint32_t opcode);
+void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd,
+ struct ctdb_reply_control_data *cd2);
+
+void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_control *c, uint32_t opcode);
+void verify_ctdb_reply_control(struct ctdb_reply_control *c,
+ struct ctdb_reply_control *c2);
+
+void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md,
+ uint64_t srvid);
+void verify_ctdb_message_data(union ctdb_message_data *md,
+ union ctdb_message_data *md2, uint64_t srvid);
+
+void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c,
+ uint64_t srvid);
+void verify_ctdb_req_message(struct ctdb_req_message *c,
+ struct ctdb_req_message *c2);
+
+void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_message_data *c);
+void verify_ctdb_req_message_data(struct ctdb_req_message_data *c,
+ struct ctdb_req_message_data *c2);
+
+void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx,
+ struct ctdb_req_keepalive *c);
+void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c,
+ struct ctdb_req_keepalive *c2);
+
+void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c);
+void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c,
+ struct ctdb_req_tunnel *c2);
+
+#endif /* __CTDB_PROTOCOL_COMMON_CTDB_H__ */
diff --git a/ctdb/tests/src/protocol_ctdb_compat_test.c b/ctdb/tests/src/protocol_ctdb_compat_test.c
new file mode 100644
index 0000000..fc9f82e
--- /dev/null
+++ b/ctdb/tests/src/protocol_ctdb_compat_test.c
@@ -0,0 +1,1270 @@
+/*
+ ctdb protocol backward compatibility test
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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/filesys.h"
+
+#include <assert.h>
+
+#include "protocol/protocol_basic.c"
+#include "protocol/protocol_types.c"
+#include "protocol/protocol_header.c"
+#include "protocol/protocol_call.c"
+#include "protocol/protocol_control.c"
+#include "protocol/protocol_message.c"
+#include "protocol/protocol_keepalive.c"
+#include "protocol/protocol_tunnel.c"
+
+#include "tests/src/protocol_common.h"
+#include "tests/src/protocol_common_ctdb.h"
+
+#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat
+#define OLD_LEN_FUNC(NAME) NAME## _len_old
+#define OLD_PUSH_FUNC(NAME) NAME## _push_old
+#define OLD_PULL_FUNC(NAME) NAME## _pull_old
+
+#define COMPAT_CTDB1_TEST(TYPE, NAME) \
+static void COMPAT_TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2, np = 0; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(&p); \
+ buflen1 = LEN_FUNC(NAME)(&p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ PUSH_FUNC(NAME)(&p, buf1, &np); \
+ OLD_PUSH_FUNC(NAME)(&p, buf2); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \
+ assert(ret == 0); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+#define COMPAT_CTDB4_TEST(TYPE, NAME, OPER) \
+static void COMPAT_TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ struct ctdb_req_header h, h1, h2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h); \
+ FILL_FUNC(NAME)(mem_ctx, &p); \
+ buflen1 = LEN_FUNC(NAME)(&h, &p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \
+ assert(ret == 0); \
+ ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \
+ assert(ret == 0); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+#define COMPAT_CTDB5_TEST(TYPE, NAME, OPER) \
+static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ struct ctdb_req_header h, h1, h2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h); \
+ FILL_FUNC(NAME)(mem_ctx, &p, opcode); \
+ buflen1 = LEN_FUNC(NAME)(&h, &p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \
+ assert(ret == 0); \
+ ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \
+ assert(ret == 0); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+#define COMPAT_CTDB6_TEST(TYPE, NAME, OPER) \
+static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ struct ctdb_req_header h, h1, h2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h); \
+ FILL_FUNC(NAME)(mem_ctx, &p, opcode); \
+ buflen1 = LEN_FUNC(NAME)(&h, &p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \
+ assert(ret == 0); \
+ ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \
+ assert(ret == 0); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, opcode, &h1, mem_ctx, &p1); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, opcode, &h2, mem_ctx, &p2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+#define COMPAT_CTDB7_TEST(TYPE, NAME, OPER) \
+static void COMPAT_TEST_FUNC(NAME)(uint64_t srvid) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ struct ctdb_req_header h, h1, h2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h); \
+ FILL_FUNC(NAME)(mem_ctx, &p, srvid); \
+ buflen1 = LEN_FUNC(NAME)(&h, &p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \
+ assert(ret == 0); \
+ ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \
+ assert(ret == 0); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+
+static size_t ctdb_req_header_len_old(struct ctdb_req_header *in)
+{
+ return sizeof(struct ctdb_req_header);
+}
+
+static void ctdb_req_header_push_old(struct ctdb_req_header *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_req_header));
+}
+
+static int ctdb_req_header_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *out)
+{
+ if (buflen < sizeof(struct ctdb_req_header)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_req_header));
+ return 0;
+}
+
+struct ctdb_req_call_wire {
+ struct ctdb_req_header hdr;
+ uint32_t flags;
+ uint32_t db_id;
+ uint32_t callid;
+ uint32_t hopcount;
+ uint32_t keylen;
+ uint32_t calldatalen;
+ uint8_t data[1]; /* key[] followed by calldata[] */
+};
+
+static size_t ctdb_req_call_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_call *c)
+{
+ return offsetof(struct ctdb_req_call_wire, data) +
+ ctdb_tdb_data_len(&c->key) +
+ ctdb_tdb_data_len(&c->calldata);
+}
+
+static int ctdb_req_call_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_call *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_call_wire *wire =
+ (struct ctdb_req_call_wire *)buf;
+ size_t length, np;
+
+ if (c->key.dsize == 0) {
+ return EINVAL;
+ }
+
+ length = ctdb_req_call_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->flags = c->flags;
+ wire->db_id = c->db_id;
+ wire->callid = c->callid;
+ wire->hopcount = c->hopcount;
+ wire->keylen = ctdb_tdb_data_len(&c->key);
+ wire->calldatalen = ctdb_tdb_data_len(&c->calldata);
+ ctdb_tdb_data_push(&c->key, wire->data, &np);
+ ctdb_tdb_data_push(&c->calldata, wire->data + wire->keylen, &np);
+
+ return 0;
+}
+
+static int ctdb_req_call_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_call *c)
+{
+ struct ctdb_req_call_wire *wire =
+ (struct ctdb_req_call_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_call_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->keylen > buflen || wire->calldatalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen < length) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen + wire->calldatalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->keylen + wire->calldatalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->flags = wire->flags;
+ c->db_id = wire->db_id;
+ c->callid = wire->callid;
+ c->hopcount = wire->hopcount;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->calldatalen,
+ mem_ctx, &c->calldata, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_reply_call_wire {
+ struct ctdb_req_header hdr;
+ uint32_t status;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_reply_call_len_old(struct ctdb_req_header *h,
+ struct ctdb_reply_call *c)
+{
+ return offsetof(struct ctdb_reply_call_wire, data) +
+ ctdb_tdb_data_len(&c->data);
+}
+
+static int ctdb_reply_call_push_old(struct ctdb_req_header *h,
+ struct ctdb_reply_call *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_reply_call_wire *wire =
+ (struct ctdb_reply_call_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_reply_call_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->status = c->status;
+ wire->datalen = ctdb_tdb_data_len(&c->data);
+ ctdb_tdb_data_push(&c->data, wire->data, &np);
+
+ return 0;
+}
+
+static int ctdb_reply_call_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_call *c)
+{
+ struct ctdb_reply_call_wire *wire =
+ (struct ctdb_reply_call_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_reply_call_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->status = wire->status;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_reply_error_wire {
+ struct ctdb_req_header hdr;
+ uint32_t status;
+ uint32_t msglen;
+ uint8_t msg[1];
+};
+
+static size_t ctdb_reply_error_len_old(struct ctdb_req_header *h,
+ struct ctdb_reply_error *c)
+{
+ return offsetof(struct ctdb_reply_error_wire, msg) +
+ ctdb_tdb_data_len(&c->msg);
+}
+
+static int ctdb_reply_error_push_old(struct ctdb_req_header *h,
+ struct ctdb_reply_error *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_reply_error_wire *wire =
+ (struct ctdb_reply_error_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_reply_error_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->status = c->status;
+ wire->msglen = ctdb_tdb_data_len(&c->msg);
+ ctdb_tdb_data_push(&c->msg, wire->msg, &np);
+
+ return 0;
+}
+
+static int ctdb_reply_error_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_error *c)
+{
+ struct ctdb_reply_error_wire *wire =
+ (struct ctdb_reply_error_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_reply_error_wire, msg);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->msglen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->msglen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->msglen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->status = wire->status;
+
+ ret = ctdb_tdb_data_pull(wire->msg, wire->msglen, mem_ctx, &c->msg,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_req_dmaster_wire {
+ struct ctdb_req_header hdr;
+ uint32_t db_id;
+ uint64_t rsn;
+ uint32_t dmaster;
+ uint32_t keylen;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_req_dmaster_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_dmaster *c)
+{
+ return offsetof(struct ctdb_req_dmaster_wire, data) +
+ ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data);
+}
+
+static int ctdb_req_dmaster_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_dmaster *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_dmaster_wire *wire =
+ (struct ctdb_req_dmaster_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_req_dmaster_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->db_id = c->db_id;
+ wire->rsn = c->rsn;
+ wire->dmaster = c->dmaster;
+ wire->keylen = ctdb_tdb_data_len(&c->key);
+ wire->datalen = ctdb_tdb_data_len(&c->data);
+ ctdb_tdb_data_push(&c->key, wire->data, &np);
+ ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np);
+
+ return 0;
+}
+
+static int ctdb_req_dmaster_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_dmaster *c)
+{
+ struct ctdb_req_dmaster_wire *wire =
+ (struct ctdb_req_dmaster_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_dmaster_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->keylen > buflen || wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen < length) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->keylen + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->db_id = wire->db_id;
+ c->rsn = wire->rsn;
+ c->dmaster = wire->dmaster;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen,
+ mem_ctx, &c->data, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_reply_dmaster_wire {
+ struct ctdb_req_header hdr;
+ uint32_t db_id;
+ uint64_t rsn;
+ uint32_t keylen;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_reply_dmaster_len_old(struct ctdb_req_header *h,
+ struct ctdb_reply_dmaster *c)
+{
+ return offsetof(struct ctdb_reply_dmaster_wire, data) +
+ ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data);
+}
+
+static int ctdb_reply_dmaster_push_old(struct ctdb_req_header *h,
+ struct ctdb_reply_dmaster *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_reply_dmaster_wire *wire =
+ (struct ctdb_reply_dmaster_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_reply_dmaster_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->db_id = c->db_id;
+ wire->rsn = c->rsn;
+ wire->keylen = ctdb_tdb_data_len(&c->key);
+ wire->datalen = ctdb_tdb_data_len(&c->data);
+ ctdb_tdb_data_push(&c->key, wire->data, &np);
+ ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np);
+
+ return 0;
+}
+
+static int ctdb_reply_dmaster_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_dmaster *c)
+{
+ struct ctdb_reply_dmaster_wire *wire =
+ (struct ctdb_reply_dmaster_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_reply_dmaster_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->keylen > buflen || wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen < length) {
+ return EMSGSIZE;
+ }
+ if (length + wire->keylen + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->keylen + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->db_id = wire->db_id;
+ c->rsn = wire->rsn;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen,
+ mem_ctx, &c->data, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_req_control_wire {
+ struct ctdb_req_header hdr;
+ uint32_t opcode;
+ uint32_t pad;
+ uint64_t srvid;
+ uint32_t client_id;
+ uint32_t flags;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_req_control_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_control *c)
+{
+ return offsetof(struct ctdb_req_control_wire, data) +
+ ctdb_req_control_data_len(&c->rdata);
+}
+
+static int ctdb_req_control_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_control *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_control_wire *wire =
+ (struct ctdb_req_control_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_req_control_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->opcode = c->opcode;
+ wire->pad = c->pad;
+ wire->srvid = c->srvid;
+ wire->client_id = c->client_id;
+ wire->flags = c->flags;
+
+ wire->datalen = ctdb_req_control_data_len(&c->rdata);
+ ctdb_req_control_data_push(&c->rdata, wire->data, &np);
+
+ return 0;
+}
+
+static int ctdb_req_control_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_control *c)
+{
+ struct ctdb_req_control_wire *wire =
+ (struct ctdb_req_control_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_control_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->opcode = wire->opcode;
+ c->pad = wire->pad;
+ c->srvid = wire->srvid;
+ c->client_id = wire->client_id;
+ c->flags = wire->flags;
+
+ ret = ctdb_req_control_data_pull(wire->data, wire->datalen,
+ c->opcode, mem_ctx, &c->rdata, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_reply_control_wire {
+ struct ctdb_req_header hdr;
+ int32_t status;
+ uint32_t datalen;
+ uint32_t errorlen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_reply_control_len_old(struct ctdb_req_header *h,
+ struct ctdb_reply_control *c)
+{
+ return offsetof(struct ctdb_reply_control_wire, data) +
+ (c->status == 0 ?
+ ctdb_reply_control_data_len(&c->rdata) :
+ ctdb_string_len(&c->errmsg));
+}
+
+static int ctdb_reply_control_push_old(struct ctdb_req_header *h,
+ struct ctdb_reply_control *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_reply_control_wire *wire =
+ (struct ctdb_reply_control_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_reply_control_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->status = c->status;
+
+ if (c->status == 0) {
+ wire->datalen = ctdb_reply_control_data_len(&c->rdata);
+ wire->errorlen = 0;
+ ctdb_reply_control_data_push(&c->rdata, wire->data, &np);
+ } else {
+ wire->datalen = 0;
+ wire->errorlen = ctdb_string_len(&c->errmsg);
+ ctdb_string_push(&c->errmsg, wire->data + wire->datalen, &np);
+ }
+
+ return 0;
+}
+
+static int ctdb_reply_control_pull_old(uint8_t *buf, size_t buflen,
+ uint32_t opcode,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_reply_control *c)
+{
+ struct ctdb_reply_control_wire *wire =
+ (struct ctdb_reply_control_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_reply_control_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen || wire->errorlen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen + wire->errorlen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen + wire->errorlen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->status = wire->status;
+
+ if (c->status != -1) {
+ ret = ctdb_reply_control_data_pull(wire->data, wire->datalen,
+ opcode, mem_ctx,
+ &c->rdata, &np);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = ctdb_string_pull(wire->data + wire->datalen, wire->errorlen,
+ mem_ctx, &c->errmsg, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_req_message_wire {
+ struct ctdb_req_header hdr;
+ uint64_t srvid;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_req_message_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_message *c)
+{
+ return offsetof(struct ctdb_req_message_wire, data) +
+ ctdb_message_data_len(&c->data, c->srvid);
+}
+
+static int ctdb_req_message_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_message *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_message_wire *wire =
+ (struct ctdb_req_message_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_req_message_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->srvid = c->srvid;
+ wire->datalen = ctdb_message_data_len(&c->data, c->srvid);
+ ctdb_message_data_push(&c->data, c->srvid, wire->data, &np);
+
+ return 0;
+}
+
+static int ctdb_req_message_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_message *c)
+{
+ struct ctdb_req_message_wire *wire =
+ (struct ctdb_req_message_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_message_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->srvid = wire->srvid;
+ ret = ctdb_message_data_pull(wire->data, wire->datalen, wire->srvid,
+ mem_ctx, &c->data, &np);
+ return ret;
+}
+
+static size_t ctdb_req_message_data_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_message_data *c)
+{
+ return offsetof(struct ctdb_req_message_wire, data) +
+ ctdb_tdb_data_len(&c->data);
+}
+
+static int ctdb_req_message_data_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_message_data *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_message_wire *wire =
+ (struct ctdb_req_message_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_req_message_data_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push(h, (uint8_t *)&wire->hdr, &np);
+
+ wire->srvid = c->srvid;
+ wire->datalen = ctdb_tdb_data_len(&c->data);
+ ctdb_tdb_data_push(&c->data, wire->data, &np);
+
+ return 0;
+}
+
+static int ctdb_req_message_data_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_message_data *c)
+{
+ struct ctdb_req_message_wire *wire =
+ (struct ctdb_req_message_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_message_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->srvid = wire->srvid;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->datalen,
+ mem_ctx, &c->data, &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ctdb_req_keepalive_wire {
+ struct ctdb_req_header hdr;
+ uint32_t version;
+ uint32_t uptime;
+};
+
+static size_t ctdb_req_keepalive_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_keepalive *c)
+{
+ return sizeof(struct ctdb_req_keepalive_wire);
+}
+
+static int ctdb_req_keepalive_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_keepalive *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_keepalive_wire *wire =
+ (struct ctdb_req_keepalive_wire *)buf;
+ size_t length;
+
+ length = ctdb_req_keepalive_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->version = c->version;
+ wire->uptime = c->uptime;
+
+ return 0;
+}
+
+static int ctdb_req_keepalive_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_keepalive *c)
+{
+ struct ctdb_req_keepalive_wire *wire =
+ (struct ctdb_req_keepalive_wire *)buf;
+ size_t length;
+ int ret;
+
+ length = sizeof(struct ctdb_req_keepalive_wire);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->version = wire->version;
+ c->uptime = wire->uptime;
+
+ return 0;
+}
+
+struct ctdb_req_tunnel_wire {
+ struct ctdb_req_header hdr;
+ uint64_t tunnel_id;
+ uint32_t flags;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_req_tunnel_len_old(struct ctdb_req_header *h,
+ struct ctdb_req_tunnel *c)
+{
+ return offsetof(struct ctdb_req_tunnel_wire, data) +
+ ctdb_tdb_data_len(&c->data);
+}
+
+static int ctdb_req_tunnel_push_old(struct ctdb_req_header *h,
+ struct ctdb_req_tunnel *c,
+ uint8_t *buf, size_t *buflen)
+{
+ struct ctdb_req_tunnel_wire *wire =
+ (struct ctdb_req_tunnel_wire *)buf;
+ size_t length, np;
+
+ length = ctdb_req_tunnel_len_old(h, c);
+ if (*buflen < length) {
+ *buflen = length;
+ return EMSGSIZE;
+ }
+
+ h->length = *buflen;
+ ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr);
+
+ wire->tunnel_id = c->tunnel_id;
+ wire->flags = c->flags;
+ wire->datalen = ctdb_tdb_data_len(&c->data);
+ ctdb_tdb_data_push(&c->data, wire->data, &np);
+
+ return 0;
+}
+
+static int ctdb_req_tunnel_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_req_header *h,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_tunnel *c)
+{
+ struct ctdb_req_tunnel_wire *wire =
+ (struct ctdb_req_tunnel_wire *)buf;
+ size_t length, np;
+ int ret;
+
+ length = offsetof(struct ctdb_req_tunnel_wire, data);
+ if (buflen < length) {
+ return EMSGSIZE;
+ }
+ if (wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (length + wire->datalen < length) {
+ return EMSGSIZE;
+ }
+ if (buflen < length + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ if (h != NULL) {
+ ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen,
+ h);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ c->tunnel_id = wire->tunnel_id;
+ c->flags = wire->flags;
+
+ ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data,
+ &np);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+
+COMPAT_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header);
+
+COMPAT_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL);
+COMPAT_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL);
+COMPAT_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, CTDB_REPLY_ERROR);
+COMPAT_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, CTDB_REQ_DMASTER);
+COMPAT_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, CTDB_REPLY_DMASTER);
+
+COMPAT_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, CTDB_REQ_CONTROL);
+COMPAT_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, CTDB_REPLY_CONTROL);
+
+COMPAT_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, CTDB_REQ_MESSAGE);
+COMPAT_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, CTDB_REQ_MESSAGE);
+
+COMPAT_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, CTDB_REQ_KEEPALIVE);
+COMPAT_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL);
+
+#define NUM_CONTROLS 151
+
+static void protocol_ctdb_compat_test(void)
+{
+ uint32_t opcode;
+ uint64_t test_srvid[] = {
+ CTDB_SRVID_BANNING,
+ CTDB_SRVID_ELECTION,
+ CTDB_SRVID_LEADER,
+ CTDB_SRVID_RECONFIGURE,
+ CTDB_SRVID_RELEASE_IP,
+ CTDB_SRVID_TAKE_IP,
+ CTDB_SRVID_SET_NODE_FLAGS,
+ CTDB_SRVID_RECD_UPDATE_IP,
+ CTDB_SRVID_VACUUM_FETCH,
+ CTDB_SRVID_DETACH_DATABASE,
+ CTDB_SRVID_MEM_DUMP,
+ CTDB_SRVID_GETLOG,
+ CTDB_SRVID_CLEARLOG,
+ CTDB_SRVID_PUSH_NODE_FLAGS,
+ CTDB_SRVID_RELOAD_NODES,
+ CTDB_SRVID_TAKEOVER_RUN,
+ CTDB_SRVID_REBALANCE_NODE,
+ CTDB_SRVID_DISABLE_TAKEOVER_RUNS,
+ CTDB_SRVID_DISABLE_RECOVERIES,
+ CTDB_SRVID_DISABLE_IP_CHECK,
+ };
+ unsigned int i;
+
+ COMPAT_TEST_FUNC(ctdb_req_header)();
+
+ COMPAT_TEST_FUNC(ctdb_req_call)();
+ COMPAT_TEST_FUNC(ctdb_reply_call)();
+ COMPAT_TEST_FUNC(ctdb_reply_error)();
+ COMPAT_TEST_FUNC(ctdb_req_dmaster)();
+ COMPAT_TEST_FUNC(ctdb_reply_dmaster)();
+
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ COMPAT_TEST_FUNC(ctdb_req_control)(opcode);
+ }
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ COMPAT_TEST_FUNC(ctdb_reply_control)(opcode);
+ }
+
+ for (i=0; i<ARRAY_SIZE(test_srvid); i++) {
+ COMPAT_TEST_FUNC(ctdb_req_message)(test_srvid[i]);
+ }
+ COMPAT_TEST_FUNC(ctdb_req_message_data)();
+
+ COMPAT_TEST_FUNC(ctdb_req_keepalive)();
+ COMPAT_TEST_FUNC(ctdb_req_tunnel)();
+}
+
+int main(int argc, const char *argv[])
+{
+ protocol_test_iterate(argc, argv, protocol_ctdb_compat_test);
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_ctdb_test.c b/ctdb/tests/src/protocol_ctdb_test.c
new file mode 100644
index 0000000..f6fb513
--- /dev/null
+++ b/ctdb/tests/src/protocol_ctdb_test.c
@@ -0,0 +1,365 @@
+/*
+ protocol tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+
+#include "protocol/protocol_basic.c"
+#include "protocol/protocol_types.c"
+#include "protocol/protocol_header.c"
+#include "protocol/protocol_call.c"
+#include "protocol/protocol_control.c"
+#include "protocol/protocol_message.c"
+#include "protocol/protocol_keepalive.c"
+#include "protocol/protocol_tunnel.c"
+#include "protocol/protocol_packet.c"
+
+#include "tests/src/protocol_common.h"
+#include "tests/src/protocol_common_ctdb.h"
+
+/*
+ * Functions to test marshalling
+ */
+
+/* for ctdb_req_header */
+#define PROTOCOL_CTDB1_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, np; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s\n", #NAME); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(&c1); \
+ buflen = LEN_FUNC(NAME)(&c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ np = 0; \
+ PUSH_FUNC(NAME)(&c1, pkt, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, &c2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_req_control_data, ctdb_reply_control_data */
+#define PROTOCOL_CTDB2_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(uint32_t opcode) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, np; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \
+ buflen = LEN_FUNC(NAME)(&c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ np = 0; \
+ PUSH_FUNC(NAME)(&c1, pkt, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, mem_ctx, &c2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_message_data */
+#define PROTOCOL_CTDB3_TEST(TYPE, NAME) \
+static void TEST_FUNC(NAME)(uint64_t srvid) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, np; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \
+ buflen = LEN_FUNC(NAME)(&c1, srvid); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ np = 0; \
+ PUSH_FUNC(NAME)(&c1, srvid, pkt, &np); \
+ assert(np == buflen); \
+ np = 0; \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, srvid, mem_ctx, &c2, &np); \
+ assert(ret == 0); \
+ assert(np == buflen); \
+ VERIFY_FUNC(NAME)(&c1, &c2, srvid); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_req_call, ctdb_reply_call, etc. */
+#define PROTOCOL_CTDB4_TEST(TYPE, NAME, OPER) \
+static void TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ struct ctdb_req_header h1, h2; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, len; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s\n", #NAME); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h1); \
+ FILL_FUNC(NAME)(mem_ctx, &c1); \
+ buflen = LEN_FUNC(NAME)(&h1, &c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ len = 0; \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \
+ assert(ret == EMSGSIZE); \
+ assert(len == buflen); \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \
+ assert(ret == 0); \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ assert(h2.length == pkt_len); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_req_control */
+#define PROTOCOL_CTDB5_TEST(TYPE, NAME, OPER) \
+static void TEST_FUNC(NAME)(uint32_t opcode) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ struct ctdb_req_header h1, h2; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, len; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h1); \
+ FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \
+ buflen = LEN_FUNC(NAME)(&h1, &c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ len = 0; \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \
+ assert(ret == EMSGSIZE); \
+ assert(len == buflen); \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \
+ assert(ret == 0); \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ assert(h2.length == pkt_len); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_reply_control */
+#define PROTOCOL_CTDB6_TEST(TYPE, NAME, OPER) \
+static void TEST_FUNC(NAME)(uint32_t opcode) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ struct ctdb_req_header h1, h2; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, len; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h1); \
+ FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \
+ buflen = LEN_FUNC(NAME)(&h1, &c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ len = 0; \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \
+ assert(ret == EMSGSIZE); \
+ assert(len == buflen); \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \
+ assert(ret == 0); \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, &h2, mem_ctx, &c2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ assert(h2.length == pkt_len); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+/* for ctdb_req_message */
+#define PROTOCOL_CTDB7_TEST(TYPE, NAME, OPER) \
+static void TEST_FUNC(NAME)(uint64_t srvid) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ struct ctdb_req_header h1, h2; \
+ TYPE c1, c2; \
+ uint8_t *pkt; \
+ size_t pkt_len, buflen, len; \
+ int ret; \
+\
+ protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ fill_ctdb_req_header(&h1); \
+ FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \
+ buflen = LEN_FUNC(NAME)(&h1, &c1); \
+ ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \
+ assert(ret == 0); \
+ assert(pkt != NULL); \
+ assert(pkt_len >= buflen); \
+ len = 0; \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \
+ assert(ret == EMSGSIZE); \
+ assert(len == buflen); \
+ ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \
+ assert(ret == 0); \
+ ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \
+ assert(ret == 0); \
+ verify_ctdb_req_header(&h1, &h2); \
+ assert(h2.length == pkt_len); \
+ VERIFY_FUNC(NAME)(&c1, &c2); \
+ talloc_free(mem_ctx); \
+}
+
+PROTOCOL_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header);
+
+PROTOCOL_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL);
+PROTOCOL_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL);
+PROTOCOL_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error,
+ CTDB_REPLY_ERROR);
+PROTOCOL_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster,
+ CTDB_REQ_DMASTER);
+PROTOCOL_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster,
+ CTDB_REPLY_DMASTER);
+
+#define NUM_CONTROLS 159
+
+PROTOCOL_CTDB2_TEST(struct ctdb_req_control_data, ctdb_req_control_data);
+PROTOCOL_CTDB2_TEST(struct ctdb_reply_control_data, ctdb_reply_control_data);
+
+PROTOCOL_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control,
+ CTDB_REQ_CONTROL);
+PROTOCOL_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control,
+ CTDB_REPLY_CONTROL);
+
+PROTOCOL_CTDB3_TEST(union ctdb_message_data, ctdb_message_data);
+PROTOCOL_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message,
+ CTDB_REQ_MESSAGE);
+PROTOCOL_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data,
+ CTDB_REQ_MESSAGE);
+
+PROTOCOL_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive,
+ CTDB_REQ_KEEPALIVE);
+PROTOCOL_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL);
+
+static void protocol_ctdb_test(void)
+{
+ uint32_t opcode;
+ uint64_t test_srvid[] = {
+ CTDB_SRVID_BANNING,
+ CTDB_SRVID_ELECTION,
+ CTDB_SRVID_LEADER,
+ CTDB_SRVID_RECONFIGURE,
+ CTDB_SRVID_RELEASE_IP,
+ CTDB_SRVID_TAKE_IP,
+ CTDB_SRVID_SET_NODE_FLAGS,
+ CTDB_SRVID_RECD_UPDATE_IP,
+ CTDB_SRVID_VACUUM_FETCH,
+ CTDB_SRVID_DETACH_DATABASE,
+ CTDB_SRVID_MEM_DUMP,
+ CTDB_SRVID_GETLOG,
+ CTDB_SRVID_CLEARLOG,
+ CTDB_SRVID_PUSH_NODE_FLAGS,
+ CTDB_SRVID_RELOAD_NODES,
+ CTDB_SRVID_TAKEOVER_RUN,
+ CTDB_SRVID_REBALANCE_NODE,
+ CTDB_SRVID_DISABLE_TAKEOVER_RUNS,
+ CTDB_SRVID_DISABLE_RECOVERIES,
+ CTDB_SRVID_DISABLE_IP_CHECK,
+ };
+ size_t i;
+
+ TEST_FUNC(ctdb_req_header)();
+
+ TEST_FUNC(ctdb_req_call)();
+ TEST_FUNC(ctdb_reply_call)();
+ TEST_FUNC(ctdb_reply_error)();
+ TEST_FUNC(ctdb_req_dmaster)();
+ TEST_FUNC(ctdb_reply_dmaster)();
+
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ TEST_FUNC(ctdb_req_control_data)(opcode);
+ }
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ TEST_FUNC(ctdb_reply_control_data)(opcode);
+ }
+
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ TEST_FUNC(ctdb_req_control)(opcode);
+ }
+ for (opcode=0; opcode<NUM_CONTROLS; opcode++) {
+ TEST_FUNC(ctdb_reply_control)(opcode);
+ }
+
+ for (i=0; i<ARRAY_SIZE(test_srvid); i++) {
+ TEST_FUNC(ctdb_message_data)(test_srvid[i]);
+ }
+ for (i=0; i<ARRAY_SIZE(test_srvid); i++) {
+ TEST_FUNC(ctdb_req_message)(test_srvid[i]);
+ }
+ TEST_FUNC(ctdb_req_message_data)();
+
+ TEST_FUNC(ctdb_req_keepalive)();
+ TEST_FUNC(ctdb_req_tunnel)();
+}
+
+int main(int argc, const char *argv[])
+{
+ protocol_test_iterate(argc, argv, protocol_ctdb_test);
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_types_compat_test.c b/ctdb/tests/src/protocol_types_compat_test.c
new file mode 100644
index 0000000..140ea86
--- /dev/null
+++ b/ctdb/tests/src/protocol_types_compat_test.c
@@ -0,0 +1,2371 @@
+/*
+ protocol types backward compatibility test
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "protocol/protocol_basic.c"
+#include "protocol/protocol_types.c"
+
+#include "tests/src/protocol_common.h"
+
+#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat
+#define OLD_LEN_FUNC(NAME) NAME## _len_old
+#define OLD_PUSH_FUNC(NAME) NAME## _push_old
+#define OLD_PULL_FUNC(NAME) NAME## _pull_old
+
+#define COMPAT_TYPE1_TEST(TYPE, NAME) \
+static void COMPAT_TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ TYPE p = { 0 }, p1, p2; \
+ size_t buflen1, buflen2, np = 0; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ FILL_FUNC(NAME)(&p); \
+ buflen1 = LEN_FUNC(NAME)(&p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(&p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ PUSH_FUNC(NAME)(&p, buf1, &np); \
+ OLD_PUSH_FUNC(NAME)(&p, buf2); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \
+ assert(ret == 0); \
+ VERIFY_FUNC(NAME)(&p1, &p2); \
+ talloc_free(mem_ctx); \
+}
+
+#define COMPAT_TYPE3_TEST(TYPE, NAME) \
+static void COMPAT_TEST_FUNC(NAME)(void) \
+{ \
+ TALLOC_CTX *mem_ctx; \
+ uint8_t *buf1, *buf2; \
+ TYPE *p, *p1, *p2; \
+ size_t buflen1, buflen2, np = 0; \
+ int ret; \
+\
+ mem_ctx = talloc_new(NULL); \
+ assert(mem_ctx != NULL); \
+ p = talloc_zero(mem_ctx, TYPE); \
+ assert(p != NULL); \
+ FILL_FUNC(NAME)(p, p); \
+ buflen1 = LEN_FUNC(NAME)(p); \
+ buflen2 = OLD_LEN_FUNC(NAME)(p); \
+ assert(buflen1 == buflen2); \
+ buf1 = talloc_zero_size(mem_ctx, buflen1); \
+ assert(buf1 != NULL); \
+ buf2 = talloc_zero_size(mem_ctx, buflen2); \
+ assert(buf2 != NULL); \
+ PUSH_FUNC(NAME)(p, buf1, &np); \
+ OLD_PUSH_FUNC(NAME)(p, buf2); \
+ assert(memcmp(buf1, buf2, buflen1) == 0); \
+ ret = PULL_FUNC(NAME)(buf1, buflen1, mem_ctx, &p1, &np); \
+ assert(ret == 0); \
+ ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, mem_ctx, &p2); \
+ assert(ret == 0); \
+ VERIFY_FUNC(NAME)(p1, p2); \
+ talloc_free(mem_ctx); \
+}
+
+
+static size_t ctdb_statistics_len_old(struct ctdb_statistics *in)
+{
+ return sizeof(struct ctdb_statistics);
+}
+
+static void ctdb_statistics_push_old(struct ctdb_statistics *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_statistics));
+}
+
+static int ctdb_statistics_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_statistics **out)
+{
+ struct ctdb_statistics *val;
+
+ if (buflen < sizeof(struct ctdb_statistics)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_statistics);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(val, buf, sizeof(struct ctdb_statistics));
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_vnn_map_wire {
+ uint32_t generation;
+ uint32_t size;
+ uint32_t map[1];
+};
+
+static size_t ctdb_vnn_map_len_old(struct ctdb_vnn_map *in)
+{
+ return offsetof(struct ctdb_vnn_map, map) +
+ in->size * sizeof(uint32_t);
+}
+
+static void ctdb_vnn_map_push_old(struct ctdb_vnn_map *in, uint8_t *buf)
+{
+ struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf;
+
+ memcpy(wire, in, offsetof(struct ctdb_vnn_map, map));
+ memcpy(wire->map, in->map, in->size * sizeof(uint32_t));
+}
+
+static int ctdb_vnn_map_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_vnn_map **out)
+{
+ struct ctdb_vnn_map *val;
+ struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_vnn_map_wire, map)) {
+ return EMSGSIZE;
+ }
+ if (wire->size > buflen / sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_vnn_map_wire, map) +
+ wire->size * sizeof(uint32_t) <
+ offsetof(struct ctdb_vnn_map_wire, map)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_vnn_map_wire, map) +
+ wire->size * sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_vnn_map);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(val, wire, offsetof(struct ctdb_vnn_map, map));
+
+ val->map = talloc_memdup(val, wire->map,
+ wire->size * sizeof(uint32_t));
+ if (val->map == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_dbid_map_wire {
+ uint32_t num;
+ struct ctdb_dbid dbs[1];
+};
+
+static size_t ctdb_dbid_map_len_old(struct ctdb_dbid_map *in)
+{
+ return sizeof(uint32_t) + in->num * sizeof(struct ctdb_dbid);
+}
+
+static void ctdb_dbid_map_push_old(struct ctdb_dbid_map *in, uint8_t *buf)
+{
+ struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf;
+
+ wire->num = in->num;
+ memcpy(wire->dbs, in->dbs, in->num * sizeof(struct ctdb_dbid));
+}
+
+static int ctdb_dbid_map_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_dbid_map **out)
+{
+ struct ctdb_dbid_map *val;
+ struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf;
+
+ if (buflen < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_dbid)) {
+ return EMSGSIZE;
+ }
+ if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid) <
+ sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_dbid_map);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->num = wire->num;
+
+ val->dbs = talloc_memdup(val, wire->dbs,
+ wire->num * sizeof(struct ctdb_dbid));
+ if (val->dbs == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_pulldb_len_old(struct ctdb_pulldb *in)
+{
+ return sizeof(struct ctdb_pulldb);
+}
+
+static void ctdb_pulldb_push_old(struct ctdb_pulldb *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_pulldb));
+}
+
+static int ctdb_pulldb_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx, struct ctdb_pulldb **out)
+{
+ struct ctdb_pulldb *val;
+
+ if (buflen < sizeof(struct ctdb_pulldb)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_pulldb_ext_len_old(struct ctdb_pulldb_ext *in)
+{
+ return sizeof(struct ctdb_pulldb_ext);
+}
+
+static void ctdb_pulldb_ext_push_old(struct ctdb_pulldb_ext *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_pulldb_ext));
+}
+
+static int ctdb_pulldb_ext_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_pulldb_ext **out)
+{
+ struct ctdb_pulldb_ext *val;
+
+ if (buflen < sizeof(struct ctdb_pulldb_ext)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb_ext));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_ltdb_header_len_old(struct ctdb_ltdb_header *in)
+{
+ return sizeof(struct ctdb_ltdb_header);
+}
+
+static void ctdb_ltdb_header_push_old(struct ctdb_ltdb_header *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_ltdb_header));
+}
+
+static int ctdb_ltdb_header_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_ltdb_header *out)
+{
+ if (buflen < sizeof(struct ctdb_ltdb_header)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_ltdb_header));
+ return 0;
+}
+
+struct ctdb_rec_data_wire {
+ uint32_t length;
+ uint32_t reqid;
+ uint32_t keylen;
+ uint32_t datalen;
+ uint8_t data[1];
+};
+
+static size_t ctdb_rec_data_len_old(struct ctdb_rec_data *in)
+{
+ return offsetof(struct ctdb_rec_data_wire, data) +
+ in->key.dsize + in->data.dsize +
+ (in->header == NULL ? 0 : sizeof(struct ctdb_ltdb_header));
+}
+
+static void ctdb_rec_data_push_old(struct ctdb_rec_data *in, uint8_t *buf)
+{
+ struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf;
+ size_t offset;
+
+ wire->length = ctdb_rec_data_len(in);
+ wire->reqid = in->reqid;
+ wire->keylen = in->key.dsize;
+ wire->datalen = in->data.dsize;
+ if (in->header != NULL) {
+ wire->datalen += sizeof(struct ctdb_ltdb_header);
+ }
+
+ memcpy(wire->data, in->key.dptr, in->key.dsize);
+ offset = in->key.dsize;
+ if (in->header != NULL) {
+ memcpy(&wire->data[offset], in->header,
+ sizeof(struct ctdb_ltdb_header));
+ offset += sizeof(struct ctdb_ltdb_header);
+ }
+ if (in->data.dsize > 0) {
+ memcpy(&wire->data[offset], in->data.dptr, in->data.dsize);
+ }
+}
+
+static int ctdb_rec_data_pull_data_old(uint8_t *buf, size_t buflen,
+ uint32_t *reqid,
+ struct ctdb_ltdb_header **header,
+ TDB_DATA *key, TDB_DATA *data,
+ size_t *reclen)
+{
+ struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf;
+ size_t offset;
+
+ if (buflen < offsetof(struct ctdb_rec_data_wire, data)) {
+ return EMSGSIZE;
+ }
+ if (wire->keylen > buflen || wire->datalen > buflen) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_rec_data_wire, data) + wire->keylen <
+ offsetof(struct ctdb_rec_data_wire, data)) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_rec_data_wire, data) +
+ wire->keylen + wire->datalen <
+ offsetof(struct ctdb_rec_data_wire, data)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_rec_data_wire, data) +
+ wire->keylen + wire->datalen) {
+ return EMSGSIZE;
+ }
+
+ *reqid = wire->reqid;
+
+ key->dsize = wire->keylen;
+ key->dptr = wire->data;
+ offset = wire->keylen;
+
+ /* Always set header to NULL. If it is required, exact it using
+ * ctdb_rec_data_extract_header()
+ */
+ *header = NULL;
+
+ data->dsize = wire->datalen;
+ data->dptr = &wire->data[offset];
+
+ *reclen = offsetof(struct ctdb_rec_data_wire, data) +
+ wire->keylen + wire->datalen;
+
+ return 0;
+}
+
+static int ctdb_rec_data_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_rec_data *out)
+{
+ uint32_t reqid;
+ struct ctdb_ltdb_header *header;
+ TDB_DATA key, data;
+ size_t reclen;
+ int ret;
+
+ ret = ctdb_rec_data_pull_data_old(buf, buflen, &reqid, &header,
+ &key, &data, &reclen);
+ if (ret != 0) {
+ return ret;
+ }
+
+ out->reqid = reqid;
+ out->header = NULL;
+
+ out->key.dsize = key.dsize;
+ if (key.dsize > 0) {
+ out->key.dptr = talloc_memdup(mem_ctx, key.dptr, key.dsize);
+ if (out->key.dptr == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ out->data.dsize = data.dsize;
+ if (data.dsize > 0) {
+ out->data.dptr = talloc_memdup(mem_ctx, data.dptr, data.dsize);
+ if (out->data.dptr == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int ctdb_rec_data_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_rec_data **out)
+{
+ struct ctdb_rec_data *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_rec_data);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_rec_data_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_rec_buffer_wire {
+ uint32_t db_id;
+ uint32_t count;
+ uint8_t data[1];
+};
+
+static size_t ctdb_rec_buffer_len_old(struct ctdb_rec_buffer *in)
+{
+ return offsetof(struct ctdb_rec_buffer_wire, data) + in->buflen;
+}
+
+static void ctdb_rec_buffer_push_old(struct ctdb_rec_buffer *in, uint8_t *buf)
+{
+ struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf;
+
+ wire->db_id = in->db_id;
+ wire->count = in->count;
+ if (in->buflen > 0) {
+ memcpy(wire->data, in->buf, in->buflen);
+ }
+}
+
+static int ctdb_rec_buffer_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_rec_buffer **out)
+{
+ struct ctdb_rec_buffer *val;
+ struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf;
+ size_t offset;
+
+ if (buflen < offsetof(struct ctdb_rec_buffer_wire, data)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_rec_buffer);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->db_id = wire->db_id;
+ val->count = wire->count;
+
+ offset = offsetof(struct ctdb_rec_buffer_wire, data);
+ val->buflen = buflen - offset;
+ val->buf = talloc_memdup(val, wire->data, val->buflen);
+ if (val->buf == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_traverse_start_len_old(struct ctdb_traverse_start *in)
+{
+ return sizeof(struct ctdb_traverse_start);
+}
+
+static void ctdb_traverse_start_push_old(struct ctdb_traverse_start *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_traverse_start));
+}
+
+static int ctdb_traverse_start_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start **out)
+{
+ struct ctdb_traverse_start *val;
+
+ if (buflen < sizeof(struct ctdb_traverse_start)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_start));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_traverse_all_len_old(struct ctdb_traverse_all *in)
+{
+ return sizeof(struct ctdb_traverse_all);
+}
+
+static void ctdb_traverse_all_push_old(struct ctdb_traverse_all *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_traverse_all));
+}
+
+static int ctdb_traverse_all_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all **out)
+{
+ struct ctdb_traverse_all *val;
+
+ if (buflen < sizeof(struct ctdb_traverse_all)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_all));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_traverse_start_ext_len_old(
+ struct ctdb_traverse_start_ext *in)
+{
+ return sizeof(struct ctdb_traverse_start_ext);
+}
+
+static void ctdb_traverse_start_ext_push_old(
+ struct ctdb_traverse_start_ext *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_traverse_start_ext));
+}
+
+static int ctdb_traverse_start_ext_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_start_ext **out)
+{
+ struct ctdb_traverse_start_ext *val;
+
+ if (buflen < sizeof(struct ctdb_traverse_start_ext)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf,
+ sizeof(struct ctdb_traverse_start_ext));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_traverse_all_ext_len_old(struct ctdb_traverse_all_ext *in)
+{
+ return sizeof(struct ctdb_traverse_all_ext);
+}
+
+static void ctdb_traverse_all_ext_push_old(struct ctdb_traverse_all_ext *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_traverse_all_ext));
+}
+
+static int ctdb_traverse_all_ext_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_traverse_all_ext **out)
+{
+ struct ctdb_traverse_all_ext *val;
+
+ if (buflen < sizeof(struct ctdb_traverse_all_ext)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf,
+ sizeof(struct ctdb_traverse_all_ext));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_sock_addr_len_old(ctdb_sock_addr *in)
+{
+ return sizeof(ctdb_sock_addr);
+}
+
+static void ctdb_sock_addr_push_old(ctdb_sock_addr *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(ctdb_sock_addr));
+}
+
+static int ctdb_sock_addr_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ ctdb_sock_addr *out)
+{
+ if (buflen < sizeof(ctdb_sock_addr)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(ctdb_sock_addr));
+
+ return 0;
+}
+
+static int ctdb_sock_addr_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx, ctdb_sock_addr **out)
+{
+ ctdb_sock_addr *val;
+ int ret;
+
+ val = talloc(mem_ctx, ctdb_sock_addr);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_sock_addr_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+static size_t ctdb_connection_len_old(struct ctdb_connection *in)
+{
+ return sizeof(struct ctdb_connection);
+}
+
+static void ctdb_connection_push_old(struct ctdb_connection *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_connection));
+}
+
+static int ctdb_connection_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_connection *out)
+{
+ if (buflen < sizeof(struct ctdb_connection)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_connection));
+
+ return 0;
+}
+
+static int ctdb_connection_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_connection **out)
+{
+ struct ctdb_connection *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_connection);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_connection_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_tunable_wire {
+ uint32_t value;
+ uint32_t length;
+ uint8_t name[1];
+};
+
+static size_t ctdb_tunable_len_old(struct ctdb_tunable *in)
+{
+ return offsetof(struct ctdb_tunable_wire, name) +
+ strlen(in->name) + 1;
+}
+
+static void ctdb_tunable_push_old(struct ctdb_tunable *in, uint8_t *buf)
+{
+ struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf;
+
+ wire->value = in->value;
+ wire->length = strlen(in->name) + 1;
+ memcpy(wire->name, in->name, wire->length);
+}
+
+static int ctdb_tunable_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_tunable **out)
+{
+ struct ctdb_tunable *val;
+ struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_tunable_wire, name)) {
+ return EMSGSIZE;
+ }
+ if (wire->length > buflen) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_tunable_wire, name) + wire->length <
+ offsetof(struct ctdb_tunable_wire, name)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_tunable_wire, name) + wire->length) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_tunable);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->value = wire->value;
+ val->name = talloc_memdup(val, wire->name, wire->length);
+ if (val->name == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_node_flag_change_len_old(struct ctdb_node_flag_change *in)
+{
+ return sizeof(struct ctdb_node_flag_change);
+}
+
+static void ctdb_node_flag_change_push_old(struct ctdb_node_flag_change *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_node_flag_change));
+}
+
+static int ctdb_node_flag_change_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_node_flag_change **out)
+{
+ struct ctdb_node_flag_change *val;
+
+ if (buflen < sizeof(struct ctdb_node_flag_change)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf,
+ sizeof(struct ctdb_node_flag_change));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_var_list_wire {
+ uint32_t length;
+ char list_str[1];
+};
+
+static size_t ctdb_var_list_len_old(struct ctdb_var_list *in)
+{
+ int i;
+ size_t len = sizeof(uint32_t);
+
+ for (i=0; i<in->count; i++) {
+ assert(in->var[i] != NULL);
+ len += strlen(in->var[i]) + 1;
+ }
+ return len;
+}
+
+static void ctdb_var_list_push_old(struct ctdb_var_list *in, uint8_t *buf)
+{
+ struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf;
+ int i, n;
+ size_t offset = 0;
+
+ if (in->count > 0) {
+ n = sprintf(wire->list_str, "%s", in->var[0]);
+ offset += n;
+ }
+ for (i=1; i<in->count; i++) {
+ n = sprintf(&wire->list_str[offset], ":%s", in->var[i]);
+ offset += n;
+ }
+ wire->length = offset + 1;
+}
+
+static int ctdb_var_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_var_list **out)
+{
+ struct ctdb_var_list *val = NULL;
+ struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf;
+ char *str, *s, *tok, *ptr;
+ const char **list;
+
+ if (buflen < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (wire->length > buflen) {
+ return EMSGSIZE;
+ }
+ if (sizeof(uint32_t) + wire->length < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(uint32_t) + wire->length) {
+ return EMSGSIZE;
+ }
+
+ str = talloc_strndup(mem_ctx, (char *)wire->list_str, wire->length);
+ if (str == NULL) {
+ return ENOMEM;
+ }
+
+ val = talloc_zero(mem_ctx, struct ctdb_var_list);
+ if (val == NULL) {
+ goto fail;
+ }
+
+ s = str;
+ while ((tok = strtok_r(s, ":", &ptr)) != NULL) {
+ s = NULL;
+ list = talloc_realloc(val, val->var, const char *,
+ val->count+1);
+ if (list == NULL) {
+ goto fail;
+ }
+
+ val->var = list;
+ val->var[val->count] = talloc_strdup(val, tok);
+ if (val->var[val->count] == NULL) {
+ goto fail;
+ }
+ val->count++;
+ }
+
+ talloc_free(str);
+ *out = val;
+ return 0;
+
+fail:
+ talloc_free(str);
+ talloc_free(val);
+ return ENOMEM;
+}
+
+static size_t ctdb_tunable_list_len_old(struct ctdb_tunable_list *in)
+{
+ return sizeof(struct ctdb_tunable_list);
+}
+
+static void ctdb_tunable_list_push_old(struct ctdb_tunable_list *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_tunable_list));
+}
+
+static int ctdb_tunable_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_tunable_list **out)
+{
+ struct ctdb_tunable_list *val;
+
+ if (buflen < sizeof(struct ctdb_tunable_list)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_tunable_list));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_tickle_list_wire {
+ ctdb_sock_addr addr;
+ uint32_t num;
+ struct ctdb_connection conn[1];
+};
+
+static size_t ctdb_tickle_list_len_old(struct ctdb_tickle_list *in)
+{
+ return offsetof(struct ctdb_tickle_list, conn) +
+ in->num * sizeof(struct ctdb_connection);
+}
+
+static void ctdb_tickle_list_push_old(struct ctdb_tickle_list *in,
+ uint8_t *buf)
+{
+ struct ctdb_tickle_list_wire *wire =
+ (struct ctdb_tickle_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+
+ memcpy(&wire->addr, &in->addr, sizeof(ctdb_sock_addr));
+ wire->num = in->num;
+
+ offset = offsetof(struct ctdb_tickle_list_wire, conn);
+ for (i=0; i<in->num; i++) {
+ ctdb_connection_push_old(&in->conn[i], &buf[offset]);
+ offset += ctdb_connection_len_old(&in->conn[i]);
+ }
+}
+
+static int ctdb_tickle_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_tickle_list **out)
+{
+ struct ctdb_tickle_list *val;
+ struct ctdb_tickle_list_wire *wire =
+ (struct ctdb_tickle_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+ int ret;
+
+ if (buflen < offsetof(struct ctdb_tickle_list_wire, conn)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_connection)) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_tickle_list_wire, conn) +
+ wire->num * sizeof(struct ctdb_connection) <
+ offsetof(struct ctdb_tickle_list_wire, conn)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_tickle_list_wire, conn) +
+ wire->num * sizeof(struct ctdb_connection)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_tickle_list);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ offset = offsetof(struct ctdb_tickle_list, conn);
+ memcpy(val, wire, offset);
+
+ val->conn = talloc_array(val, struct ctdb_connection, wire->num);
+ if (val->conn == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ for (i=0; i<wire->num; i++) {
+ ret = ctdb_connection_pull_elems_old(&buf[offset],
+ buflen-offset,
+ val->conn,
+ &val->conn[i]);
+ if (ret != 0) {
+ talloc_free(val);
+ return ret;
+ }
+ offset += ctdb_connection_len_old(&val->conn[i]);
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_addr_info_wire {
+ ctdb_sock_addr addr;
+ uint32_t mask;
+ uint32_t len;
+ char iface[1];
+};
+
+static size_t ctdb_addr_info_len_old(struct ctdb_addr_info *in)
+{
+ uint32_t len;
+
+ len = offsetof(struct ctdb_addr_info_wire, iface);
+ if (in->iface != NULL) {
+ len += strlen(in->iface)+1;
+ }
+
+ return len;
+}
+
+static void ctdb_addr_info_push_old(struct ctdb_addr_info *in, uint8_t *buf)
+{
+ struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf;
+
+ wire->addr = in->addr;
+ wire->mask = in->mask;
+ if (in->iface == NULL) {
+ wire->len = 0;
+ } else {
+ wire->len = strlen(in->iface)+1;
+ memcpy(wire->iface, in->iface, wire->len);
+ }
+}
+
+static int ctdb_addr_info_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_addr_info **out)
+{
+ struct ctdb_addr_info *val;
+ struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_addr_info_wire, iface)) {
+ return EMSGSIZE;
+ }
+ if (wire->len > buflen) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_addr_info_wire, iface) + wire->len <
+ offsetof(struct ctdb_addr_info_wire, iface)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_addr_info_wire, iface) + wire->len) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_addr_info);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->addr = wire->addr;
+ val->mask = wire->mask;
+
+ if (wire->len == 0) {
+ val->iface = NULL;
+ } else {
+ val->iface = talloc_strndup(val, wire->iface, wire->len);
+ if (val->iface == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_transdb_len_old(struct ctdb_transdb *in)
+{
+ return sizeof(struct ctdb_transdb);
+}
+
+static void ctdb_transdb_push_old(struct ctdb_transdb *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_transdb));
+}
+
+static int ctdb_transdb_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_transdb **out)
+{
+ struct ctdb_transdb *val;
+
+ if (buflen < sizeof(struct ctdb_transdb)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_transdb));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_uptime_len_old(struct ctdb_uptime *in)
+{
+ return sizeof(struct ctdb_uptime);
+}
+
+static void ctdb_uptime_push_old(struct ctdb_uptime *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_uptime));
+}
+
+static int ctdb_uptime_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx, struct ctdb_uptime **out)
+{
+ struct ctdb_uptime *val;
+
+ if (buflen < sizeof(struct ctdb_uptime)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_uptime));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_public_ip_len_old(struct ctdb_public_ip *in)
+{
+ return sizeof(struct ctdb_public_ip);
+}
+
+static void ctdb_public_ip_push_old(struct ctdb_public_ip *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_public_ip));
+}
+
+static int ctdb_public_ip_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip *out)
+{
+ if (buflen < sizeof(struct ctdb_public_ip)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_public_ip));
+
+ return 0;
+}
+
+static int ctdb_public_ip_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip **out)
+{
+ struct ctdb_public_ip *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_public_ip);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_public_ip_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_public_ip_list_wire {
+ uint32_t num;
+ struct ctdb_public_ip ip[1];
+};
+
+static size_t ctdb_public_ip_list_len_old(struct ctdb_public_ip_list *in)
+{
+ unsigned int i;
+ size_t len;
+
+ len = sizeof(uint32_t);
+ for (i=0; i<in->num; i++) {
+ len += ctdb_public_ip_len_old(&in->ip[i]);
+ }
+ return len;
+}
+
+static void ctdb_public_ip_list_push_old(struct ctdb_public_ip_list *in,
+ uint8_t *buf)
+{
+ struct ctdb_public_ip_list_wire *wire =
+ (struct ctdb_public_ip_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+
+ wire->num = in->num;
+
+ offset = offsetof(struct ctdb_public_ip_list_wire, ip);
+ for (i=0; i<in->num; i++) {
+ ctdb_public_ip_push_old(&in->ip[i], &buf[offset]);
+ offset += ctdb_public_ip_len_old(&in->ip[i]);
+ }
+}
+
+static int ctdb_public_ip_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list **out)
+{
+ struct ctdb_public_ip_list *val;
+ struct ctdb_public_ip_list_wire *wire =
+ (struct ctdb_public_ip_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+ bool ret;
+
+ if (buflen < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_public_ip)) {
+ return EMSGSIZE;
+ }
+ if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_public_ip) <
+ sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(uint32_t) +
+ wire->num * sizeof(struct ctdb_public_ip)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_public_ip_list);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->num = wire->num;
+ if (wire->num == 0) {
+ val->ip = NULL;
+ *out = val;
+ return 0;
+ }
+ val->ip = talloc_array(val, struct ctdb_public_ip, wire->num);
+ if (val->ip == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ offset = offsetof(struct ctdb_public_ip_list_wire, ip);
+ for (i=0; i<wire->num; i++) {
+ ret = ctdb_public_ip_pull_elems_old(&buf[offset],
+ buflen-offset,
+ val->ip,
+ &val->ip[i]);
+ if (ret != 0) {
+ talloc_free(val);
+ return ret;
+ }
+ offset += ctdb_public_ip_len_old(&val->ip[i]);
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_node_and_flags_len_old(struct ctdb_node_and_flags *in)
+{
+ return sizeof(struct ctdb_node_and_flags);
+}
+
+static void ctdb_node_and_flags_push_old(struct ctdb_node_and_flags *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_node_and_flags));
+}
+
+static int ctdb_node_and_flags_pull_elems_old(TALLOC_CTX *mem_ctx,
+ uint8_t *buf, size_t buflen,
+ struct ctdb_node_and_flags *out)
+{
+ if (buflen < sizeof(struct ctdb_node_and_flags)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_node_and_flags));
+
+ return 0;
+}
+
+static int ctdb_node_and_flags_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_node_and_flags **out)
+{
+ struct ctdb_node_and_flags *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_node_and_flags);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_node_and_flags_pull_elems_old(val, buf, buflen, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_node_map_wire {
+ uint32_t num;
+ struct ctdb_node_and_flags node[1];
+};
+
+static size_t ctdb_node_map_len_old(struct ctdb_node_map *in)
+{
+ return sizeof(uint32_t) +
+ in->num * sizeof(struct ctdb_node_and_flags);
+}
+
+static void ctdb_node_map_push_old(struct ctdb_node_map *in, uint8_t *buf)
+{
+ struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf;
+ size_t offset;
+ unsigned int i;
+
+ wire->num = in->num;
+
+ offset = offsetof(struct ctdb_node_map_wire, node);
+ for (i=0; i<in->num; i++) {
+ ctdb_node_and_flags_push_old(&in->node[i], &buf[offset]);
+ offset += ctdb_node_and_flags_len_old(&in->node[i]);
+ }
+}
+
+static int ctdb_node_map_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_node_map **out)
+{
+ struct ctdb_node_map *val;
+ struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf;
+ size_t offset;
+ unsigned int i;
+ bool ret;
+
+ if (buflen < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_node_and_flags)) {
+ return EMSGSIZE;
+ }
+ if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_node_and_flags) <
+ sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(uint32_t) +
+ wire->num * sizeof(struct ctdb_node_and_flags)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_node_map);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->num = wire->num;
+ val->node = talloc_array(val, struct ctdb_node_and_flags, wire->num);
+ if (val->node == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ offset = offsetof(struct ctdb_node_map_wire, node);
+ for (i=0; i<wire->num; i++) {
+ ret = ctdb_node_and_flags_pull_elems_old(val->node,
+ &buf[offset],
+ buflen-offset,
+ &val->node[i]);
+ if (ret != 0) {
+ talloc_free(val);
+ return ret;
+ }
+ offset += ctdb_node_and_flags_len_old(&val->node[i]);
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_script_len_old(struct ctdb_script *in)
+{
+ return sizeof(struct ctdb_script);
+}
+
+static void ctdb_script_push_old(struct ctdb_script *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_script));
+}
+
+static int ctdb_script_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_script *out)
+{
+ if (buflen < sizeof(struct ctdb_script)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_script));
+
+ return 0;
+}
+
+static int ctdb_script_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx, struct ctdb_script **out)
+{
+ struct ctdb_script *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_script);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_script_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_script_list_wire {
+ uint32_t num_scripts;
+ struct ctdb_script script[1];
+};
+
+static size_t ctdb_script_list_len_old(struct ctdb_script_list *in)
+{
+ unsigned int i;
+ size_t len;
+
+ if (in == NULL) {
+ return 0;
+ }
+
+ len = offsetof(struct ctdb_script_list_wire, script);
+ for (i=0; i<in->num_scripts; i++) {
+ len += ctdb_script_len_old(&in->script[i]);
+ }
+ return len;
+}
+
+static void ctdb_script_list_push_old(struct ctdb_script_list *in,
+ uint8_t *buf)
+{
+ struct ctdb_script_list_wire *wire =
+ (struct ctdb_script_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+
+ if (in == NULL) {
+ return;
+ }
+
+ wire->num_scripts = in->num_scripts;
+
+ offset = offsetof(struct ctdb_script_list_wire, script);
+ for (i=0; i<in->num_scripts; i++) {
+ ctdb_script_push_old(&in->script[i], &buf[offset]);
+ offset += ctdb_script_len_old(&in->script[i]);
+ }
+}
+
+static int ctdb_script_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_script_list **out)
+{
+ struct ctdb_script_list *val;
+ struct ctdb_script_list_wire *wire =
+ (struct ctdb_script_list_wire *)buf;
+ size_t offset;
+ unsigned int i;
+ bool ret;
+
+ /* If event scripts have never been run, the result will be NULL */
+ if (buflen == 0) {
+ *out = NULL;
+ return 0;
+ }
+
+ offset = offsetof(struct ctdb_script_list_wire, script);
+
+ if (buflen < offset) {
+ return EMSGSIZE;
+ }
+ if (wire->num_scripts > buflen / sizeof(struct ctdb_script)) {
+ return EMSGSIZE;
+ }
+ if (offset + wire->num_scripts * sizeof(struct ctdb_script) < offset) {
+ return EMSGSIZE;
+ }
+ if (buflen < offset + wire->num_scripts * sizeof(struct ctdb_script)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_script_list);
+ if (val == NULL) {
+ return ENOMEM;
+
+ }
+
+ val->num_scripts = wire->num_scripts;
+ val->script = talloc_array(val, struct ctdb_script, wire->num_scripts);
+ if (val->script == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ for (i=0; i<wire->num_scripts; i++) {
+ ret = ctdb_script_pull_elems_old(&buf[offset], buflen-offset,
+ val->script,
+ &val->script[i]);
+ if (ret != 0) {
+ talloc_free(val);
+ return ret;
+ }
+ offset += ctdb_script_len_old(&val->script[i]);
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_ban_state_len_old(struct ctdb_ban_state *in)
+{
+ return sizeof(struct ctdb_ban_state);
+}
+
+static void ctdb_ban_state_push_old(struct ctdb_ban_state *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_ban_state));
+}
+
+static int ctdb_ban_state_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_ban_state **out)
+{
+ struct ctdb_ban_state *val;
+
+ if (buflen < sizeof(struct ctdb_ban_state)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_ban_state));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_notify_data_wire {
+ uint64_t srvid;
+ uint32_t len;
+ uint8_t data[1];
+};
+
+static size_t ctdb_notify_data_len_old(struct ctdb_notify_data *in)
+{
+ return offsetof(struct ctdb_notify_data_wire, data) +
+ in->data.dsize;
+}
+
+static void ctdb_notify_data_push_old(struct ctdb_notify_data *in,
+ uint8_t *buf)
+{
+ struct ctdb_notify_data_wire *wire =
+ (struct ctdb_notify_data_wire *)buf;
+
+ wire->srvid = in->srvid;
+ wire->len = in->data.dsize;
+ memcpy(wire->data, in->data.dptr, in->data.dsize);
+}
+
+static int ctdb_notify_data_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_notify_data **out)
+{
+ struct ctdb_notify_data *val;
+ struct ctdb_notify_data_wire *wire =
+ (struct ctdb_notify_data_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_notify_data_wire, data)) {
+ return EMSGSIZE;
+ }
+ if (wire->len > buflen) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_notify_data_wire, data) + wire->len <
+ offsetof(struct ctdb_notify_data_wire, data)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_notify_data_wire, data) + wire->len) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_notify_data);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->srvid = wire->srvid;
+ val->data.dsize = wire->len;
+ val->data.dptr = talloc_memdup(val, wire->data, wire->len);
+ if (val->data.dptr == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_iface_len_old(struct ctdb_iface *in)
+{
+ return sizeof(struct ctdb_iface);
+}
+
+static void ctdb_iface_push_old(struct ctdb_iface *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_iface));
+}
+
+static int ctdb_iface_pull_elems_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_iface *out)
+{
+ if (buflen < sizeof(struct ctdb_iface)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_iface));
+
+ return 0;
+}
+
+static int ctdb_iface_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx, struct ctdb_iface **out)
+{
+ struct ctdb_iface *val;
+ int ret;
+
+ val = talloc(mem_ctx, struct ctdb_iface);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ ret = ctdb_iface_pull_elems_old(buf, buflen, val, val);
+ if (ret != 0) {
+ TALLOC_FREE(val);
+ return ret;
+ }
+
+ *out = val;
+ return ret;
+}
+
+struct ctdb_iface_list_wire {
+ uint32_t num;
+ struct ctdb_iface iface[1];
+};
+
+static size_t ctdb_iface_list_len_old(struct ctdb_iface_list *in)
+{
+ return sizeof(uint32_t) +
+ in->num * sizeof(struct ctdb_iface);
+}
+
+static void ctdb_iface_list_push_old(struct ctdb_iface_list *in, uint8_t *buf)
+{
+ struct ctdb_iface_list_wire *wire =
+ (struct ctdb_iface_list_wire *)buf;
+
+ wire->num = in->num;
+ memcpy(wire->iface, in->iface, in->num * sizeof(struct ctdb_iface));
+}
+
+static int ctdb_iface_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_iface_list **out)
+{
+ struct ctdb_iface_list *val;
+ struct ctdb_iface_list_wire *wire =
+ (struct ctdb_iface_list_wire *)buf;
+
+ if (buflen < sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_iface)) {
+ return EMSGSIZE;
+ }
+ if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface) <
+ sizeof(uint32_t)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_iface_list);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->num = wire->num;
+ val->iface = talloc_array(val, struct ctdb_iface, wire->num);
+ if (val->iface == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ memcpy(val->iface, wire->iface, wire->num * sizeof(struct ctdb_iface));
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_public_ip_info_wire {
+ struct ctdb_public_ip ip;
+ uint32_t active_idx;
+ uint32_t num;
+ struct ctdb_iface ifaces[1];
+};
+
+static size_t ctdb_public_ip_info_len_old(struct ctdb_public_ip_info *in)
+{
+ return offsetof(struct ctdb_public_ip_info_wire, num) +
+ ctdb_iface_list_len_old(in->ifaces);
+}
+
+static void ctdb_public_ip_info_push_old(struct ctdb_public_ip_info *in,
+ uint8_t *buf)
+{
+ struct ctdb_public_ip_info_wire *wire =
+ (struct ctdb_public_ip_info_wire *)buf;
+ size_t offset;
+
+ offset = offsetof(struct ctdb_public_ip_info_wire, num);
+ memcpy(wire, in, offset);
+ wire->num = in->ifaces->num;
+ memcpy(wire->ifaces, in->ifaces->iface,
+ in->ifaces->num * sizeof(struct ctdb_iface));
+}
+
+static int ctdb_public_ip_info_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_info **out)
+{
+ struct ctdb_public_ip_info *val;
+ struct ctdb_public_ip_info_wire *wire =
+ (struct ctdb_public_ip_info_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_iface)) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_public_ip_info_wire, ifaces) +
+ wire->num * sizeof(struct ctdb_iface) <
+ offsetof(struct ctdb_public_ip_info_wire, ifaces)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces) +
+ wire->num * sizeof(struct ctdb_iface)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_public_ip_info);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(val, wire, offsetof(struct ctdb_public_ip_info_wire, num));
+
+ val->ifaces = talloc(val, struct ctdb_iface_list);
+ if (val->ifaces == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ val->ifaces->num = wire->num;
+ val->ifaces->iface = talloc_array(val->ifaces, struct ctdb_iface,
+ wire->num);
+ if (val->ifaces->iface == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ memcpy(val->ifaces->iface, wire->ifaces,
+ wire->num * sizeof(struct ctdb_iface));
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_statistics_list_wire {
+ uint32_t num;
+ struct ctdb_statistics stats[1];
+};
+
+static size_t ctdb_statistics_list_len_old(struct ctdb_statistics_list *in)
+{
+ return offsetof(struct ctdb_statistics_list_wire, stats) +
+ in->num * sizeof(struct ctdb_statistics);
+}
+
+static void ctdb_statistics_list_push_old(struct ctdb_statistics_list *in,
+ uint8_t *buf)
+{
+ struct ctdb_statistics_list_wire *wire =
+ (struct ctdb_statistics_list_wire *)buf;
+
+ wire->num = in->num;
+ memcpy(wire->stats, in->stats,
+ in->num * sizeof(struct ctdb_statistics));
+}
+
+static int ctdb_statistics_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_statistics_list **out)
+{
+ struct ctdb_statistics_list *val;
+ struct ctdb_statistics_list_wire *wire =
+ (struct ctdb_statistics_list_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_statistics_list_wire, stats)) {
+ return EMSGSIZE;
+ }
+ if (wire->num > buflen / sizeof(struct ctdb_statistics)) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_statistics_list_wire, stats) +
+ wire->num * sizeof(struct ctdb_statistics) <
+ offsetof(struct ctdb_statistics_list_wire, stats)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_statistics_list_wire, stats) +
+ wire->num * sizeof(struct ctdb_statistics)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_statistics_list);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ val->num = wire->num;
+
+ val->stats = talloc_array(val, struct ctdb_statistics, wire->num);
+ if (val->stats == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ memcpy(val->stats, wire->stats,
+ wire->num * sizeof(struct ctdb_statistics));
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_key_data_wire {
+ uint32_t db_id;
+ struct ctdb_ltdb_header header;
+ uint32_t keylen;
+ uint8_t key[1];
+};
+
+static size_t ctdb_key_data_len_old(struct ctdb_key_data *in)
+{
+ return offsetof(struct ctdb_key_data_wire, key) + in->key.dsize;
+}
+
+static void ctdb_key_data_push_old(struct ctdb_key_data *in, uint8_t *buf)
+{
+ struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf;
+
+ memcpy(wire, in, offsetof(struct ctdb_key_data, key));
+ wire->keylen = in->key.dsize;
+ memcpy(wire->key, in->key.dptr, in->key.dsize);
+}
+
+static int ctdb_key_data_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_key_data **out)
+{
+ struct ctdb_key_data *val;
+ struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf;
+
+ if (buflen < offsetof(struct ctdb_key_data_wire, key)) {
+ return EMSGSIZE;
+ }
+ if (wire->keylen > buflen) {
+ return EMSGSIZE;
+ }
+ if (offsetof(struct ctdb_key_data_wire, key) + wire->keylen <
+ offsetof(struct ctdb_key_data_wire, key)) {
+ return EMSGSIZE;
+ }
+ if (buflen < offsetof(struct ctdb_key_data_wire, key) + wire->keylen) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_key_data);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(val, wire, offsetof(struct ctdb_key_data, key));
+
+ val->key.dsize = wire->keylen;
+ val->key.dptr = talloc_memdup(val, wire->key, wire->keylen);
+ if (val->key.dptr == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+struct ctdb_db_statistics_wire {
+ struct ctdb_db_statistics dbstats;
+ char hot_keys_wire[1];
+};
+
+static size_t ctdb_db_statistics_len_old(struct ctdb_db_statistics *in)
+{
+ size_t len;
+ int i;
+
+ len = sizeof(struct ctdb_db_statistics);
+ for (i=0; i<MAX_HOT_KEYS; i++) {
+ len += in->hot_keys[i].key.dsize;
+ }
+ return len;
+}
+
+static void ctdb_db_statistics_push_old(struct ctdb_db_statistics *in,
+ void *buf)
+{
+ struct ctdb_db_statistics_wire *wire =
+ (struct ctdb_db_statistics_wire *)buf;
+ size_t offset;
+ int i;
+
+ in->num_hot_keys = MAX_HOT_KEYS;
+ memcpy(wire, in, sizeof(struct ctdb_db_statistics));
+
+ offset = 0;
+ for (i=0; i<MAX_HOT_KEYS; i++) {
+ memcpy(&wire->hot_keys_wire[offset],
+ in->hot_keys[i].key.dptr,
+ in->hot_keys[i].key.dsize);
+ offset += in->hot_keys[i].key.dsize;
+ }
+}
+
+static int ctdb_db_statistics_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_db_statistics **out)
+{
+ struct ctdb_db_statistics *val;
+ struct ctdb_db_statistics_wire *wire =
+ (struct ctdb_db_statistics_wire *)buf;
+ size_t offset;
+ unsigned int i;
+
+ if (buflen < sizeof(struct ctdb_db_statistics)) {
+ return EMSGSIZE;
+ }
+
+ offset = 0;
+ for (i=0; i<wire->dbstats.num_hot_keys; i++) {
+ if (wire->dbstats.hot_keys[i].key.dsize > buflen) {
+ return EMSGSIZE;
+ }
+ if (offset + wire->dbstats.hot_keys[i].key.dsize < offset) {
+ return EMSGSIZE;
+ }
+ offset += wire->dbstats.hot_keys[i].key.dsize;
+ if (offset > buflen) {
+ return EMSGSIZE;
+ }
+ }
+ if (sizeof(struct ctdb_db_statistics) + offset <
+ sizeof(struct ctdb_db_statistics)) {
+ return EMSGSIZE;
+ }
+ if (buflen < sizeof(struct ctdb_db_statistics) + offset) {
+ return EMSGSIZE;
+ }
+
+ val = talloc(mem_ctx, struct ctdb_db_statistics);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(val, wire, sizeof(struct ctdb_db_statistics));
+
+ offset = 0;
+ for (i=0; i<wire->dbstats.num_hot_keys; i++) {
+ uint8_t *ptr;
+ size_t key_size;
+
+ key_size = val->hot_keys[i].key.dsize;
+ ptr = talloc_memdup(mem_ctx, &wire->hot_keys_wire[offset],
+ key_size);
+ if (ptr == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+ val->hot_keys[i].key.dptr = ptr;
+ offset += key_size;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_election_message_len_old(struct ctdb_election_message *in)
+{
+ return sizeof(struct ctdb_election_message);
+}
+
+static void ctdb_election_message_push_old(struct ctdb_election_message *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_election_message));
+}
+
+static int ctdb_election_message_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_election_message **out)
+{
+ struct ctdb_election_message *val;
+
+ if (buflen < sizeof(struct ctdb_election_message)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf,
+ sizeof(struct ctdb_election_message));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_srvid_message_len_old(struct ctdb_srvid_message *in)
+{
+ return sizeof(struct ctdb_srvid_message);
+}
+
+static void ctdb_srvid_message_push_old(struct ctdb_srvid_message *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_srvid_message));
+}
+
+static int ctdb_srvid_message_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_srvid_message **out)
+{
+ struct ctdb_srvid_message *val;
+
+ if (buflen < sizeof(struct ctdb_srvid_message)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_srvid_message));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_disable_message_len_old(struct ctdb_disable_message *in)
+{
+ return sizeof(struct ctdb_disable_message);
+}
+
+static void ctdb_disable_message_push_old(struct ctdb_disable_message *in,
+ uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_disable_message));
+}
+
+static int ctdb_disable_message_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_disable_message **out)
+{
+ struct ctdb_disable_message *val;
+
+ if (buflen < sizeof(struct ctdb_disable_message)) {
+ return EMSGSIZE;
+ }
+
+ val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_disable_message));
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ *out = val;
+ return 0;
+}
+
+static size_t ctdb_server_id_len_old(struct ctdb_server_id *in)
+{
+ return sizeof(struct ctdb_server_id);
+}
+
+static void ctdb_server_id_push_old(struct ctdb_server_id *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_server_id));
+}
+
+static int ctdb_server_id_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_server_id *out)
+{
+ if (buflen < sizeof(struct ctdb_server_id)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_server_id));
+ return 0;
+}
+
+static size_t ctdb_g_lock_len_old(struct ctdb_g_lock *in)
+{
+ return sizeof(struct ctdb_g_lock);
+}
+
+static void ctdb_g_lock_push_old(struct ctdb_g_lock *in, uint8_t *buf)
+{
+ memcpy(buf, in, sizeof(struct ctdb_g_lock));
+}
+
+static int ctdb_g_lock_pull_old(uint8_t *buf, size_t buflen,
+ struct ctdb_g_lock *out)
+{
+ if (buflen < sizeof(struct ctdb_g_lock)) {
+ return EMSGSIZE;
+ }
+
+ memcpy(out, buf, sizeof(struct ctdb_g_lock));
+ return 0;
+}
+
+static size_t ctdb_g_lock_list_len_old(struct ctdb_g_lock_list *in)
+{
+ return in->num * sizeof(struct ctdb_g_lock);
+}
+
+static void ctdb_g_lock_list_push_old(struct ctdb_g_lock_list *in,
+ uint8_t *buf)
+{
+ size_t offset = 0;
+ unsigned int i;
+
+ for (i=0; i<in->num; i++) {
+ ctdb_g_lock_push_old(&in->lock[i], &buf[offset]);
+ offset += sizeof(struct ctdb_g_lock);
+ }
+}
+
+static int ctdb_g_lock_list_pull_old(uint8_t *buf, size_t buflen,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_g_lock_list **out)
+{
+ struct ctdb_g_lock_list *val;
+ unsigned count;
+ size_t offset;
+ unsigned int i;
+ int ret;
+
+ val = talloc_zero(mem_ctx, struct ctdb_g_lock_list);
+ if (val == NULL) {
+ return ENOMEM;
+ }
+
+ count = buflen / sizeof(struct ctdb_g_lock);
+ val->lock = talloc_array(val, struct ctdb_g_lock, count);
+ if (val->lock == NULL) {
+ talloc_free(val);
+ return ENOMEM;
+ }
+
+ offset = 0;
+ for (i=0; i<count; i++) {
+ ret = ctdb_g_lock_pull_old(&buf[offset], buflen-offset,
+ &val->lock[i]);
+ if (ret != 0) {
+ talloc_free(val);
+ return ret;
+ }
+ offset += sizeof(struct ctdb_g_lock);
+ }
+
+ val->num = count;
+
+ *out = val;
+ return 0;
+}
+
+COMPAT_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics);
+COMPAT_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map);
+COMPAT_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map);
+COMPAT_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb);
+COMPAT_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext);
+
+COMPAT_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header);
+
+COMPAT_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data);
+COMPAT_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer);
+COMPAT_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start);
+COMPAT_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all);
+COMPAT_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext);
+COMPAT_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext);
+COMPAT_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr);
+COMPAT_TYPE3_TEST(struct ctdb_connection, ctdb_connection);
+COMPAT_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable);
+COMPAT_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change);
+COMPAT_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list);
+COMPAT_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list);
+COMPAT_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list);
+COMPAT_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info);
+COMPAT_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb);
+COMPAT_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime);
+COMPAT_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip);
+COMPAT_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list);
+COMPAT_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags);
+COMPAT_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map);
+COMPAT_TYPE3_TEST(struct ctdb_script, ctdb_script);
+COMPAT_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list);
+COMPAT_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state);
+COMPAT_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data);
+COMPAT_TYPE3_TEST(struct ctdb_iface, ctdb_iface);
+COMPAT_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list);
+COMPAT_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info);
+COMPAT_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list);
+COMPAT_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data);
+COMPAT_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics);
+
+COMPAT_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message);
+COMPAT_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message);
+COMPAT_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message);
+
+COMPAT_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id);
+COMPAT_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock);
+
+COMPAT_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list);
+
+static void protocol_types_compat_test(void)
+{
+ COMPAT_TEST_FUNC(ctdb_statistics)();
+ COMPAT_TEST_FUNC(ctdb_vnn_map)();
+ COMPAT_TEST_FUNC(ctdb_dbid_map)();
+ COMPAT_TEST_FUNC(ctdb_pulldb)();
+ COMPAT_TEST_FUNC(ctdb_pulldb_ext)();
+ COMPAT_TEST_FUNC(ctdb_ltdb_header)();
+ COMPAT_TEST_FUNC(ctdb_rec_data)();
+ COMPAT_TEST_FUNC(ctdb_rec_buffer)();
+ COMPAT_TEST_FUNC(ctdb_traverse_start)();
+ COMPAT_TEST_FUNC(ctdb_traverse_all)();
+ COMPAT_TEST_FUNC(ctdb_traverse_start_ext)();
+ COMPAT_TEST_FUNC(ctdb_traverse_all_ext)();
+ COMPAT_TEST_FUNC(ctdb_sock_addr)();
+ COMPAT_TEST_FUNC(ctdb_connection)();
+ COMPAT_TEST_FUNC(ctdb_tunable)();
+ COMPAT_TEST_FUNC(ctdb_node_flag_change)();
+ COMPAT_TEST_FUNC(ctdb_var_list)();
+ COMPAT_TEST_FUNC(ctdb_tunable_list)();
+ COMPAT_TEST_FUNC(ctdb_tickle_list)();
+ COMPAT_TEST_FUNC(ctdb_addr_info)();
+ COMPAT_TEST_FUNC(ctdb_transdb)();
+ COMPAT_TEST_FUNC(ctdb_uptime)();
+ COMPAT_TEST_FUNC(ctdb_public_ip)();
+ COMPAT_TEST_FUNC(ctdb_public_ip_list)();
+ COMPAT_TEST_FUNC(ctdb_node_and_flags)();
+ COMPAT_TEST_FUNC(ctdb_node_map)();
+ COMPAT_TEST_FUNC(ctdb_script)();
+ COMPAT_TEST_FUNC(ctdb_script_list)();
+ COMPAT_TEST_FUNC(ctdb_ban_state)();
+ COMPAT_TEST_FUNC(ctdb_notify_data)();
+ COMPAT_TEST_FUNC(ctdb_iface)();
+ COMPAT_TEST_FUNC(ctdb_iface_list)();
+ COMPAT_TEST_FUNC(ctdb_public_ip_info)();
+ COMPAT_TEST_FUNC(ctdb_statistics_list)();
+ COMPAT_TEST_FUNC(ctdb_key_data)();
+ COMPAT_TEST_FUNC(ctdb_db_statistics)();
+
+ COMPAT_TEST_FUNC(ctdb_election_message)();
+ COMPAT_TEST_FUNC(ctdb_srvid_message)();
+ COMPAT_TEST_FUNC(ctdb_disable_message)();
+ COMPAT_TEST_FUNC(ctdb_server_id)();
+ COMPAT_TEST_FUNC(ctdb_g_lock)();
+ COMPAT_TEST_FUNC(ctdb_g_lock_list)();
+}
+
+int main(int argc, const char *argv[])
+{
+ protocol_test_iterate(argc, argv, protocol_types_compat_test);
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_types_test.c b/ctdb/tests/src/protocol_types_test.c
new file mode 100644
index 0000000..f4a3048
--- /dev/null
+++ b/ctdb/tests/src/protocol_types_test.c
@@ -0,0 +1,194 @@
+/*
+ protocol types tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+
+#include <assert.h>
+
+#include "protocol/protocol_basic.c"
+#include "protocol/protocol_types.c"
+#include "protocol/protocol_sock.c"
+
+#include "tests/src/protocol_common.h"
+
+PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_data);
+PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_datan);
+PROTOCOL_TYPE1_TEST(struct ctdb_latency_counter, ctdb_latency_counter);
+
+PROTOCOL_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics);
+PROTOCOL_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map);
+PROTOCOL_TYPE3_TEST(struct ctdb_dbid, ctdb_dbid);
+PROTOCOL_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map);
+PROTOCOL_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb);
+PROTOCOL_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext);
+PROTOCOL_TYPE3_TEST(struct ctdb_db_vacuum, ctdb_db_vacuum);
+PROTOCOL_TYPE3_TEST(struct ctdb_echo_data, ctdb_echo_data);
+PROTOCOL_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header);
+PROTOCOL_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data);
+PROTOCOL_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer);
+PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start);
+PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all);
+PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext);
+PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext);
+PROTOCOL_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr);
+PROTOCOL_TYPE3_TEST(struct ctdb_connection, ctdb_connection);
+PROTOCOL_TYPE3_TEST(struct ctdb_connection_list, ctdb_connection_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable);
+PROTOCOL_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change);
+PROTOCOL_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info);
+PROTOCOL_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb);
+PROTOCOL_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime);
+PROTOCOL_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip);
+PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags);
+PROTOCOL_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map);
+PROTOCOL_TYPE3_TEST(struct ctdb_script, ctdb_script);
+PROTOCOL_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state);
+PROTOCOL_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data);
+PROTOCOL_TYPE3_TEST(struct ctdb_iface, ctdb_iface);
+PROTOCOL_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info);
+PROTOCOL_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list);
+PROTOCOL_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data);
+PROTOCOL_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics);
+PROTOCOL_TYPE3_TEST(struct ctdb_pid_srvid, ctdb_pid_srvid);
+PROTOCOL_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message);
+PROTOCOL_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message);
+PROTOCOL_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message);
+PROTOCOL_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id);
+PROTOCOL_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock);
+PROTOCOL_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list);
+
+PROTOCOL_TYPE1_TEST(struct sock_packet_header, sock_packet_header);
+
+static void test_ctdb_rec_buffer_read_write(void)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ struct ctdb_rec_buffer *p1, **p2;
+ const char *filename = "ctdb_rec_buffer_test.dat";
+ int count = 100;
+ int fd, i, ret;
+ off_t offset;
+
+ p1 = talloc_array(mem_ctx, struct ctdb_rec_buffer, count);
+ assert(p1 != NULL);
+ for (i=0; i<count; i++) {
+ fill_ctdb_rec_buffer(mem_ctx, &p1[i]);
+ }
+
+ fd = open(filename, O_RDWR|O_CREAT, 0600);
+ assert(fd != -1);
+ unlink(filename);
+
+ for (i=0; i<count; i++) {
+ ret = ctdb_rec_buffer_write(&p1[i], fd);
+ assert(ret == 0);
+ }
+
+ offset = lseek(fd, 0, SEEK_CUR);
+ assert(offset != -1);
+ offset = lseek(fd, -offset, SEEK_CUR);
+ assert(offset == 0);
+
+ p2 = talloc_array(mem_ctx, struct ctdb_rec_buffer *, count);
+ assert(p2 != NULL);
+
+ for (i=0; i<count; i++) {
+ ret = ctdb_rec_buffer_read(fd, mem_ctx, &p2[i]);
+ assert(ret == 0);
+ }
+
+ close(fd);
+
+ for (i=0; i<count; i++) {
+ verify_ctdb_rec_buffer(&p1[i], p2[i]);
+ }
+
+ talloc_free(mem_ctx);
+}
+
+static void protocol_types_test(void)
+{
+ TEST_FUNC(ctdb_tdb_data)();
+ TEST_FUNC(ctdb_tdb_datan)();
+ TEST_FUNC(ctdb_latency_counter)();
+
+ TEST_FUNC(ctdb_statistics)();
+ TEST_FUNC(ctdb_vnn_map)();
+ TEST_FUNC(ctdb_dbid)();
+ TEST_FUNC(ctdb_dbid_map)();
+ TEST_FUNC(ctdb_pulldb)();
+ TEST_FUNC(ctdb_pulldb_ext)();
+ TEST_FUNC(ctdb_db_vacuum)();
+ TEST_FUNC(ctdb_echo_data)();
+ TEST_FUNC(ctdb_ltdb_header)();
+ TEST_FUNC(ctdb_rec_data)();
+ TEST_FUNC(ctdb_rec_buffer)();
+ TEST_FUNC(ctdb_traverse_start)();
+ TEST_FUNC(ctdb_traverse_all)();
+ TEST_FUNC(ctdb_traverse_start_ext)();
+ TEST_FUNC(ctdb_traverse_all_ext)();
+ TEST_FUNC(ctdb_sock_addr)();
+ TEST_FUNC(ctdb_connection)();
+ TEST_FUNC(ctdb_connection_list)();
+ TEST_FUNC(ctdb_tunable)();
+ TEST_FUNC(ctdb_node_flag_change)();
+ TEST_FUNC(ctdb_var_list)();
+ TEST_FUNC(ctdb_tunable_list)();
+ TEST_FUNC(ctdb_tickle_list)();
+ TEST_FUNC(ctdb_addr_info)();
+ TEST_FUNC(ctdb_transdb)();
+ TEST_FUNC(ctdb_uptime)();
+ TEST_FUNC(ctdb_public_ip)();
+ TEST_FUNC(ctdb_public_ip_list)();
+ TEST_FUNC(ctdb_node_and_flags)();
+ TEST_FUNC(ctdb_node_map)();
+ TEST_FUNC(ctdb_script)();
+ TEST_FUNC(ctdb_script_list)();
+ TEST_FUNC(ctdb_ban_state)();
+ TEST_FUNC(ctdb_notify_data)();
+ TEST_FUNC(ctdb_iface)();
+ TEST_FUNC(ctdb_iface_list)();
+ TEST_FUNC(ctdb_public_ip_info)();
+ TEST_FUNC(ctdb_statistics_list)();
+ TEST_FUNC(ctdb_key_data)();
+ TEST_FUNC(ctdb_db_statistics)();
+ TEST_FUNC(ctdb_pid_srvid)();
+ TEST_FUNC(ctdb_election_message)();
+ TEST_FUNC(ctdb_srvid_message)();
+ TEST_FUNC(ctdb_disable_message)();
+ TEST_FUNC(ctdb_server_id)();
+ TEST_FUNC(ctdb_g_lock)();
+ TEST_FUNC(ctdb_g_lock_list)();
+
+ TEST_FUNC(sock_packet_header)();
+
+ test_ctdb_rec_buffer_read_write();
+}
+
+int main(int argc, const char *argv[])
+{
+ protocol_test_iterate(argc, argv, protocol_types_test);
+ return 0;
+}
diff --git a/ctdb/tests/src/protocol_util_test.c b/ctdb/tests/src/protocol_util_test.c
new file mode 100644
index 0000000..4ffe58c
--- /dev/null
+++ b/ctdb/tests/src/protocol_util_test.c
@@ -0,0 +1,417 @@
+/*
+ protocol utilities tests
+
+ Copyright (C) Martin Schwenke 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 <assert.h>
+
+#include "protocol/protocol_basic.c"
+#include "protocol/protocol_types.c"
+#include "protocol/protocol_util.c"
+
+/*
+ * Test parsing of IPs, conversion to string
+ */
+
+static void test_sock_addr_to_string(const char *ip, bool with_port)
+{
+ ctdb_sock_addr sa;
+ const char *s;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(ip, &sa, with_port);
+ assert(ret == 0);
+ s = ctdb_sock_addr_to_string(NULL, &sa, with_port);
+ assert(strcmp(ip, s) == 0);
+ talloc_free(discard_const(s));
+}
+
+static void test_sock_addr_from_string_bad(const char *ip, bool with_port)
+{
+ ctdb_sock_addr sa;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(ip, &sa, with_port);
+ assert(ret == EINVAL);
+}
+
+static void test_sock_addr_from_string_memcmp(const char *ip1,
+ const char* ip2)
+{
+ ctdb_sock_addr sa1, sa2;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(ip1, &sa1, false);
+ assert(ret == 0);
+ ret = ctdb_sock_addr_from_string(ip2, &sa2, false);
+ assert(ret == 0);
+ ret = memcmp(&sa1, &sa2, sizeof(ctdb_sock_addr));
+ assert(ret == 0);
+}
+
+static void test_sock_addr_cmp(const char *ip1, const char *ip2,
+ bool with_port, int res)
+{
+ ctdb_sock_addr sa1, sa2;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(ip1, &sa1, with_port);
+ assert(ret == 0);
+ ret = ctdb_sock_addr_from_string(ip2, &sa2, with_port);
+ assert(ret == 0);
+ ret = ctdb_sock_addr_cmp(&sa1, &sa2);
+ if (ret < 0) {
+ ret = -1;
+ } else if (ret > 0) {
+ ret = 1;
+ }
+
+ assert(ret == res);
+}
+
+/*
+ * Test parsing of IP/mask, conversion to string
+ */
+
+static void test_sock_addr_mask_from_string(const char *ip_mask)
+{
+ ctdb_sock_addr sa;
+ unsigned mask;
+ const char *s, *t;
+ int ret;
+
+ ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask);
+ assert(ret == 0);
+ s = ctdb_sock_addr_to_string(NULL, &sa, false);
+ assert(s != NULL);
+ t = talloc_asprintf(s, "%s/%u", s, mask);
+ assert(strcmp(ip_mask, t) == 0);
+ talloc_free(discard_const(s));
+}
+
+static void test_sock_addr_mask_from_string_bad(const char *ip_mask)
+{
+ ctdb_sock_addr sa;
+ unsigned mask;
+ int ret;
+
+ ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask);
+ assert(ret == EINVAL);
+}
+
+/*
+ * Test parsing of connection, conversion to string
+ */
+
+static void test_connection_to_string(const char *conn_str)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ctdb_connection conn;
+ const char *s, *r;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ assert(tmp_ctx != NULL);
+
+ /*
+ * Test non-reversed parse and render
+ */
+
+ ret = ctdb_connection_from_string(conn_str, false, &conn);
+ assert(ret == 0);
+
+ s = ctdb_connection_to_string(tmp_ctx, &conn, false);
+ assert(s != NULL);
+ ret = strcmp(conn_str, s);
+ assert(ret == 0);
+
+ talloc_free(discard_const(s));
+
+ /*
+ * Reversed render
+ */
+ r = ctdb_connection_to_string(tmp_ctx, &conn, true);
+ assert(r != NULL);
+ ret = strcmp(conn_str, r);
+ assert(ret != 0);
+
+ /*
+ * Reversed parse with forward render
+ */
+ ret = ctdb_connection_from_string(conn_str, true, &conn);
+ assert(ret == 0);
+
+ s = ctdb_connection_to_string(tmp_ctx, &conn, false);
+ assert(s != NULL);
+ ret = strcmp(r, s);
+ assert(ret == 0);
+
+ talloc_free(discard_const(s));
+
+ /*
+ * Reversed parse and render
+ */
+ ret = ctdb_connection_from_string(conn_str, true, &conn);
+ assert(ret == 0);
+
+ s = ctdb_connection_to_string(tmp_ctx, &conn, true);
+ assert(s != NULL);
+ ret = strcmp(conn_str, s);
+ assert(ret == 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_connection_from_string_bad(const char *conn_str)
+{
+ struct ctdb_connection conn;
+ int ret;
+
+ ret = ctdb_connection_from_string(conn_str, false, &conn);
+ assert(ret == EINVAL);
+}
+
+/*
+ * Test connection list utilities
+ */
+
+static void test_connection_list_read(const char *s1, const char *s2)
+{
+ TALLOC_CTX *tmp_ctx;
+ int pipefd[2];
+ pid_t pid;
+ struct ctdb_connection_list *conn_list = NULL;
+ const char *t;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ assert(tmp_ctx != NULL);
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ close(pipefd[0]);
+
+ ret = dup2(pipefd[1], STDOUT_FILENO);
+ assert(ret != -1);
+
+ close(pipefd[1]);
+
+ printf("%s", s1);
+ fflush(stdout);
+
+ exit(0);
+ }
+
+ close(pipefd[1]);
+
+ ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list);
+ assert(ret == 0);
+
+ close(pipefd[0]);
+
+ ret = ctdb_connection_list_sort(conn_list);
+ assert(ret == 0);
+
+ t = ctdb_connection_list_to_string(tmp_ctx, conn_list, false);
+ assert(t != NULL);
+ ret = strcmp(t, s2);
+ assert(ret == 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_connection_list_read_bad(const char *s1)
+{
+ TALLOC_CTX *tmp_ctx;
+ int pipefd[2];
+ pid_t pid;
+ struct ctdb_connection_list *conn_list = NULL;
+ int ret;
+
+ tmp_ctx = talloc_new(NULL);
+ assert(tmp_ctx != NULL);
+
+ ret = pipe(pipefd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ close(pipefd[0]);
+
+ ret = dup2(pipefd[1], STDOUT_FILENO);
+ assert(ret != -1);
+
+ close(pipefd[1]);
+
+ printf("%s", s1);
+ fflush(stdout);
+
+ exit(0);
+ }
+
+ close(pipefd[1]);
+
+ ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list);
+ assert(ret == EINVAL);
+
+ close(pipefd[0]);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Use macros for these to make them easy to concatenate
+ */
+
+#define CONN4 \
+"\
+127.0.0.1:12345 127.0.0.2:54321\n\
+127.0.0.2:12345 127.0.0.1:54322\n\
+127.0.0.1:12346 127.0.0.2:54323\n\
+127.0.0.2:12345 127.0.0.1:54324\n\
+127.0.0.1:12345 127.0.0.2:54325\n\
+"
+
+#define CONN4_SORT \
+"\
+127.0.0.1:12345 127.0.0.2:54321\n\
+127.0.0.1:12345 127.0.0.2:54325\n\
+127.0.0.1:12346 127.0.0.2:54323\n\
+127.0.0.2:12345 127.0.0.1:54322\n\
+127.0.0.2:12345 127.0.0.1:54324\n\
+"
+
+#define CONN6 \
+"\
+[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\
+[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\
+[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\
+[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\
+[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\
+"
+
+#define CONN6_SORT \
+"\
+[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\
+[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\
+[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\
+[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\
+[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\
+"
+
+int main(int argc, char *argv[])
+{
+ test_sock_addr_to_string("0.0.0.0", false);
+ test_sock_addr_to_string("127.0.0.1", false);
+ test_sock_addr_to_string("::1", false);
+ test_sock_addr_to_string("192.168.2.1", false);
+ test_sock_addr_to_string("fe80::6af7:28ff:fefa:d136", false);
+
+ test_sock_addr_to_string("0.0.0.0:0", true);
+ test_sock_addr_to_string("127.0.0.1:123", true);
+ test_sock_addr_to_string("[::1]:234", true);
+ test_sock_addr_to_string("192.168.2.1:123", true);
+ test_sock_addr_to_string("[fe80::6af7:28ff:fefa:d136]:234", true);
+
+ test_sock_addr_from_string_bad("0.0.0", false);
+ test_sock_addr_from_string_bad("0.0.0:0", true);
+ test_sock_addr_from_string_bad("fe80::6af7:28ff:fefa:d136", true);
+ test_sock_addr_from_string_bad("junk", false);
+ test_sock_addr_from_string_bad("0.0.0.0:0 trailing junk", true);
+
+ test_sock_addr_from_string_memcmp("127.0.0.1", "127.0.0.1");
+ test_sock_addr_from_string_memcmp("fe80::6af7:28ff:fefa:d136",
+ "fe80::6af7:28ff:fefa:d136");
+ test_sock_addr_from_string_memcmp("::ffff:192.0.2.128", "192.0.2.128");
+
+ test_sock_addr_cmp("127.0.0.1", "127.0.0.1" , false, 0);
+ test_sock_addr_cmp("127.0.0.1", "127.0.0.2" , false, -1);
+ test_sock_addr_cmp("127.0.0.2", "127.0.0.1" , false, 1);
+ test_sock_addr_cmp("127.0.1.2", "127.0.2.1" , false, -1);
+ test_sock_addr_cmp("127.0.2.1", "127.0.1.2" , false, 1);
+ test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", "127.0.1.2" , false, 1);
+ test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136",
+ "fe80::6af7:28ff:fefa:d136" , false, 0);
+ test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136",
+ "fe80::6af7:28ff:fefa:d137" , false, -1);
+ test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136",
+ "fe80:0000:0000:0000:6af7:28ff:fefa:d136" ,
+ false, 0);
+ test_sock_addr_cmp("::ffff:192.0.2.128", "192.0.2.128", false, 0);
+
+ test_sock_addr_cmp("127.0.0.1:123", "127.0.0.1:124" , true, -1);
+ test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136:123",
+ "fe80::6af7:28ff:fefa:d136:122" , true, 1);
+
+ /*
+ * Confirm equivalence of IPv6 sockets with and without
+ * square-brackets
+ */
+ test_sock_addr_cmp("[::1]:234", "::1:234", true, 0);
+ test_sock_addr_cmp("[fe80::6af7:28ff:fefa:d136]:234",
+ "fe80::6af7:28ff:fefa:d136:234",
+ true,
+ 0);
+ /* Check IPv4-mapped IPv6 addresses */
+ test_sock_addr_cmp("::ffff:172.16.0.27:977",
+ "172.16.0.27:977",
+ true,
+ 0);
+ test_sock_addr_cmp("[::ffff:172.16.0.27]:977",
+ "172.16.0.27:977",
+ true,
+ 0);
+
+ test_sock_addr_mask_from_string("127.0.0.1/8");
+ test_sock_addr_mask_from_string("::1/128");
+ test_sock_addr_mask_from_string("fe80::6af7:28ff:fefa:d136/64");
+ test_sock_addr_mask_from_string_bad("127.0.0.1");
+
+ test_connection_to_string("127.0.0.1:12345 127.0.0.2:54321");
+ test_connection_to_string("[fe80::6af7:28ff:fefa:d137]:12345 "
+ "[fe80::6af7:28ff:fefa:d138]:54321");
+
+ test_connection_from_string_bad("127.0.0.1:12345 127.0.0.2:");
+ test_connection_from_string_bad("127.0.0.1:12345");
+ test_connection_from_string_bad("127.0.0.1:12345 "
+ "[fe80::6af7:28ff:fefa:d136]:122");
+ test_connection_from_string_bad("Junk!");
+ test_connection_from_string_bad("More junk");
+
+ test_connection_list_read(CONN4, CONN4_SORT);
+ test_connection_list_read(CONN6, CONN6_SORT);
+ test_connection_list_read(CONN4 CONN6, CONN4_SORT CONN6_SORT);
+ test_connection_list_read(CONN4 "# Comment\n\n# Comment\n" CONN6,
+ CONN4_SORT CONN6_SORT);
+
+ test_connection_list_read_bad(CONN4 "# Comment\n\nJunk!!!\n" CONN6);
+ test_connection_list_read_bad(CONN4
+ "# Comment\n\n127.0.0.1: 127.0.0.1:124\n"
+ CONN6);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/rb_test.c b/ctdb/tests/src/rb_test.c
new file mode 100644
index 0000000..7dc0629
--- /dev/null
+++ b/ctdb/tests/src/rb_test.c
@@ -0,0 +1,336 @@
+/*
+ simple rb test tool
+
+ Copyright (C) Ronnie Sahlberg 2007
+
+ 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/time.h"
+
+#include <talloc.h>
+#include <assert.h>
+
+#include "lib/util/dlinklist.h"
+#include "lib/util/debug.h"
+
+#include "common/rb_tree.c"
+
+static struct timeval tp1,tp2;
+
+static void start_timer(void)
+{
+ gettimeofday(&tp1,NULL);
+}
+
+static double end_timer(void)
+{
+ gettimeofday(&tp2,NULL);
+ return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) -
+ (tp1.tv_sec + (tp1.tv_usec*1.0e-6));
+}
+
+int num_records=5;
+
+static void *callback(void *p, void *d)
+{
+ uint32_t *data = (uint32_t *)d;
+
+ if (d==NULL) {
+ data = (uint32_t *)p;
+ }
+
+ (*data)++;
+
+ return data;
+}
+
+static void *random_add(void *p, void *d)
+{
+ return p;
+}
+
+static int traverse(void *p, void *d)
+{
+ uint32_t *data = (uint32_t *)d;
+
+ printf("traverse data:%d\n",*data);
+ return 0;
+}
+
+static int random_traverse(void *p, void *d)
+{
+ printf("%s ",(char *)d);
+ return 0;
+}
+
+static uint32_t calc_checksum = 0;
+static int traverse_checksum(void *p, void *d)
+{
+ int i,j,k;
+
+ sscanf(d, "%d.%d.%d", &i, &j, &k);
+ calc_checksum += i*100+j*10+k;
+ return 0;
+}
+
+static int count_traverse(void *p, void *d)
+{
+ int *count = p;
+ (*count)++;
+ return 0;
+}
+
+static int count_traverse_abort(void *p, void *d)
+{
+ int *count = p;
+ (*count)++;
+ return -1;
+}
+
+/*
+ main program
+*/
+int main(int argc, const char *argv[])
+{
+ int traverse_count;
+ int i,j,k;
+ trbt_tree_t *tree;
+ uint32_t *data;
+ uint32_t key[3];
+ uint32_t key1[3] = {0,10,20};
+ uint32_t key2[3] = {0,10,21};
+ uint32_t key3[3] = {0,11,20};
+ uint32_t key4[3] = {2,10,20};
+ TALLOC_CTX *memctx;
+ uint32_t **u32array;
+ uint32_t checksum;
+
+ /* testing trbt_insert32_callback for num_records */
+ memctx = talloc_new(NULL);
+ assert(memctx != NULL);
+
+ u32array = talloc_array(memctx, uint32_t *, num_records);
+ assert(u32array != NULL);
+
+ tree = trbt_create(memctx, 0);
+ assert(tree != NULL);
+
+ for (i=0; i<num_records; i++) {
+ u32array[i] = talloc(u32array, uint32_t);
+ assert(u32array[i] != NULL);
+ *u32array[i] = 0;
+ trbt_insert32_callback(tree, i, callback, u32array[i]);
+ }
+ for (i=3; i<num_records; i++) {
+ trbt_insert32_callback(tree, i, callback, NULL);
+ }
+
+ /* first 3 keys should have data == 1
+ * the rest of the keys should have data == 2
+ */
+ for (i=0; i<num_records; i++) {
+ data = trbt_lookup32(tree, i);
+ assert(data != NULL);
+ if (i < 3) {
+ assert(*data == 1);
+ } else {
+ assert(*data == 2);
+ }
+ }
+
+ /* deleting key 2 */
+ talloc_free(u32array[2]);
+
+ /* deleting key 1 */
+ talloc_free(u32array[1]);
+
+ assert(talloc_total_size(memctx) == 212);
+
+ /* freeing tree */
+ talloc_free(memctx);
+
+
+ printf("testing trbt_insertarray32_callback\n");
+ memctx = talloc_new(NULL);
+ assert(memctx != NULL);
+
+ tree = trbt_create(memctx, 0);
+ assert(tree != NULL);
+
+ u32array = talloc_array(memctx, uint32_t *, 4);
+ assert(u32array != NULL);
+
+ for (i=0;i<4;i++) {
+ u32array[i] = talloc(u32array, uint32_t);
+ assert(u32array[i] != NULL);
+ *u32array[i] = 0;
+ }
+
+ trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]);
+ trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]);
+ trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]);
+ trbt_insertarray32_callback(tree, 3, key3, callback, u32array[2]);
+ trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]);
+ trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]);
+
+ data = trbt_lookuparray32(tree, 3, key1);
+ assert(data != NULL && *data == 3);
+ data = trbt_lookuparray32(tree, 3, key2);
+ assert(data != NULL && *data == 2);
+ data = trbt_lookuparray32(tree, 3, key3);
+ assert(data != NULL && *data == 1);
+ data = trbt_lookuparray32(tree, 3, key4);
+ assert(data == NULL);
+ trbt_traversearray32(tree, 3, traverse, NULL);
+
+ printf("\ndeleting key4\n");
+ talloc_free(trbt_lookuparray32(tree, 3, key4));
+
+ data = trbt_lookuparray32(tree, 3, key1);
+ assert(data != NULL && *data == 3);
+ data = trbt_lookuparray32(tree, 3, key2);
+ assert(data != NULL && *data == 2);
+ data = trbt_lookuparray32(tree, 3, key3);
+ assert(data != NULL && *data == 1);
+ data = trbt_lookuparray32(tree, 3, key4);
+ assert(data == NULL);
+ trbt_traversearray32(tree, 3, traverse, NULL);
+
+ printf("\ndeleting key2\n");
+ talloc_free(trbt_lookuparray32(tree, 3, key2));
+
+ data = trbt_lookuparray32(tree, 3, key1);
+ assert(data != NULL && *data == 3);
+ data = trbt_lookuparray32(tree, 3, key2);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key3);
+ assert(data != NULL && *data == 1);
+ data = trbt_lookuparray32(tree, 3, key4);
+ assert(data == NULL);
+ trbt_traversearray32(tree, 3, traverse, NULL);
+
+ printf("\ndeleting key3\n");
+ talloc_free(trbt_lookuparray32(tree, 3, key3));
+
+ data = trbt_lookuparray32(tree, 3, key1);
+ assert(data != NULL && *data == 3);
+ data = trbt_lookuparray32(tree, 3, key2);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key3);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key4);
+ assert(data == NULL);
+ trbt_traversearray32(tree, 3, traverse, NULL);
+
+ printf("\ndeleting key1\n");
+ talloc_free(trbt_lookuparray32(tree, 3, key1));
+
+ data = trbt_lookuparray32(tree, 3, key1);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key2);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key3);
+ assert(data == NULL);
+ data = trbt_lookuparray32(tree, 3, key4);
+ assert(data == NULL);
+ trbt_traversearray32(tree, 3, traverse, NULL);
+
+ talloc_free(tree);
+ talloc_free(memctx);
+
+
+ printf("\nrun random insert and delete for 60 seconds\n");
+ memctx = talloc_new(NULL);
+ assert(memctx != NULL);
+
+ tree = trbt_create(memctx, 0);
+ assert(tree != NULL);
+
+ i=0;
+ start_timer();
+ checksum = 0;
+ /* add and delete nodes from a 3 level tree fro 60 seconds.
+ each time a node is added or deleted, traverse the tree and
+ compute a checksum over the data stored in the tree and compare this
+ with a checksum we keep which contains what the checksum should be
+ */
+ while(end_timer() < 60.0){
+ char *str;
+
+ i++;
+ key[0]=random()%10;
+ key[1]=random()%10;
+ key[2]=random()%10;
+
+ if (random()%2) {
+ if (trbt_lookuparray32(tree, 3, key) == NULL) {
+ /* this node does not yet exist, add it to the
+ tree and update the checksum
+ */
+ str=talloc_asprintf(memctx, "%d.%d.%d", key[0],key[1],key[2]);
+ trbt_insertarray32_callback(tree, 3, key, random_add, str);
+ checksum += key[0]*100+key[1]*10+key[2];
+ }
+ } else {
+ if ((str=trbt_lookuparray32(tree, 3, key)) != NULL) {
+ /* this node does exist in the tree, delete
+ it and update the checksum accordingly
+ */
+ talloc_free(str);
+ checksum -= key[0]*100+key[1]*10+key[2];
+ }
+ }
+ /* traverse all nodes in the tree and calculate the checksum
+ it better match the one we keep track of in
+ 'checksum'
+ */
+ calc_checksum = 0;
+ trbt_traversearray32(tree, 3, traverse_checksum, NULL);
+ assert(checksum == calc_checksum);
+ }
+
+ /*
+ printf("\niterations passed:%d\n", i);
+ trbt_traversearray32(tree, 3, random_traverse, NULL);
+ printf("\n");
+ printf("first node: %s\n", (char *)trbt_findfirstarray32(tree, 3));
+ */
+
+ traverse_count = 0;
+ trbt_traversearray32(tree, 3, count_traverse, &traverse_count);
+ assert(traverse_count > 0);
+
+ traverse_count = 0;
+ trbt_traversearray32(tree, 3, count_traverse_abort, &traverse_count);
+ assert(traverse_count == 1);
+
+ printf("\ndeleting all entries\n");
+ for(i=0;i<10;i++){
+ for(j=0;j<10;j++){
+ for(k=0;k<10;k++){
+ key[0]=i;
+ key[1]=j;
+ key[2]=k;
+ talloc_free(trbt_lookuparray32(tree, 3, key));
+ }
+ }
+ }
+ trbt_traversearray32(tree, 3, random_traverse, NULL);
+
+ assert(talloc_total_size(memctx) == 16);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/reqid_test.c b/ctdb/tests/src/reqid_test.c
new file mode 100644
index 0000000..2a0828c
--- /dev/null
+++ b/ctdb/tests/src/reqid_test.c
@@ -0,0 +1,89 @@
+/*
+ reqid tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <talloc.h>
+#include <assert.h>
+
+#include "common/reqid.c"
+
+
+int main(void)
+{
+ struct reqid_context *reqid_ctx;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ int i, ret;
+ uint32_t reqid;
+ int *data, *tmp;
+
+ ret = reqid_init(mem_ctx, INT_MAX-200, &reqid_ctx);
+ assert(ret == 0);
+
+ data = talloc_zero(mem_ctx, int);
+ assert(data != 0);
+
+ for (i=0; i<1024*1024; i++) {
+ reqid = reqid_new(reqid_ctx, data);
+ assert(reqid != REQID_INVALID);
+ }
+
+ for (i=0; i<1024; i++) {
+ tmp = reqid_find(reqid_ctx, i, int);
+ assert(tmp == data);
+ }
+
+ for (i=0; i<1024; i++) {
+ ret = reqid_remove(reqid_ctx, i);
+ assert(ret == 0);
+ }
+
+ for (i=0; i<1024; i++) {
+ tmp = reqid_find(reqid_ctx, i, int);
+ assert(tmp == NULL);
+ }
+
+ for (i=0; i<1024; i++) {
+ ret = reqid_remove(reqid_ctx, i);
+ assert(ret == ENOENT);
+ }
+
+ talloc_free(reqid_ctx);
+ assert(talloc_get_size(mem_ctx) == 0);
+
+ ret = reqid_init(mem_ctx, INT_MAX-1, &reqid_ctx);
+ assert(ret == 0);
+
+ reqid = reqid_new(reqid_ctx, data);
+ assert(reqid == INT_MAX);
+
+ reqid = reqid_new(reqid_ctx, data);
+ assert(reqid == 0);
+
+ reqid_remove(reqid_ctx, 0);
+
+ reqid = reqid_new(reqid_ctx, data);
+ assert(reqid == 1);
+
+ talloc_free(reqid_ctx);
+
+ talloc_free(mem_ctx);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/run_event_test.c b/ctdb/tests/src/run_event_test.c
new file mode 100644
index 0000000..9454864
--- /dev/null
+++ b/ctdb/tests/src/run_event_test.c
@@ -0,0 +1,251 @@
+/*
+ run_event test wrapper
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <talloc.h>
+#include <tevent.h>
+
+#include "common/db_hash.c"
+#include "common/run_proc.c"
+#include "common/event_script.c"
+#include "common/run_event.c"
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "Usage: %s <scriptdir> run|list|enable|disable <options>\n", prog);
+ fprintf(stderr, " %s <scriptdir> run <timeout> <event> [<args>]\n", prog);
+ fprintf(stderr, " %s <scriptdir> list\n", prog);
+ fprintf(stderr, " %s <scriptdir> enable <scriptname>\n", prog);
+ fprintf(stderr, " %s <scriptdir> disable <scriptname>\n", prog);
+}
+
+static char *compact_args(const char **argv, int argc, int from)
+{
+ char *arg_str = NULL;
+ int i;
+
+ for (i = from; i < argc; i++) {
+ arg_str = talloc_asprintf_append(arg_str, "%s ", argv[i]);
+ if (arg_str == NULL) {
+ fprintf(stderr, "talloc_asprintf_append() failed\n");
+ exit(1);
+ }
+ }
+
+ return arg_str;
+}
+
+static void run_done(struct tevent_req *req)
+{
+ struct run_event_script_list **script_list =
+ tevent_req_callback_data_void(req);
+ bool status;
+ int ret;
+
+ status = run_event_recv(req, &ret, NULL, script_list);
+ if (!status) {
+ fprintf(stderr, "run_event_recv() failed, ret=%d\n", ret);
+ }
+}
+
+static void do_run(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct run_event_context *run_ctx,
+ int argc, const char **argv)
+{
+ struct tevent_req *req;
+ struct timeval timeout;
+ struct run_event_script_list *script_list = NULL;
+ char *arg_str;
+ unsigned int i;
+ int t;
+ bool wait_for_signal = false;
+
+ if (argc < 5) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ t = atoi(argv[3]);
+ if (t > 0) {
+ timeout = tevent_timeval_current_ofs(t, 0);
+ } else {
+ timeout = tevent_timeval_zero();
+ }
+
+ arg_str = compact_args(argv, argc, 5);
+
+ req = run_event_send(mem_ctx,
+ ev,
+ run_ctx,
+ argv[4],
+ arg_str,
+ timeout,
+ false);
+ if (req == NULL) {
+ fprintf(stderr, "run_event_send() failed\n");
+ return;
+ }
+
+ tevent_req_set_callback(req, run_done, &script_list);
+
+ tevent_req_poll(req, ev);
+
+ if (script_list == NULL || script_list->num_scripts == 0) {
+ printf("No event scripts found\n");
+ return;
+ }
+
+ printf("Event %s completed with result=%d\n",
+ argv[4], script_list->summary);
+ for (i=0; i<script_list->num_scripts; i++) {
+ struct run_event_script *s = &script_list->script[i];
+ printf("%s result=%d\n", s->name, s->summary);
+
+ if (s->summary == -ETIMEDOUT) {
+ wait_for_signal = true;
+ }
+ }
+
+ TALLOC_FREE(script_list);
+ TALLOC_FREE(req);
+
+ if (!wait_for_signal) {
+ return;
+ }
+
+ req = tevent_wakeup_send(
+ ev, ev, tevent_timeval_current_ofs(10, 0));
+ if (req == NULL) {
+ fprintf(stderr, "Could not wait for signal\n");
+ return;
+ }
+
+ tevent_req_poll(req, ev);
+ TALLOC_FREE(req);
+}
+
+static void do_list(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct run_event_context *run_ctx,
+ int argc, const char **argv)
+{
+ struct run_event_script_list *script_list = NULL;
+ unsigned int i;
+ int ret;
+
+ ret = run_event_list(run_ctx, mem_ctx, &script_list);
+ if (ret != 0) {
+ printf("Script list failed with result=%d\n", ret);
+ return;
+ }
+
+ if (script_list == NULL || script_list->num_scripts == 0) {
+ printf("No event scripts found\n");
+ return;
+ }
+
+ for (i=0; i<script_list->num_scripts; i++) {
+ printf("%s\n", script_list->script[i].name);
+ }
+}
+
+static void do_enable(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct run_event_context *run_ctx,
+ int argc, const char **argv)
+{
+ int ret;
+
+ if (argc != 4) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ ret = run_event_script_enable(run_ctx, argv[3]);
+ printf("Script enable %s completed with result=%d\n", argv[3], ret);
+}
+
+static void do_disable(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct run_event_context *run_ctx,
+ int argc, const char **argv)
+{
+ int ret;
+
+ if (argc != 4) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ ret = run_event_script_disable(run_ctx, argv[3]);
+ printf("Script disable %s completed with result=%d\n", argv[3], ret);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct run_proc_context *run_proc_ctx = NULL;
+ struct run_event_context *run_ctx = NULL;
+ int ret;
+
+ if (argc < 3) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "talloc_new() failed\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init() failed\n");
+ exit(1);
+ }
+
+ ret = run_proc_init(mem_ctx, ev, &run_proc_ctx);
+ if (ret != 0) {
+ fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ ret = run_event_init(mem_ctx, run_proc_ctx, argv[1], NULL, &run_ctx);
+ if (ret != 0) {
+ fprintf(stderr, "run_event_init() failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (strcmp(argv[2], "run") == 0) {
+ do_run(mem_ctx, ev, run_ctx, argc, argv);
+ } else if (strcmp(argv[2], "list") == 0) {
+ do_list(mem_ctx, ev, run_ctx, argc, argv);
+ } else if (strcmp(argv[2], "enable") == 0) {
+ do_enable(mem_ctx, ev, run_ctx, argc, argv);
+ } else if (strcmp(argv[2], "disable") == 0) {
+ do_disable(mem_ctx, ev, run_ctx, argc, argv);
+ } else {
+ fprintf(stderr, "Invalid command %s\n", argv[2]);
+ usage(argv[0]);
+ }
+
+ talloc_free(mem_ctx);
+ exit(0);
+}
+
diff --git a/ctdb/tests/src/run_proc_test.c b/ctdb/tests/src/run_proc_test.c
new file mode 100644
index 0000000..7cfb870
--- /dev/null
+++ b/ctdb/tests/src/run_proc_test.c
@@ -0,0 +1,111 @@
+/*
+ run_proc test wrapper
+
+ 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 <talloc.h>
+#include <tevent.h>
+
+#include "common/db_hash.c"
+#include "common/run_proc.c"
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ struct run_proc_context *run_ctx;
+ struct timeval tv;
+ char *output;
+ struct run_proc_result result;
+ pid_t pid;
+ int timeout, ret, fd;
+ bool status;
+
+ if (argc < 4) {
+ fprintf(stderr,
+ "Usage: %s <timeout> <stdin-fd> <program> <args>\n",
+ argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "talloc_new() failed\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init() failed\n");
+ exit(1);
+ }
+
+ timeout = atoi(argv[1]);
+ if (timeout <= 0) {
+ tv = tevent_timeval_zero();
+ } else {
+ tv = tevent_timeval_current_ofs(timeout, 0);
+ }
+
+ fd = atoi(argv[2]);
+ if (fd < 0) {
+ fd = -1;
+ }
+
+ ret = run_proc_init(mem_ctx, ev, &run_ctx);
+ if (ret != 0) {
+ fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ req = run_proc_send(mem_ctx, ev, run_ctx, argv[3], &argv[3], fd, tv);
+ if (req == NULL) {
+ fprintf(stderr, "run_proc_send() failed\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = run_proc_recv(req, &ret, &result, &pid, mem_ctx, &output);
+ if (! status) {
+ fprintf(stderr, "run_proc_recv() failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (result.sig > 0) {
+ printf("Process exited with signal %d\n", result.sig);
+ } else if (result.err > 0) {
+ printf("Process exited with error %d\n", result.err);
+ } else {
+ printf("Process exited with status %d\n", result.status);
+ }
+
+ if (pid != -1) {
+ printf("Child = %d\n", pid);
+ }
+
+ if (output != NULL) {
+ printf("Output = (%s)\n", output);
+ }
+
+ talloc_free(mem_ctx);
+
+ exit(0);
+}
diff --git a/ctdb/tests/src/sigcode.c b/ctdb/tests/src/sigcode.c
new file mode 100644
index 0000000..9e5ed81
--- /dev/null
+++ b/ctdb/tests/src/sigcode.c
@@ -0,0 +1,120 @@
+/*
+ Portability layer for signal codes
+
+ Copyright (C) Amitay Isaacs 2018
+
+ 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/>.
+*/
+
+/*
+ * These signals are as listed in POSIX standard
+ * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)
+ */
+
+#include "replace.h"
+#include "system/wait.h"
+
+struct {
+ const char *label;
+ int code;
+} sig_codes[] = {
+ { "SIGABRT", SIGABRT },
+ { "SIGALRM", SIGALRM },
+ { "SIBGUS", SIGBUS },
+ { "SIGCHLD", SIGCHLD },
+ { "SIGCONT", SIGCONT },
+ { "SIGFPE", SIGFPE },
+ { "SIGHUP", SIGHUP },
+ { "SIGILL", SIGILL },
+ { "SIGINT", SIGINT },
+ { "SIGKILL", SIGKILL },
+ { "SIGPIPE", SIGPIPE },
+ { "SIGQUIT", SIGQUIT },
+ { "SIGSEGV", SIGSEGV },
+ { "SIGSTOP", SIGSTOP },
+ { "SIGTERM", SIGTERM },
+ { "SIGTSTP", SIGTSTP },
+ { "SIGTTIN", SIGTTIN },
+ { "SIGTTOU", SIGTTOU },
+ { "SIGUSR1", SIGUSR1 },
+ { "SIGUSR2", SIGUSR2 },
+ { "SIGTRAP", SIGTRAP },
+ { "SIGURG", SIGURG },
+ { "SIGXCPU", SIGXCPU },
+ { "SIGXFSZ", SIGXFSZ },
+
+};
+
+static void dump(void)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(sig_codes); i++) {
+ printf("%s %d\n", sig_codes[i].label, sig_codes[i].code);
+ }
+}
+
+static void match_label(const char *str)
+{
+ int code = -1;
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(sig_codes); i++) {
+ if (strcasecmp(sig_codes[i].label, str) == 0) {
+ code = sig_codes[i].code;
+ break;
+ }
+ }
+
+ printf("%d\n", code);
+}
+
+static void match_code(int code)
+{
+ const char *label = "UNKNOWN";
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(sig_codes); i++) {
+ if (sig_codes[i].code == code) {
+ label = sig_codes[i].label;
+ break;
+ }
+ }
+
+ printf("%s\n", label);
+}
+
+int main(int argc, const char **argv)
+{
+ long int code;
+ char *endptr;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s dump|<sigcode>\n", argv[0]);
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "dump") == 0) {
+ dump();
+ } else {
+ code = strtol(argv[1], &endptr, 0);
+ if (*endptr == '\0') {
+ match_code(code);
+ } else {
+ match_label(argv[1]);
+ }
+ }
+
+ exit(0);
+}
diff --git a/ctdb/tests/src/sock_daemon_test.c b/ctdb/tests/src/sock_daemon_test.c
new file mode 100644
index 0000000..acafc9f
--- /dev/null
+++ b/ctdb/tests/src/sock_daemon_test.c
@@ -0,0 +1,1980 @@
+/*
+ sock daemon tests
+
+ 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/filesys.h"
+#include "system/network.h"
+#include "system/wait.h"
+
+#include <assert.h>
+
+#include "common/logging.c"
+#include "common/pkt_read.c"
+#include "common/pkt_write.c"
+#include "common/comm.c"
+#include "common/pidfile.c"
+#include "common/sock_daemon.c"
+#include "common/sock_io.c"
+
+struct dummy_wait_state {
+};
+
+static struct tevent_req *dummy_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct dummy_wait_state *state;
+ const char *sockpath = (const char *)private_data;
+ struct stat st;
+ int ret;
+
+ ret = stat(sockpath, &st);
+ assert(ret == 0);
+ assert(S_ISSOCK(st.st_mode));
+
+ req = tevent_req_create(mem_ctx, &state, struct dummy_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static bool dummy_wait_recv(struct tevent_req *req, int *perr)
+{
+ return true;
+}
+
+static int test1_startup_fail(void *private_data)
+{
+ return 1;
+}
+
+static int test1_startup(void *private_data)
+{
+ const char *sockpath = (const char *)private_data;
+ struct stat st;
+ int ret;
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+
+ return 0;
+}
+
+struct test1_startup_state {
+};
+
+static struct tevent_req *test1_startup_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct test1_startup_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct test1_startup_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_error(req, 2);
+ return tevent_req_post(req, ev);
+}
+
+static bool test1_startup_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static struct tevent_req *dummy_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sock_client_context *client,
+ uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ return NULL;
+}
+
+static bool dummy_read_recv(struct tevent_req *req, int *perr)
+{
+ if (perr != NULL) {
+ *perr = EINVAL;
+ }
+ return false;
+}
+
+static struct sock_socket_funcs dummy_socket_funcs = {
+ .read_send = dummy_read_send,
+ .read_recv = dummy_read_recv,
+};
+
+/*
+ * test1
+ *
+ * Check setup without actually running daemon
+ */
+
+static void test1(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ struct sock_daemon_funcs test1_funcs;
+ struct stat st;
+ int ret;
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ test1_funcs = (struct sock_daemon_funcs){
+ .startup = test1_startup_fail,
+ };
+
+ ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE",
+ &test1_funcs, NULL, &sockd);
+ assert(ret == 0);
+ assert(sockd != NULL);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = sock_daemon_run(ev, sockd, NULL, false, false, -1);
+ assert(ret == EIO);
+ talloc_free(sockd);
+
+ test1_funcs = (struct sock_daemon_funcs){
+ .startup_send = test1_startup_send,
+ .startup_recv = test1_startup_recv,
+ };
+
+ ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE",
+ &test1_funcs, NULL, &sockd);
+ assert(ret == 0);
+ assert(sockd != NULL);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = sock_daemon_run(ev, sockd, NULL, false, false, -1);
+ assert(ret == EIO);
+ talloc_free(sockd);
+
+ test1_funcs = (struct sock_daemon_funcs){
+ .startup = test1_startup,
+ .wait_send = dummy_wait_send,
+ .wait_recv = dummy_wait_recv,
+ };
+
+ ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE",
+ &test1_funcs, discard_const(sockpath), &sockd);
+ assert(ret == 0);
+ assert(sockd != NULL);
+
+ ret = sock_daemon_add_unix(sockd, sockpath, &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+
+ ret = sock_daemon_run(ev, sockd, NULL, false, false, -1);
+ assert(ret == 0);
+
+ talloc_free(mem_ctx);
+}
+
+/*
+ * test2
+ *
+ * Start daemon, check PID file, sock daemon functions, termination,
+ * exit code
+ */
+
+static int test2_startup(void *private_data)
+{
+ int fd = *(int *)private_data;
+ int ret = 1;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+ return 0;
+}
+
+static int test2_reconfigure(void *private_data)
+{
+ static bool first_time = true;
+ int fd = *(int *)private_data;
+ int ret = 2;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ if (first_time) {
+ first_time = false;
+ return 1;
+ }
+
+ return 0;
+}
+
+struct test2_reconfigure_state {
+ int fd;
+};
+
+static struct tevent_req *test2_reconfigure_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct test2_reconfigure_state *state;
+ static bool first_time = true;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct test2_reconfigure_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->fd = *(int *)private_data;
+
+ if (first_time) {
+ first_time = false;
+ tevent_req_error(req, 2);
+ } else {
+ tevent_req_done(req);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+static bool test2_reconfigure_recv(struct tevent_req *req, int *perr)
+{
+ struct test2_reconfigure_state *state = tevent_req_data(
+ req, struct test2_reconfigure_state);
+ int ret = 2;
+ ssize_t nwritten;
+
+ nwritten = write(state->fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static int test2_reopen_logs(void *private_data)
+{
+ static bool first_time = true;
+ int fd = *(int *)private_data;
+ int ret = 4;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ if (first_time) {
+ first_time = false;
+ return 1;
+ }
+
+ return 0;
+}
+
+struct test2_reopen_logs_state {
+ int fd;
+};
+
+static struct tevent_req *test2_reopen_logs_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct test2_reopen_logs_state *state;
+ static bool first_time = true;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct test2_reopen_logs_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->fd = *(int *)private_data;
+
+ if (first_time) {
+ first_time = false;
+ tevent_req_error(req, 2);
+ } else {
+ tevent_req_done(req);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+static bool test2_reopen_logs_recv(struct tevent_req *req, int *perr)
+{
+ struct test2_reopen_logs_state *state = tevent_req_data(
+ req, struct test2_reopen_logs_state);
+ int ret = 4;
+ ssize_t nwritten;
+
+ nwritten = write(state->fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static void test2_shutdown(void *private_data)
+{
+ int fd = *(int *)private_data;
+ int ret = 3;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+}
+
+struct test2_shutdown_state {
+ int fd;
+};
+
+static struct tevent_req *test2_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct test2_shutdown_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct test2_shutdown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->fd = *(int *)private_data;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void test2_shutdown_recv(struct tevent_req *req)
+{
+ struct test2_shutdown_state *state = tevent_req_data(
+ req, struct test2_shutdown_state);
+ int ret = 3;
+ ssize_t nwritten;
+
+ nwritten = write(state->fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+}
+
+static void test2(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct stat st;
+ int fd[2];
+ pid_t pid, pid2;
+ int ret;
+ ssize_t n;
+ int pidfile_fd;
+ char pidstr[20] = { 0 };
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ struct sock_daemon_funcs test2_funcs = {
+ .startup = test2_startup,
+ .reconfigure = test2_reconfigure,
+ .reopen_logs = test2_reopen_logs,
+ .shutdown = test2_shutdown,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE",
+ &test2_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ pidfile_fd = open(pidfile, O_RDONLY, 0644);
+ assert(pidfile_fd != -1);
+ ret = fstat(pidfile_fd, &st);
+ assert(ret == 0);
+ assert(S_ISREG(st.st_mode));
+ n = read(pidfile_fd, pidstr, sizeof(pidstr)-1);
+ assert(n != -1);
+ pid2 = (pid_t)atoi(pidstr);
+ assert(pid == pid2);
+ close(pidfile_fd);
+
+ ret = kill(pid, SIGUSR1);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 2);
+
+ ret = kill(pid, SIGUSR1);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 2);
+
+ ret = kill(pid, SIGHUP);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 4);
+
+ ret = kill(pid, SIGHUP);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 4);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 3);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ struct sock_daemon_funcs test2_funcs = {
+ .startup = test2_startup,
+ .reconfigure_send = test2_reconfigure_send,
+ .reconfigure_recv = test2_reconfigure_recv,
+ .reopen_logs_send = test2_reopen_logs_send,
+ .reopen_logs_recv = test2_reopen_logs_recv,
+ .shutdown_send = test2_shutdown_send,
+ .shutdown_recv = test2_shutdown_recv,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE",
+ &test2_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ ret = kill(pid, SIGUSR1);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 2);
+
+ ret = kill(pid, SIGUSR1);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 2);
+
+ ret = kill(pid, SIGHUP);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 4);
+
+ ret = kill(pid, SIGHUP);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 4);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 3);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+}
+
+/*
+ * test3
+ *
+ * Start daemon, test watching of (parent) PID
+ */
+
+static void test3(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct stat st;
+ pid_t pid_watch, pid, pid2;
+ int ret;
+
+ pid_watch = fork();
+ assert(pid_watch != -1);
+
+ if (pid_watch == 0) {
+ sleep(10);
+ exit(0);
+ }
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test3", "file:", "NOTICE",
+ NULL, NULL, &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, NULL, false, false, pid_watch);
+ assert(ret == ESRCH);
+
+ exit(0);
+ }
+
+ pid2 = waitpid(pid_watch, &ret, 0);
+ assert(pid2 == pid_watch);
+ assert(WEXITSTATUS(ret) == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+}
+
+/*
+ * test4
+ *
+ * Start daemon, test termination via wait_send function
+ */
+
+struct test4_wait_state {
+};
+
+static void test4_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *test4_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct test4_wait_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct test4_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = tevent_wakeup_send(state, ev,
+ tevent_timeval_current_ofs(10,0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test4_wait_done, req);
+
+ return req;
+}
+
+static void test4_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (! status) {
+ tevent_req_error(req, EIO);
+ } else {
+ tevent_req_done(req);
+ }
+}
+
+static bool test4_wait_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static struct sock_daemon_funcs test4_funcs = {
+ .wait_send = test4_wait_send,
+ .wait_recv = test4_wait_recv,
+};
+
+static void test4(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct stat st;
+ pid_t pid, pid2;
+ int ret;
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test4", "file:", "NOTICE",
+ &test4_funcs, NULL, &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == 0);
+
+ exit(0);
+ }
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+}
+
+/*
+ * test5
+ *
+ * Start daemon, multiple client connects, requests, disconnects
+ */
+
+#define TEST5_VALID_CLIENTS 10
+#define TEST5_MAX_CLIENTS 100
+
+struct test5_pkt {
+ uint32_t len;
+ int data;
+};
+
+struct test5_client_state {
+ int id;
+ int fd;
+ bool done;
+};
+
+static void test5_client_callback(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test5_client_state *state =
+ (struct test5_client_state *)private_data;
+ struct test5_pkt *pkt;
+ ssize_t n;
+ int ret;
+
+ if (buf == NULL) {
+ assert(buflen == 0);
+
+ ret = 0;
+ } else {
+ assert(buflen == sizeof(struct test5_pkt));
+ pkt = (struct test5_pkt *)buf;
+ assert(pkt->len == sizeof(struct test5_pkt));
+
+ ret = pkt->data;
+ }
+
+ assert(state->fd != -1);
+
+ n = write(state->fd, (void *)&ret, sizeof(int));
+ assert(n == sizeof(int));
+
+ state->done = true;
+}
+
+static int test5_client(const char *sockpath, int id, pid_t pid_server,
+ pid_t *client_pid)
+{
+ pid_t pid;
+ int fd[2];
+ int ret;
+ ssize_t n;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct test5_client_state state;
+ struct sock_queue *queue;
+ struct test5_pkt pkt;
+ int conn;
+
+ close(fd[0]);
+
+ ev = tevent_context_init(NULL);
+ assert(ev != NULL);
+
+ conn = sock_connect(sockpath);
+ assert(conn != -1);
+
+ state.id = id;
+ state.fd = fd[1];
+ state.done = false;
+
+ queue = sock_queue_setup(ev, ev, conn,
+ test5_client_callback, &state);
+ assert(queue != NULL);
+
+ pkt.len = 8;
+ pkt.data = 0xbaba;
+
+ ret = sock_queue_write(queue, (uint8_t *)&pkt,
+ sizeof(struct test5_pkt));
+ assert(ret == 0);
+
+ while (! state.done) {
+ tevent_loop_once(ev);
+ }
+
+ close(fd[1]);
+ state.fd = -1;
+
+ while (kill(pid_server, 0) == 0 || errno != ESRCH) {
+ sleep(1);
+ }
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ ret = 0;
+ n = read(fd[0], &ret, sizeof(ret));
+ if (n == 0) {
+ fprintf(stderr, "client id %d read 0 bytes\n", id);
+ }
+ assert(n == 0 || n == sizeof(ret));
+
+ close(fd[0]);
+
+ *client_pid = pid;
+ return ret;
+}
+
+struct test5_server_state {
+ int num_clients;
+};
+
+static bool test5_connect(struct sock_client_context *client,
+ pid_t pid,
+ void *private_data)
+{
+ struct test5_server_state *state =
+ (struct test5_server_state *)private_data;
+
+ if (state->num_clients == TEST5_VALID_CLIENTS) {
+ return false;
+ }
+
+ state->num_clients += 1;
+ assert(state->num_clients <= TEST5_VALID_CLIENTS);
+ return true;
+}
+
+static void test5_disconnect(struct sock_client_context *client,
+ void *private_data)
+{
+ struct test5_server_state *state =
+ (struct test5_server_state *)private_data;
+
+ state->num_clients -= 1;
+ assert(state->num_clients >= 0);
+}
+
+struct test5_read_state {
+ struct test5_pkt reply;
+};
+
+static void test5_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *test5_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sock_client_context *client,
+ uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test5_server_state *server_state =
+ (struct test5_server_state *)private_data;
+ struct tevent_req *req, *subreq;
+ struct test5_read_state *state;
+ struct test5_pkt *pkt;
+
+ req = tevent_req_create(mem_ctx, &state, struct test5_read_state);
+ assert(req != NULL);
+
+ assert(buflen == sizeof(struct test5_pkt));
+
+ pkt = (struct test5_pkt *)buf;
+ assert(pkt->data == 0xbaba);
+
+ state->reply.len = sizeof(struct test5_pkt);
+ state->reply.data = server_state->num_clients;
+
+ subreq = sock_socket_write_send(state, ev, client,
+ (uint8_t *)&state->reply,
+ state->reply.len);
+ assert(subreq != NULL);
+
+ tevent_req_set_callback(subreq, test5_read_done, req);
+
+ return req;
+}
+
+static void test5_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+ bool status;
+
+ status = sock_socket_write_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool test5_read_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static struct sock_socket_funcs test5_client_funcs = {
+ .connect = test5_connect,
+ .disconnect = test5_disconnect,
+ .read_send = test5_read_send,
+ .read_recv = test5_read_recv,
+};
+
+struct test5_wait_state {
+};
+
+static struct tevent_req *test5_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct test5_wait_state *state;
+ int fd = *(int *)private_data;
+ int ret = 1;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+ close(fd);
+
+ req = tevent_req_create(mem_ctx, &state, struct test5_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ return req;
+}
+
+static bool test5_wait_recv(struct tevent_req *req, int *perr)
+{
+ return true;
+}
+
+static struct sock_daemon_funcs test5_funcs = {
+ .wait_send = test5_wait_send,
+ .wait_recv = test5_wait_recv,
+};
+
+static void test5(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ pid_t pid_server, pid;
+ int fd[2], ret, i;
+ ssize_t n;
+ pid_t client_pid[TEST5_MAX_CLIENTS];
+
+ pid = getpid();
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid_server = fork();
+ assert(pid_server != -1);
+
+ if (pid_server == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ struct test5_server_state state;
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test5", "file:", "NOTICE",
+ &test5_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ state.num_clients = 0;
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &test5_client_funcs, &state);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ close(fd[0]);
+
+ for (i=0; i<TEST5_MAX_CLIENTS; i++) {
+ ret = test5_client(sockpath, i, pid_server, &client_pid[i]);
+ if (i < TEST5_VALID_CLIENTS) {
+ assert(ret == i+1);
+ } else {
+ assert(ret == 0);
+ }
+ }
+
+ for (i=TEST5_MAX_CLIENTS-1; i>=0; i--) {
+ kill(client_pid[i], SIGKILL);
+
+ pid = wait(&ret);
+ assert(pid != -1);
+ }
+
+ ret = kill(pid_server, SIGTERM);
+ assert(ret == 0);
+
+ pid = waitpid(pid_server, &ret, 0);
+ assert(pid == pid_server);
+ assert(WEXITSTATUS(ret) == 0);
+}
+
+/*
+ * test6
+ *
+ * Start daemon, test client connects, requests, replies, disconnects
+ */
+
+struct test6_pkt {
+ uint32_t len;
+ uint32_t data;
+};
+
+struct test6_client_state {
+ bool done;
+};
+
+static void test6_client_callback(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test6_client_state *state =
+ (struct test6_client_state *)private_data;
+ struct test6_pkt *pkt;
+
+ assert(buflen == sizeof(struct test6_pkt));
+ pkt = (struct test6_pkt *)buf;
+ assert(pkt->len == sizeof(struct test6_pkt));
+ assert(pkt->data == 0xffeeddcc);
+
+ state->done = true;
+}
+
+static void test6_client(const char *sockpath)
+{
+ struct tevent_context *ev;
+ struct test6_client_state state;
+ struct sock_queue *queue;
+ struct test6_pkt pkt;
+ int conn, ret;
+
+ ev = tevent_context_init(NULL);
+ assert(ev != NULL);
+
+ conn = sock_connect(sockpath);
+ assert(conn != -1);
+
+ state.done = false;
+
+ queue = sock_queue_setup(ev, ev, conn,
+ test6_client_callback, &state);
+ assert(queue != NULL);
+
+ pkt.len = 8;
+ pkt.data = 0xaabbccdd;
+
+ ret = sock_queue_write(queue, (uint8_t *)&pkt,
+ sizeof(struct test6_pkt));
+ assert(ret == 0);
+
+ while (! state.done) {
+ tevent_loop_once(ev);
+ }
+
+ talloc_free(ev);
+}
+
+struct test6_server_state {
+ struct sock_daemon_context *sockd;
+ int fd, done;
+};
+
+struct test6_read_state {
+ struct test6_server_state *server_state;
+ struct test6_pkt reply;
+};
+
+static void test6_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *test6_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sock_client_context *client,
+ uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct test6_server_state *server_state =
+ (struct test6_server_state *)private_data;
+ struct tevent_req *req, *subreq;
+ struct test6_read_state *state;
+ struct test6_pkt *pkt;
+
+ req = tevent_req_create(mem_ctx, &state, struct test6_read_state);
+ assert(req != NULL);
+
+ state->server_state = server_state;
+
+ assert(buflen == sizeof(struct test6_pkt));
+
+ pkt = (struct test6_pkt *)buf;
+ assert(pkt->data == 0xaabbccdd);
+
+ state->reply.len = sizeof(struct test6_pkt);
+ state->reply.data = 0xffeeddcc;
+
+ subreq = sock_socket_write_send(state, ev, client,
+ (uint8_t *)&state->reply,
+ state->reply.len);
+ assert(subreq != NULL);
+
+ tevent_req_set_callback(subreq, test6_read_done, req);
+
+ return req;
+}
+
+static void test6_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test6_read_state *state = tevent_req_data(
+ req, struct test6_read_state);
+ int ret;
+ bool status;
+
+ status = sock_socket_write_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->server_state->done = 1;
+ tevent_req_done(req);
+}
+
+static bool test6_read_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static struct sock_socket_funcs test6_client_funcs = {
+ .read_send = test6_read_send,
+ .read_recv = test6_read_recv,
+};
+
+struct test6_wait_state {
+ struct test6_server_state *server_state;
+};
+
+static void test6_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *test6_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct test6_server_state *server_state =
+ (struct test6_server_state *)private_data;
+ struct tevent_req *req, *subreq;
+ struct test6_wait_state *state;
+ ssize_t nwritten;
+ int ret = 1;
+
+ nwritten = write(server_state->fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+ close(server_state->fd);
+ server_state->fd = -1;
+
+ req = tevent_req_create(mem_ctx, &state, struct test6_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->server_state = (struct test6_server_state *)private_data;
+
+ subreq = tevent_wakeup_send(state, ev,
+ tevent_timeval_current_ofs(10,0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test6_wait_done, req);
+
+ return req;
+}
+
+static void test6_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test6_wait_state *state = tevent_req_data(
+ req, struct test6_wait_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (state->server_state->done == 0) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool test6_wait_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static struct sock_daemon_funcs test6_funcs = {
+ .wait_send = test6_wait_send,
+ .wait_recv = test6_wait_recv,
+};
+
+static void test6(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ pid_t pid_server, pid;
+ int fd[2], ret;
+ ssize_t n;
+
+ pid = getpid();
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid_server = fork();
+ assert(pid_server != -1);
+
+ if (pid_server == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ struct test6_server_state server_state = { 0 };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ server_state.fd = fd[1];
+
+ ret = sock_daemon_setup(mem_ctx, "test6", "file:", "NOTICE",
+ &test6_funcs, &server_state,
+ &sockd);
+ assert(ret == 0);
+
+ server_state.sockd = sockd;
+ server_state.done = 0;
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &test6_client_funcs, &server_state);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid);
+ assert(ret == 0);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ close(fd[0]);
+
+ test6_client(sockpath);
+
+ pid = waitpid(pid_server, &ret, 0);
+ assert(pid == pid_server);
+ assert(WEXITSTATUS(ret) == 0);
+}
+
+/*
+ * test7
+ *
+ * Start daemon twice, confirm PID file contention
+ */
+
+static void test7(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct sock_daemon_funcs test7_funcs;
+ struct stat st;
+ int fd[2];
+ pid_t pid, pid2;
+ int ret;
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ ssize_t n;
+
+ /* Reuse test2 funcs for the startup synchronisation */
+ test7_funcs = (struct sock_daemon_funcs) {
+ .startup = test2_startup,
+ .reconfigure = test2_reconfigure,
+ .shutdown = test2_shutdown,
+ };
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test7", "file:", "NOTICE",
+ &test7_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ ret = stat(pidfile, &st);
+ assert(ret == 0);
+ assert(S_ISREG(st.st_mode));
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test7-parent", "file:", "NOTICE",
+ &test7_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EEXIST);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 3);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+}
+
+/*
+ * test8
+ *
+ * Start daemon, confirm that create_session argument works as expected
+ */
+
+static void test8(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ int fd[2];
+ pid_t pid, pid2, sid;
+ int ret;
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ ssize_t n;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Reuse test2 funcs for the startup synchronisation */
+ struct sock_daemon_funcs test8_funcs = {
+ .startup = test2_startup,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE",
+ &test8_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ /* create_session false above, so pid != sid */
+ sid = getsid(pid);
+ assert(pid != sid);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Reuse test2 funcs for the startup synchronisation */
+ struct sock_daemon_funcs test8_funcs = {
+ .startup = test2_startup,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE",
+ &test8_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, true, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ /* create_session true above, so pid == sid */
+ sid = getsid(pid);
+ assert(pid == sid);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+}
+
+/*
+ * test9
+ *
+ * Confirm that do_fork causes the daemon to be forked as a separate child
+ */
+
+static void test9(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ int fd[2];
+ pid_t pid, pid2;
+ int ret;
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+ ssize_t n;
+ int pidfile_fd;
+ char pidstr[20] = { 0 };
+ struct stat st;
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Reuse test2 funcs for the startup synchronisation */
+ struct sock_daemon_funcs test9_funcs = {
+ .startup = test2_startup,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE",
+ &test9_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ /* do_fork false above, so pid should be active */
+ ret = kill(pid, 0);
+ assert(ret == 0);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* Reuse test2 funcs for the startup synchronisation */
+ struct sock_daemon_funcs test9_funcs = {
+ .startup = test2_startup,
+ .shutdown = test2_shutdown,
+ };
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE",
+ &test9_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, true, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ /* do_fork true above, so pid should have exited */
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ pidfile_fd = open(pidfile, O_RDONLY, 0644);
+ assert(pidfile_fd != -1);
+ n = read(pidfile_fd, pidstr, sizeof(pidstr)-1);
+ assert(n != -1);
+ pid2 = (pid_t)atoi(pidstr);
+ assert(pid != pid2);
+ close(pidfile_fd);
+
+ ret = kill(pid2, SIGTERM);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 3);
+
+ /*
+ * pid2 isn't our child, so can't call waitpid(). kill(pid2, 0)
+ * is unreliable - pid2 may have been recycled. Above indicates
+ * that the shutdown function was called, so just do 1 final
+ * check to see if pidfile has been removed.
+ */
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+
+ close(fd[0]);
+}
+
+static void test10_shutdown(void *private_data)
+{
+ int fd = *(int *)private_data;
+ int ret = 3;
+ ssize_t nwritten;
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+}
+
+struct test10_wait_state {
+};
+
+static void test10_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *test10_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ int fd = *(int *)private_data;
+ struct tevent_req *req, *subreq;
+ struct test10_wait_state *state;
+ size_t nwritten;
+ int ret = 1;
+
+ req = tevent_req_create(mem_ctx, &state, struct test10_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = tevent_wakeup_send(state, ev,
+ tevent_timeval_current_ofs(10, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test10_wait_done, req);
+
+ nwritten = write(fd, &ret, sizeof(ret));
+ assert(nwritten == sizeof(ret));
+
+ return req;
+}
+
+static void test10_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool test10_wait_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static struct sock_daemon_funcs test10_funcs = {
+ .shutdown = test10_shutdown,
+ .wait_send = test10_wait_send,
+ .wait_recv = test10_wait_recv,
+};
+
+/*
+ * test10
+ *
+ * Confirm that the daemon starts successfully if there is a stale socket
+ */
+
+static void test10(TALLOC_CTX *mem_ctx, const char *pidfile,
+ const char *sockpath)
+{
+ struct stat st;
+ int fd[2];
+ pid_t pid, pid2;
+ int ret;
+ ssize_t n;
+ int pidfile_fd;
+ char pidstr[20] = { 0 };
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE",
+ &test10_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ /* KILL will leave PID file and socket behind */
+ ret = kill (pid, SIGKILL);
+ assert(ret == 0);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ ret = stat(sockpath, &st);
+ assert(ret == 0);
+
+ close(fd[0]);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ struct tevent_context *ev;
+ struct sock_daemon_context *sockd;
+
+ close(fd[0]);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE",
+ &test10_funcs, &fd[1], &sockd);
+ assert(ret == 0);
+
+ ret = sock_daemon_add_unix(sockd, sockpath,
+ &dummy_socket_funcs, NULL);
+ assert(ret == 0);
+
+ ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1);
+ assert(ret == EINTR);
+
+ exit(0);
+ }
+
+ close(fd[1]);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 1);
+
+ pidfile_fd = open(pidfile, O_RDONLY, 0644);
+ assert(pidfile_fd != -1);
+ n = read(pidfile_fd, pidstr, sizeof(pidstr)-1);
+ assert(n != -1);
+ pid2 = (pid_t)atoi(pidstr);
+ assert(pid == pid2);
+ close(pidfile_fd);
+
+ ret = kill(pid, SIGTERM);
+ assert(ret == 0);
+
+ n = read(fd[0], &ret, sizeof(ret));
+ assert(n == sizeof(ret));
+ assert(ret == 3);
+
+ pid2 = waitpid(pid, &ret, 0);
+ assert(pid2 == pid);
+ assert(WEXITSTATUS(ret) == 0);
+
+ close(fd[0]);
+
+ ret = stat(pidfile, &st);
+ assert(ret == -1);
+
+ ret = stat(sockpath, &st);
+ assert(ret == -1);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ const char *pidfile, *sockpath;
+ int num;
+
+ if (argc != 4) {
+ fprintf(stderr, "%s <pidfile> <sockpath> <testnum>\n", argv[0]);
+ exit(1);
+ }
+
+ pidfile = argv[1];
+ sockpath = argv[2];
+ num = atoi(argv[3]);
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ switch (num) {
+ case 1:
+ test1(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 2:
+ test2(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 3:
+ test3(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 4:
+ test4(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 5:
+ test5(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 6:
+ test6(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 7:
+ test7(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 8:
+ test8(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 9:
+ test9(mem_ctx, pidfile, sockpath);
+ break;
+
+ case 10:
+ test10(mem_ctx, pidfile, sockpath);
+ break;
+
+ default:
+ fprintf(stderr, "Unknown test number %d\n", num);
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/sock_io_test.c b/ctdb/tests/src/sock_io_test.c
new file mode 100644
index 0000000..ba4b637
--- /dev/null
+++ b/ctdb/tests/src/sock_io_test.c
@@ -0,0 +1,283 @@
+/*
+ sock I/O tests
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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/filesys.h"
+#include "system/network.h"
+#include "system/wait.h"
+
+#include <assert.h>
+
+#include "common/sock_io.c"
+
+static int socket_init(const char *sockpath)
+{
+ struct sockaddr_un addr;
+ int fd, ret;
+ size_t len;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+
+ len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path));
+ assert(len < sizeof(addr.sun_path));
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ assert(fd != -1);
+
+ ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ assert(ret != -1);
+
+ ret = listen(fd, 10);
+ assert(ret != -1);
+
+ return fd;
+}
+
+static void test1_writer(int fd)
+{
+ uint8_t buf[1024];
+ ssize_t nwritten;
+ uint32_t len;
+
+ for (len = 10; len < 1000; len += 10) {
+ int value = len / 10;
+ uint32_t buflen = len + sizeof(uint32_t);
+
+ memset(buf, value, buflen);
+ memcpy(buf, &buflen, sizeof(uint32_t));
+
+ nwritten = sys_write(fd, buf, buflen);
+ assert(nwritten == buflen);
+ }
+}
+
+struct test1_reader_state {
+ size_t pkt_len;
+ bool done;
+};
+
+static void test1_reader(uint8_t *buf, size_t buflen, void *private_data)
+{
+ struct test1_reader_state *state =
+ (struct test1_reader_state *)private_data;
+
+ if (buflen == 0) {
+ state->done = true;
+ return;
+ }
+
+ assert(buflen == state->pkt_len);
+
+ state->pkt_len += 10;
+}
+
+static void test1(TALLOC_CTX *mem_ctx, const char *sockpath)
+{
+ struct test1_reader_state state;
+ struct tevent_context *ev;
+ struct sock_queue *queue;
+ pid_t pid;
+ int pfd[2], fd, ret;
+ ssize_t n;
+
+ ret = pipe(pfd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ int newfd;
+
+ close(pfd[0]);
+
+ fd = socket_init(sockpath);
+ assert(fd != -1);
+
+ ret = 1;
+ n = sys_write(pfd[1], &ret, sizeof(int));
+ assert(n == sizeof(int));
+
+ newfd = accept(fd, NULL, NULL);
+ assert(newfd != -1);
+
+ test1_writer(newfd);
+ close(newfd);
+ unlink(sockpath);
+
+ exit(0);
+ }
+
+ close(pfd[1]);
+
+ n = sys_read(pfd[0], &ret, sizeof(int));
+ assert(n == sizeof(int));
+ assert(ret == 1);
+
+ close(pfd[0]);
+
+ fd = sock_connect(sockpath);
+ assert(fd != -1);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ state.pkt_len = 10 + sizeof(uint32_t);
+ state.done = false;
+
+ queue = sock_queue_setup(mem_ctx, ev, fd, test1_reader, &state);
+ assert(queue != NULL);
+
+ while (! state.done) {
+ tevent_loop_once(ev);
+ }
+
+ talloc_free(queue);
+ talloc_free(ev);
+
+ pid = wait(&ret);
+ assert(pid != -1);
+}
+
+static void test2_reader(int fd)
+{
+ uint8_t buf[1024];
+ size_t pkt_len = 10 + sizeof(uint32_t);
+ ssize_t n;
+
+ while (1) {
+ n = sys_read(fd, buf, 1024);
+ assert(n != -1);
+
+ if (n == 0) {
+ return;
+ }
+
+ assert((size_t)n == pkt_len);
+ pkt_len += 10;
+ }
+}
+
+static void test2_dummy_reader(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ abort();
+}
+
+static void test2_writer(struct sock_queue *queue)
+{
+ uint8_t buf[1024];
+ uint32_t len;
+ int ret;
+
+ for (len = 10; len < 1000; len += 10) {
+ int value = len / 10;
+ uint32_t buflen = len + sizeof(uint32_t);
+
+ memset(buf, value, buflen);
+ memcpy(buf, &buflen, sizeof(uint32_t));
+
+ ret = sock_queue_write(queue, buf, buflen);
+ assert(ret == 0);
+ }
+}
+
+static void test2(TALLOC_CTX *mem_ctx, const char *sockpath)
+{
+ struct tevent_context *ev;
+ struct sock_queue *queue;
+ pid_t pid;
+ int pfd[2], fd, ret;
+ ssize_t n;
+
+ ret = pipe(pfd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ int newfd;
+
+ close(pfd[0]);
+
+ fd = socket_init(sockpath);
+ assert(fd != -1);
+
+ ret = 1;
+ n = sys_write(pfd[1], &ret, sizeof(int));
+ assert(n == sizeof(int));
+
+ newfd = accept(fd, NULL, NULL);
+ assert(newfd != -1);
+
+ test2_reader(newfd);
+ close(newfd);
+ unlink(sockpath);
+
+ exit(0);
+ }
+
+ close(pfd[1]);
+
+ n = sys_read(pfd[0], &ret, sizeof(int));
+ assert(n == sizeof(int));
+ assert(ret == 1);
+
+ close(pfd[0]);
+
+ fd = sock_connect(sockpath);
+ assert(fd != -1);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ queue = sock_queue_setup(mem_ctx, ev, fd, test2_dummy_reader, NULL);
+ assert(queue != NULL);
+
+ test2_writer(queue);
+
+ talloc_free(queue);
+ talloc_free(ev);
+
+ pid = wait(&ret);
+ assert(pid != -1);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ const char *sockpath;
+
+ if (argc != 2) {
+ fprintf(stderr, "%s <sockpath>\n", argv[0]);
+ exit(1);
+ }
+
+ sockpath = argv[1];
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ test1(mem_ctx, sockpath);
+ test2(mem_ctx, sockpath);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/srvid_test.c b/ctdb/tests/src/srvid_test.c
new file mode 100644
index 0000000..2367c6c
--- /dev/null
+++ b/ctdb/tests/src/srvid_test.c
@@ -0,0 +1,105 @@
+/*
+ srvid tests
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <assert.h>
+
+#include "common/db_hash.c"
+#include "common/srvid.c"
+
+#define TEST_SRVID 0xBE11223344556677
+
+static void test_handler(uint64_t srvid, TDB_DATA data, void *private_data)
+{
+ int *count = (int *)private_data;
+ (*count)++;
+}
+
+int main(void)
+{
+ struct srvid_context *srv = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ int ret;
+ int count = 0;
+
+ ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count);
+ assert(ret == EINVAL);
+
+ ret = srvid_init(mem_ctx, &srv);
+ assert(ret == 0);
+
+ ret = srvid_deregister(srv, TEST_SRVID, &count);
+ assert(ret == ENOENT);
+
+ ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count);
+ assert(ret == 0);
+
+ ret = srvid_exists(srv, TEST_SRVID, NULL);
+ assert(ret == 0);
+
+ ret = srvid_exists(srv, TEST_SRVID, &count);
+ assert(ret == 0);
+
+ ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null);
+ assert(ret == 0);
+ assert(count == 1);
+
+ ret = srvid_dispatch(srv, 0, TEST_SRVID, tdb_null);
+ assert(ret == 0);
+ assert(count == 2);
+
+ ret = srvid_deregister(srv, TEST_SRVID, NULL);
+ assert(ret == ENOENT);
+
+ ret = srvid_deregister(srv, TEST_SRVID, &count);
+ assert(ret == 0);
+
+ ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count);
+ assert(ret == 0);
+
+ talloc_free(tmp_ctx);
+ ret = srvid_exists(srv, TEST_SRVID, NULL);
+ assert(ret == ENOENT);
+
+ ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null);
+ assert(ret == ENOENT);
+
+ tmp_ctx = talloc_new(NULL);
+ assert(tmp_ctx != NULL);
+
+ ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, NULL);
+ assert(ret == 0);
+ ret = srvid_exists(srv, TEST_SRVID, &count);
+ assert(ret == ENOENT);
+
+ ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count);
+ assert(ret == 0);
+ ret = srvid_exists(srv, TEST_SRVID, &count);
+ assert(ret == 0);
+
+ talloc_free(srv);
+ assert(talloc_get_size(mem_ctx) == 0);
+ assert(talloc_get_size(tmp_ctx) == 0);
+
+ talloc_free(mem_ctx);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/system_socket_test.c b/ctdb/tests/src/system_socket_test.c
new file mode 100644
index 0000000..436f52a
--- /dev/null
+++ b/ctdb/tests/src/system_socket_test.c
@@ -0,0 +1,266 @@
+/*
+ Raw socket (un) marshalling tests
+
+ Copyright (C) Martin Schwenke 2018
+
+ 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 <assert.h>
+
+/* For ether_aton() */
+#ifdef _AIX
+#include <arpa/inet.h>
+#endif
+#ifdef __FreeBSD__
+#include <net/ethernet.h>
+#endif
+#ifdef linux
+#include <netinet/ether.h>
+#endif
+
+#include "common/system_socket.c"
+
+#include "protocol/protocol_util.h"
+
+#include "tests/src/test_backtrace.h"
+
+static void hexdump(uint8_t *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0) {
+ if (i != 0) {
+ printf("\n");
+ }
+ printf("%06zx", i);
+ }
+ printf(" %02x", buf[i]);
+ }
+
+ printf("\n%06zx\n", i);
+}
+
+static void test_types(void)
+{
+ /*
+ * We use this struct in the code but don't pack it due to
+ * portability concerns. It should have no padding.
+ */
+ struct {
+ struct ip ip;
+ struct tcphdr tcp;
+ } ip4pkt;
+
+ assert(sizeof(ip4pkt) == sizeof(struct ip) + sizeof(struct tcphdr));
+}
+
+#ifdef HAVE_PACKETSOCKET
+
+static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply)
+{
+ ctdb_sock_addr addr;
+ struct ether_addr *hw, *dhw;
+ uint8_t buf[512];
+ size_t buflen = sizeof(buf);
+ size_t len;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(addr_str, &addr, false);
+ assert(ret == 0);
+
+ hw = ether_aton(hwaddr_str);
+ assert(hw != NULL);
+
+ switch (addr.ip.sin_family) {
+ case AF_INET:
+ ret = arp_build(buf, buflen, &addr.ip, hw, reply, &dhw, &len);
+ break;
+ case AF_INET6:
+ ret = ip6_na_build(buf, buflen, &addr.ip6, hw, &dhw, &len);
+ break;
+ default:
+ abort();
+ }
+
+ assert(ret == 0);
+
+ hexdump(buf, len);
+}
+
+#else /* HAVE_PACKETSOCKET */
+
+static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply)
+{
+ fprintf(stderr, "PACKETSOCKET not supported\n");
+}
+
+#endif /* HAVE_PACKETSOCKET */
+
+static void test_tcp(const char *src_str,
+ const char *dst_str,
+ const char *seq_str,
+ const char *ack_str,
+ const char *rst_str)
+{
+ ctdb_sock_addr src, dst;
+ uint32_t seq, ack;
+ int rst;
+ uint8_t buf[512];
+ struct ether_header *eth;
+ size_t expected_len, len;
+ char src_str_out[64], dst_str_out[64];
+ uint32_t seq_out, ack_out;
+ int rst_out = 0;
+ uint16_t window;
+ int ret;
+
+ ret = ctdb_sock_addr_from_string(src_str, &src, true);
+ assert(ret == 0);
+
+ ret = ctdb_sock_addr_from_string(dst_str, &dst, true);
+ assert(ret == 0);
+
+ seq = atoi(seq_str);
+ ack = atoi(ack_str);
+ rst = atoi(rst_str);
+
+ /* Need to fake this up */
+ eth = (struct ether_header *) buf;
+ memset(eth, 0, sizeof(*eth));
+
+ switch (src.ip.sin_family) {
+ case AF_INET:
+ eth->ether_type = htons(ETHERTYPE_IP);
+ expected_len = 40;
+ ret = tcp4_build(buf + sizeof(struct ether_header),
+ sizeof(buf) - sizeof(struct ether_header),
+ &src.ip,
+ &dst.ip,
+ seq,
+ ack,
+ rst,
+ &len);
+ break;
+ case AF_INET6:
+ eth->ether_type = htons(ETHERTYPE_IP6);
+ expected_len = 60;
+ ret = tcp6_build(buf + sizeof(struct ether_header),
+ sizeof(buf) - sizeof(struct ether_header),
+ &src.ip6,
+ &dst.ip6,
+ seq,
+ ack,
+ rst,
+ &len);
+ break;
+ default:
+ abort();
+ }
+
+ assert(ret == 0);
+ assert(len == expected_len);
+
+ hexdump(buf + sizeof(struct ether_header), len);
+
+ switch (ntohs(eth->ether_type)) {
+ case ETHERTYPE_IP:
+ ret = tcp4_extract(buf + sizeof(struct ether_header),
+ len,
+ &src.ip,
+ &dst.ip,
+ &ack_out,
+ &seq_out,
+ &rst_out,
+ &window);
+ break;
+ case ETHERTYPE_IP6:
+ ret = tcp6_extract(buf + sizeof(struct ether_header),
+ len,
+ &src.ip6,
+ &dst.ip6,
+ &ack_out,
+ &seq_out,
+ &rst_out,
+ &window);
+ break;
+ default:
+ abort();
+ }
+
+ assert(ret == 0);
+
+ assert(seq == seq_out);
+ assert(ack == ack_out);
+ assert((rst != 0) == (rst_out != 0));
+ assert(window == htons(1234));
+
+ ret = ctdb_sock_addr_to_buf(src_str_out, sizeof(src_str_out),
+ &src, true);
+ assert(ret == 0);
+ ret = strcmp(src_str, src_str_out);
+ assert(ret == 0);
+
+ ret = ctdb_sock_addr_to_buf(dst_str_out, sizeof(dst_str_out),
+ &dst, true);
+ assert(ret == 0);
+ ret = strcmp(dst_str, dst_str_out);
+ assert(ret == 0);
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "usage: %s <cmd> [<arg> ...]\n", prog);
+ fprintf(stderr, " commands:\n");
+ fprintf(stderr, " types\n");
+ fprintf(stderr, " arp <ipaddr> <hwaddr> [reply]\n");
+ fprintf(stderr, " tcp <src> <dst> <seq> <ack> <rst>\n");
+
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+
+ if (argc < 2) {
+ usage(argv[0]);
+ }
+
+ test_backtrace_setup();
+
+ if (strcmp(argv[1], "types") == 0) {
+ test_types();
+ } else if (strcmp(argv[1], "arp") == 0) {
+ /*
+ * Extra arg indicates that a reply should be
+ * constructed for IPv4 - value is ignored
+ */
+ if (argc != 4 && argc != 5) {
+ usage(argv[0]);
+ }
+ test_arp(argv[2], argv[3], (argc == 5));
+ } else if (strcmp(argv[1], "tcp") == 0) {
+ if (argc != 7) {
+ usage(argv[0]);
+ }
+ test_tcp(argv[2], argv[3], argv[4], argv[5], argv[6]);
+ } else {
+ usage(argv[0]);
+ }
+
+ return 0;
+}
diff --git a/ctdb/tests/src/test_backtrace.c b/ctdb/tests/src/test_backtrace.c
new file mode 100644
index 0000000..aa3fc0c
--- /dev/null
+++ b/ctdb/tests/src/test_backtrace.c
@@ -0,0 +1,37 @@
+/*
+ Print a backtrace when a test aborts
+
+ Copyright (C) Martin Schwenke, DataDirect Networks 2022
+
+ 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 "lib/util/fault.h"
+#include "lib/util/signal.h"
+
+#include "tests/src/test_backtrace.h"
+
+static void test_abort_backtrace_handler(int sig)
+{
+ log_stack_trace();
+ CatchSignal(SIGABRT, SIG_DFL);
+ abort();
+}
+
+void test_backtrace_setup(void)
+{
+ CatchSignal(SIGABRT, test_abort_backtrace_handler);
+}
diff --git a/ctdb/tests/src/test_backtrace.h b/ctdb/tests/src/test_backtrace.h
new file mode 100644
index 0000000..a6089c9
--- /dev/null
+++ b/ctdb/tests/src/test_backtrace.h
@@ -0,0 +1,25 @@
+/*
+ Print a backtrace when a test aborts
+
+ Copyright (C) Martin Schwenke, DataDirect Networks 2022
+
+ 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/>.
+*/
+
+#ifndef __CTDB_TEST_BACKTRACE_H__
+#define __CTDB_TEST_BACKTRACE_H__
+
+void test_backtrace_setup(void);
+
+#endif /* __CTDB_TEST_BACKTRACE_H__ */
diff --git a/ctdb/tests/src/test_mutex_raw.c b/ctdb/tests/src/test_mutex_raw.c
new file mode 100644
index 0000000..926a525
--- /dev/null
+++ b/ctdb/tests/src/test_mutex_raw.c
@@ -0,0 +1,434 @@
+/*
+ * Test the system robust mutex implementation
+ *
+ * Copyright (C) 2016 Amitay Isaacs
+ * Copyright (C) 2018 Red Hat Inc.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * To run the test do the following:
+ *
+ * (a) Compile the test.
+ *
+ * gcc -O2 -g3 -o test-robust-mutex test-robust-mutex.c -lpthread
+ *
+ * (b) Start the "init" process.
+ *
+ * ./test-robust-mutex /tmp/shared-mutex init
+ *
+ * (c) Start any number of "worker" instances.
+ *
+ * ./test-robust-mutex <Shared memory file> worker <#> <Priority>
+ *
+ * <Shared memory file> e.g. /tmp/shared-mutex.
+ *
+ * <#> : Number of children processes.
+ *
+ * <Priority> : 0 - Normal, 1 - Realtime, 2 - Nice 20.
+ *
+ * For example:
+ *
+ * As non-root:
+ *
+ * $ while true ; do ./test-robust-mutex /tmp/foo worker 10 0 ; done;
+ *
+ * As root:
+ *
+ * while true ; do ./test-robust-mutex /tmp/foo worker 10 1 ; done;
+ *
+ * This creates 20 processes, 10 at normal priority and 10 at realtime
+ * priority, all taking the lock, being killed and recovering the lock.
+ *
+ * If while runnig (c) the processes block, it might mean that a futex wakeup
+ * was lost, or that the handoff of EOWNERDEAD did not happen correctly. In
+ * either case you can debug the resulting mutex like this:
+ *
+ * $ ./test-robust-mutex /tmp/shared-mutex debug
+ *
+ * This prints the PID of the process holding the mutex or nothing if
+ * the value was cleared by the kernel and now no process holds the mutex.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
+
+/* Define DEBUG to 1 to enable verbose debugging. */
+#define DEBUG 0
+
+/* Implement the worker. The worker has to do the following things:
+
+ * Succeed at locking the mutex, including possible recovery.
+ * Kill itself.
+
+ Other workers are attempting exactly the same thing in order to
+ test the loss and recovery of the robust mutex. */
+static void worker (const char *filename)
+{
+ pthread_mutex_t *mutex;
+ void *addr;
+ int ret, fd;
+
+ /* Open the file and map the shared robust mutex. */
+ fd = open(filename, O_RDWR, 0600);
+ if (fd == -1) {
+ perror ("FAIL: open");
+ exit(EXIT_FAILURE);
+ }
+
+ addr = mmap(NULL,
+ sizeof(pthread_mutex_t),
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED|MAP_FILE,
+ fd,
+ 0);
+ if (addr == NULL) {
+ perror ("FAIL: mmap");
+ exit(EXIT_FAILURE);
+ }
+
+ mutex = (pthread_mutex_t *)addr;
+
+ /* Every process will lock once, and die once. */
+ printf("INFO: pid %u locking\n", getpid());
+ do {
+ ret = pthread_mutex_lock(mutex);
+
+#if DEBUG
+ fprintf(stderr,
+ "DEBUG: pid %u lock attempt, ret=%d\n",
+ getpid(),
+ ret);
+#endif
+
+ if (ret == EOWNERDEAD) {
+ int rc;
+
+ rc = pthread_mutex_consistent(mutex);
+ if (rc == 0) {
+ pthread_mutex_unlock(mutex);
+ } else {
+ fprintf(stderr,
+ "FAIL: pthread_mutex_consistent "
+ "failed\n");
+ exit(EXIT_FAILURE);
+ }
+#if DEBUG
+ fprintf(stderr,
+ "DEBUG: pid %u recovery lock attempt, ret=%d\n",
+ getpid(),
+ ret);
+#endif
+ /* Will loop and try to lock again. */
+ }
+
+ } while (ret != 0);
+
+ printf ("INFO: pid %u locked, now killing\n", getpid());
+ kill(getpid(), SIGKILL);
+}
+
+/* One of three priority modes. */
+#define PRIO_NORMAL 0
+#define PRIO_REALTIME 1
+#define PRIO_NICE_20 2
+
+/* One of three operation modes. */
+#define MODE_INIT 0
+#define MODE_WORKER 1
+#define MODE_DEBUG 2
+
+/* Print usage information and exit. */
+static void usage (const char *name)
+{
+ fprintf(stderr,
+ "Usage: %s <file> [init|worker|debug] [#] [0|1|2]\n",
+ name);
+ exit(EXIT_FAILURE);
+}
+
+/* Set the process priority. */
+static void set_priority (int priority)
+{
+ struct sched_param p;
+ int ret;
+
+ switch (priority) {
+ case PRIO_REALTIME:
+ p.sched_priority = 1;
+ ret = sched_setscheduler(0, SCHED_FIFO, &p);
+ if (ret == -1)
+ perror("FAIL: sched_setscheduler");
+ break;
+
+ case PRIO_NICE_20:
+ ret = nice(-20);
+ if (ret == -1)
+ perror("FAIL: nice");
+ break;
+
+ case PRIO_NORMAL:
+ default:
+ /* Normal priority is the default. */
+ break;
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ int i, fd, ret, num_children, mode = -1, priority = PRIO_NORMAL;
+ const char *mode_str;
+ const char *file;
+ char *addr;
+ pthread_mutex_t *mutex;
+ pthread_mutexattr_t mattr;
+ pid_t pid;
+
+ /* One of three modes, init, worker, or debug. */
+ if (argc < 3 || argc > 5)
+ usage (argv[0]);
+
+ /*
+ * The shared memory file. Care should be taken here because if glibc
+ * is upgraded between runs the internals of the robust mutex could
+ * change. See this blog post about the dangers:
+ * https://developers.redhat.com/blog/2017/03/13/cc-library-upgrades-and-opaque-data-types-in-process-shared-memory/
+ * and how to avoid problems inherent in this.
+ */
+ file = argv[1];
+
+ /* Set the mode. */
+ mode_str = argv[2];
+ if (strcmp ("init", mode_str) == 0) {
+ mode = MODE_INIT;
+ } else if (strcmp ("worker", mode_str) == 0) {
+ mode = MODE_WORKER;
+ } else if (strcmp ("debug", mode_str) == 0) {
+ mode = MODE_DEBUG;
+ } else {
+ usage (argv[0]);
+ }
+
+ /* This is "worker" mode, so set the priority. */
+ if (mode == MODE_WORKER) {
+ priority = atoi(argv[4]);
+ set_priority(priority);
+ }
+
+ /* All modes open the file. */
+ fd = open(argv[1], O_CREAT|O_RDWR, 0600);
+ if (fd == -1) {
+ perror("FAIL: open");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = lseek(fd, 0, SEEK_SET);
+ if (ret != 0) {
+ perror("FAIL: lseek");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Truncate the file backing the mutex only in the init phase. */
+ if (mode == MODE_INIT) {
+ ret = ftruncate(fd, sizeof(pthread_mutex_t));
+ if (ret != 0) {
+ perror("FAIL: ftruncate");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Map the robust mutex. */
+ addr = mmap(NULL,
+ sizeof(pthread_mutex_t),
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED|MAP_FILE,
+ fd,
+ 0);
+ if (addr == NULL) {
+ perror("FAIL: mmap");
+ exit(EXIT_FAILURE);
+ }
+
+ mutex = (pthread_mutex_t *)(void *)addr;
+
+ /*
+ * In the debug mode we try to recover the mutex and print it.
+ * WARNING: All other processes should be stuck, otherwise they may
+ * change the value of the lock between trylock and the printing after
+ * EBUSY.
+ */
+ if (mode == MODE_DEBUG) {
+ ret = pthread_mutex_trylock(mutex);
+ if (ret == EOWNERDEAD) {
+ ret = pthread_mutex_consistent(mutex);
+ if (ret == 0) {
+ pthread_mutex_unlock(mutex);
+ } else {
+ fprintf(stderr,
+ "FAIL: pthread_mutex_consistent "
+ "failed\n");
+ exit (EXIT_FAILURE);
+ }
+ } else if (ret == EBUSY) {
+ printf("INFO: pid=%u\n", mutex->__data.__owner);
+ } else if (ret == 0) {
+ pthread_mutex_unlock(mutex);
+ }
+ exit(EXIT_SUCCESS);
+ }
+
+ /*
+ * Only the initializing process does initialization because it is
+ * undefined behaviour to re-initialize an already initialized mutex
+ * that was not destroyed.
+ */
+ if (mode == MODE_INIT) {
+
+ ret = pthread_mutexattr_init(&mattr);
+ if (ret != 0) {
+ fprintf(stderr,
+ "FAIL: pthread_mutexattr_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_mutexattr_settype(&mattr,
+ PTHREAD_MUTEX_ERRORCHECK);
+ if (ret != 0) {
+ fprintf(stderr,
+ "FAIL: pthread_mutexattr_settype failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_mutexattr_setpshared(&mattr,
+ PTHREAD_PROCESS_SHARED);
+ if (ret != 0) {
+ fprintf(stderr,
+ "FAIL: pthread_mutexattr_setpshared failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+ if (ret != 0) {
+ fprintf(stderr,
+ "FAIL: pthread_mutexattr_setrobust failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_mutex_init(mutex, &mattr);
+ if (ret != 0) {
+ fprintf(stderr, "FAIL: pthread_mutex_init failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ printf ("INFO: init: Mutex initialization complete.\n");
+ /* Never exit. */
+ for (;;)
+ sleep (1);
+ }
+
+ /* Acquire the mutext for the first time. Might be dead.
+ Might also be concurrent with the high-priority threads. */
+ fprintf(stderr,
+ "INFO: parent: Acquiring mutex (pid = %d).\n",
+ getpid());
+ do {
+ ret = pthread_mutex_lock(mutex);
+
+ /* Not consistent? Try to make it so. */
+ if (ret == EOWNERDEAD) {
+ int rc;
+
+ rc = pthread_mutex_consistent(mutex);
+ if (rc == 0) {
+ pthread_mutex_unlock (mutex);
+ } else {
+ fprintf(stderr,
+ "FAIL: pthread_mutex_consistent "
+ "failed\n");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Will loop and try to lock again. */
+ fprintf(stderr,
+ "INFO: parent: Unlock recovery ret = %d\n",
+ ret);
+ }
+
+ } while (ret != 0);
+
+ /*
+ * Set the parent process into it's own process group (hides the
+ * children).
+ */
+ setpgid(0, 0);
+
+ /* Create # of children. */
+ fprintf(stderr, "INFO: parent: Creating children\n");
+ num_children = atoi(argv[3]);
+
+ for (i = 0; i < num_children; i++) {
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "FAIL: fork() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (pid == 0) {
+ close(fd);
+ worker(file);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ fprintf(stderr, "INFO: parent: Waiting for children\n");
+
+ /* Unlock the recently acquired mutex or the old lost mutex. */
+ ret = pthread_mutex_unlock(mutex);
+ if (ret != 0) {
+ fprintf(stderr, "FAIL: pthread_mutex_unlock failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * All threads are running now, and each will take the lock and
+ * die in turn. When they are all dead we will exit and be started
+ * again by the caller.
+ */
+ for (i = 0; i < num_children; i++) {
+ int status;
+ pid = waitpid(-1, &status, 0);
+ if (pid <= 0) {
+ fprintf(stderr, "FAIL: waitpid() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ fprintf(stderr,
+ "INFO: parent: Reaped %u\n",
+ (unsigned int) pid);
+ }
+
+ /* We never unlink fd. The file must be cleaned up by the caller. */
+ close(fd);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/ctdb/tests/src/test_options.c b/ctdb/tests/src/test_options.c
new file mode 100644
index 0000000..2c64404
--- /dev/null
+++ b/ctdb/tests/src/test_options.c
@@ -0,0 +1,245 @@
+/*
+ CTDB tests commandline options
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+#include <assert.h>
+#include <popt.h>
+#include <talloc.h>
+
+#include "lib/util/debug.h"
+
+#include "common/logging.h"
+#include "common/path.h"
+
+#include "tests/src/test_options.h"
+
+static struct test_options _values;
+
+static struct poptOption options_basic[] = {
+ {
+ .longName = "socket",
+ .shortName = 's',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.socket,
+ .descrip = "CTDB socket path",
+ .argDescrip = "filename",
+ },
+ {
+ .longName = "timelimit",
+ .shortName = 't',
+ .argInfo = POPT_ARG_INT,
+ .arg = &_values.timelimit,
+ .descrip = "Time limit (in seconds)",
+ },
+ {
+ .longName = "num-nodes",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_INT,
+ .arg = &_values.num_nodes,
+ .descrip = "Number of cluster nodes",
+ },
+ {
+ .longName = "debug",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.debugstr,
+ .descrip = "Debug level",
+ },
+ {
+ .longName = "interactive",
+ .shortName = 'i',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &_values.interactive,
+ .val = 0,
+ .descrip = "Interactive output",
+ },
+ POPT_TABLEEND
+};
+
+#define TEST_OPTIONS_BASIC \
+ { \
+ .argInfo = POPT_ARG_INCLUDE_TABLE, \
+ .arg = options_basic, \
+ .descrip = "General options:", \
+ },
+
+static struct poptOption options_database[] = {
+ {
+ .longName = "database",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.dbname,
+ .descrip = "CTDB database name",
+ },
+ {
+ .longName = "key",
+ .shortName = 'k',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.keystr,
+ .descrip = "Name of database key",
+ },
+ {
+ .longName = "value",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.valuestr,
+ .descrip = "Value of database key",
+ },
+ {
+ .longName = "dbtype",
+ .shortName = 'T',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &_values.dbtype,
+ .descrip = "CTDB database type",
+ },
+ POPT_TABLEEND
+};
+
+#define TEST_OPTIONS_DATABASE \
+ { \
+ .argInfo = POPT_ARG_INCLUDE_TABLE, \
+ .arg = options_database, \
+ .descrip = "Database options:", \
+ },
+
+static void set_defaults_basic(struct test_options *opts)
+{
+ /* Set default options */
+ opts->socket = path_socket(NULL, "ctdbd"); /* leaked */
+ assert(opts->socket != NULL);
+
+ opts->timelimit = 10;
+ opts->num_nodes = 1;
+ opts->debugstr = "ERR";
+ opts->interactive = 0;
+}
+
+static void set_defaults_database(struct test_options *opts)
+{
+ opts->dbname = NULL;
+ opts->keystr = NULL;
+ opts->valuestr = NULL;
+ opts->dbtype = "volatile";
+}
+
+static bool verify_options_basic(struct test_options *opts)
+{
+ int log_level;
+ bool status;
+
+ status = debug_level_parse(opts->debugstr, &log_level);
+ if (! status) {
+ fprintf(stderr, "Error: Invalid debug string '%s'\n",
+ opts->debugstr);
+ return false;
+ }
+
+ debuglevel_set(log_level);
+
+ return true;
+}
+
+static bool verify_options_database(struct test_options *opts)
+{
+ if (opts->dbname == NULL) {
+ fprintf(stderr, "Error: Please specify database\n");
+ return false;
+ }
+ if (opts->keystr == NULL) {
+ fprintf(stderr, "Error: Please specify key name\n");
+ return false;
+ }
+
+ if ((strcmp(opts->dbtype, "volatile") != 0) &&
+ (strcmp(opts->dbtype, "persistent") != 0) &&
+ (strcmp(opts->dbtype, "replicated") != 0)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool process_options_common(int argc, const char **argv,
+ struct poptOption *options)
+{
+ poptContext pc;
+ int opt;
+
+ pc = poptGetContext(argv[0], argc, argv, options,
+ POPT_CONTEXT_KEEP_FIRST);
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ fprintf(stderr, "Invalid option %s: %s\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ return false;
+ }
+
+ return true;
+}
+
+bool process_options_basic(int argc, const char **argv,
+ const struct test_options **opts)
+{
+ struct poptOption options[] = {
+ POPT_AUTOHELP
+ TEST_OPTIONS_BASIC
+ POPT_TABLEEND
+ };
+
+ set_defaults_basic(&_values);
+
+ if (! process_options_common(argc, argv, options)) {
+ return false;
+ }
+
+ if (! verify_options_basic(&_values)) {
+ return false;
+ }
+
+ *opts = &_values;
+ return true;
+}
+
+bool process_options_database(int argc, const char **argv,
+ const struct test_options **opts)
+{
+ struct poptOption options[] = {
+ POPT_AUTOHELP
+ TEST_OPTIONS_BASIC
+ TEST_OPTIONS_DATABASE
+ POPT_TABLEEND
+ };
+
+ set_defaults_basic(&_values);
+ set_defaults_database(&_values);
+
+ if (! process_options_common(argc, argv, options)) {
+ return false;
+ }
+
+ if (! verify_options_basic(&_values)) {
+ return false;
+ }
+ if (! verify_options_database(&_values)) {
+ return false;
+ }
+
+ *opts = &_values;
+ return true;
+}
diff --git a/ctdb/tests/src/test_options.h b/ctdb/tests/src/test_options.h
new file mode 100644
index 0000000..1e194c9
--- /dev/null
+++ b/ctdb/tests/src/test_options.h
@@ -0,0 +1,44 @@
+/*
+ CTDB tests commandline options
+
+ Copyright (C) Amitay Isaacs 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TEST_OPTIONS_H__
+#define __TEST_OPTIONS_H__
+
+struct test_options {
+ /* Basic options */
+ const char *socket;
+ int timelimit;
+ int num_nodes;
+ const char *debugstr;
+ int interactive;
+
+ /* Database options */
+ const char *dbname;
+ const char *keystr;
+ const char *valuestr;
+ const char *dbtype;
+};
+
+bool process_options_basic(int argc, const char **argv,
+ const struct test_options **opts);
+
+bool process_options_database(int argc, const char **argv,
+ const struct test_options **opts);
+
+#endif /* __TEST_OPTIONS_H__ */
diff --git a/ctdb/tests/src/tmon_ping_test.c b/ctdb/tests/src/tmon_ping_test.c
new file mode 100644
index 0000000..491792d
--- /dev/null
+++ b/ctdb/tests/src/tmon_ping_test.c
@@ -0,0 +1,381 @@
+/*
+ Test trivial FD monitoring
+
+ Copyright (C) Martin Schwenke, DataDirect Networks 2022
+
+ 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/filesys.h"
+#include "system/network.h"
+#include "system/wait.h"
+
+#include <talloc.h>
+#include <tevent.h>
+#include <assert.h>
+
+#include "lib/util/tevent_unix.h"
+
+#include "common/tmon.h"
+
+#include "tests/src/test_backtrace.h"
+
+struct test_state {
+ const char *label;
+ unsigned long async_wait_time;
+ unsigned long blocking_sleep_time;
+};
+
+static void test_tmon_ping_done(struct tevent_req *subreq);
+static void test_async_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *test_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *label,
+ int fd,
+ int direction,
+ unsigned long timeout,
+ unsigned long interval,
+ unsigned long async_wait_time,
+ unsigned long blocking_sleep_time)
+{
+ struct tevent_req *req, *subreq;
+ struct test_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct test_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->label = label;
+ state->async_wait_time = async_wait_time;
+ state->blocking_sleep_time = blocking_sleep_time;
+
+ subreq = tmon_ping_send(state,
+ ev,
+ fd,
+ direction,
+ timeout,
+ interval);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test_tmon_ping_done, req);
+
+ if (state->async_wait_time != 0) {
+ fprintf(stderr,
+ "%s: async wait start %lu\n",
+ state->label,
+ state->async_wait_time);
+ }
+ subreq = tevent_wakeup_send(state,
+ ev,
+ tevent_timeval_current_ofs(
+ (uint32_t)async_wait_time, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test_async_wait_done, req);
+
+ return req;
+}
+
+static void test_tmon_ping_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test_state *state = tevent_req_data(req, struct test_state);
+ bool status;
+ int err;
+
+ status = tmon_ping_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!status) {
+ switch(err) {
+ case EPIPE:
+ fprintf(stderr, "%s: pipe closed\n", state->label);
+ break;
+ case ETIMEDOUT:
+ fprintf(stderr, "%s: ping timeout\n", state->label);
+ break;
+ default:
+ fprintf(stderr, "%s: error (%d)\n", state->label, err);
+ }
+ tevent_req_error(req, err);
+ return;
+ }
+
+ fprintf(stderr, "%s: done\n", state->label);
+ tevent_req_done(req);
+}
+
+static void test_async_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct test_state *state = tevent_req_data(req, struct test_state);
+ unsigned int left;
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!status) {
+ fprintf(stderr,
+ "%s: tevent_wakeup_recv() failed\n",
+ state->label);
+ /* Ignore error */
+ }
+ if (state->async_wait_time != 0) {
+ fprintf(stderr, "%s: async wait end\n", state->label);
+ }
+
+ if (state->blocking_sleep_time == 0) {
+ goto done;
+ }
+
+ fprintf(stderr,
+ "%s: blocking sleep start %lu\n",
+ state->label,
+ state->blocking_sleep_time);
+ left = sleep((unsigned int)state->blocking_sleep_time);
+ fprintf(stderr,
+ "%s: blocking sleep end\n",
+ state->label);
+ if (left != 0) {
+ tevent_req_error(req, EINTR);
+ return;
+ }
+
+done:
+ tevent_req_done(req);
+}
+
+static bool test_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static int test_one(bool is_parent,
+ int sync_fd,
+ int fd,
+ int direction,
+ unsigned long timeout,
+ unsigned long interval,
+ unsigned long async_wait_time,
+ unsigned long blocking_sleep_time)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ bool status;
+ char buf[1] = "";
+ ssize_t count;
+ int err;
+ int ret;
+
+ if (!is_parent) {
+ count = read(sync_fd, buf, sizeof(buf));
+ assert(count == 1);
+ assert(buf[0] == '\0');
+ close(sync_fd);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ req = test_send(mem_ctx,
+ ev,
+ is_parent ? "parent" : "child",
+ fd,
+ direction,
+ timeout,
+ interval,
+ async_wait_time,
+ blocking_sleep_time);
+ if (req == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (is_parent) {
+ count = write(sync_fd, buf, sizeof(buf));
+ assert(count == 1);
+ }
+
+ status = tevent_req_poll(req, ev);
+ if (!status) {
+ ret = EIO;
+ goto done;
+ }
+
+ status = test_recv(req, &err);
+ ret = status ? 0 : err;
+
+done:
+ return ret;
+}
+
+static void test(unsigned long parent_timeout,
+ unsigned long parent_interval,
+ unsigned long parent_async_wait_time,
+ unsigned long parent_blocking_sleep_time,
+ int parent_result,
+ unsigned long child_timeout,
+ unsigned long child_interval,
+ unsigned long child_async_wait_time,
+ unsigned long child_blocking_sleep_time,
+ int child_result)
+{
+ int sync[2];
+ int fd[2];
+ pid_t pid;
+ int wstatus;
+ int ret;
+
+ /* Pipe for synchronisation */
+ ret = pipe(sync);
+ assert(ret == 0);
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* child */
+ close(sync[1]);
+ close(fd[0]);
+
+ ret = test_one(false,
+ sync[0],
+ fd[1],
+ TMON_FD_BOTH,
+ child_timeout,
+ child_interval,
+ child_async_wait_time,
+ child_blocking_sleep_time);
+ _exit(ret);
+ }
+
+ /* Parent */
+ close(sync[0]);
+ close(fd[1]);
+
+ ret = test_one(true,
+ sync[1],
+ fd[0],
+ TMON_FD_BOTH,
+ parent_timeout,
+ parent_interval,
+ parent_async_wait_time,
+ parent_blocking_sleep_time);
+ assert(ret == parent_result);
+
+ /* Close to mimick exit, so child status can be checked below */
+ close(fd[0]);
+
+ /* Abort if child failed */
+ waitpid(pid, &wstatus, 0);
+ if (WIFEXITED(wstatus)) {
+ assert(WEXITSTATUS(wstatus) == child_result);
+ }
+}
+
+struct test_inputs {
+ unsigned int timeout;
+ unsigned int interval;
+ unsigned int async_wait_time;
+ unsigned int blocking_sleep_time;
+ int expected_result;
+};
+
+static void get_test_inputs(const char **args, struct test_inputs *inputs)
+{
+ if (strcmp(args[0], "false") == 0) {
+ inputs->interval = 0;
+ } else if (strcmp(args[0], "true") == 0) {
+ inputs->interval = 1;
+ } else {
+ inputs->interval = strtoul(args[0], NULL, 0);
+ }
+
+ inputs->timeout = strtoul(args[1], NULL, 0);
+ inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0);
+ inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0);
+ inputs->expected_result = (int)strtoul(args[4], NULL, 0);
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "usage: %s "
+ "\\\n\t"
+ "<parent_send_pings> "
+ "<parent_ping_timeout> "
+ "<parent_async_wait_time> "
+ "<parent_blocking_sleep_time> "
+ "<parent_expected_result> "
+ "\\\n\t"
+ "<child_send_pings> "
+ "<child_ping_timeout> "
+ "<child_async_wait_time> "
+ "<child_blocking_sleep_time> "
+ "<child_expected_result> "
+ "\n",
+ prog);
+ exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+ struct test_inputs parent;
+ struct test_inputs child;
+
+ if (argc != 11) {
+ usage(argv[0]);
+ }
+
+ test_backtrace_setup();
+
+ get_test_inputs(&argv[1], &parent);
+ get_test_inputs(&argv[6], &child);
+
+ test(parent.timeout,
+ parent.interval,
+ parent.async_wait_time,
+ parent.blocking_sleep_time,
+ parent.expected_result,
+ child.timeout,
+ child.interval,
+ child.async_wait_time,
+ child.blocking_sleep_time,
+ child.expected_result);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/tmon_test.c b/ctdb/tests/src/tmon_test.c
new file mode 100644
index 0000000..fb1f5eb
--- /dev/null
+++ b/ctdb/tests/src/tmon_test.c
@@ -0,0 +1,406 @@
+/*
+ Test trivial FD monitoring
+
+ Copyright (C) Martin Schwenke, DataDirect Networks 2022
+
+ 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/filesys.h"
+#include "system/wait.h"
+
+#include <talloc.h>
+#include <tevent.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "lib/util/tevent_unix.h"
+
+#include "common/tmon.h"
+
+#include "tests/src/test_backtrace.h"
+
+struct test_write_state {
+ const char *write_data;
+ size_t write_data_len;
+ unsigned int offset;
+ struct tevent_req *req;
+};
+
+static int test_write_callback(void *private_data, struct tmon_pkt *pkt)
+{
+ struct test_write_state *state = talloc_get_type_abort(
+ private_data, struct test_write_state);
+ bool status;
+ size_t len;
+ char *end;
+ int err;
+ char c;
+ const char *t;
+
+ assert(state->write_data != NULL);
+
+ len = strlen(state->write_data);
+ if (state->offset >= len) {
+ return TMON_STATUS_EXIT;
+ }
+
+ c = state->write_data[state->offset];
+ state->offset++;
+
+ if (isdigit(c)) {
+ err = c - '0';
+
+ if (err == 0) {
+ status = tmon_set_exit(pkt);
+ } else {
+ status = tmon_set_errno(pkt, err);
+ }
+ } else if (ispunct(c)) {
+ switch (c) {
+ case '.':
+ return TMON_STATUS_SKIP;
+ break;
+ case '!':
+ status = tmon_set_ping(pkt);
+ break;
+ case '#':
+ /* Additional errno syntax: #nnn[;] */
+ t = &state->write_data[state->offset];
+ err = (int)strtol(t, &end, 10);
+ state->offset += (end - t);
+ if (state->write_data[state->offset] == ';') {
+ state->offset++;
+ }
+ status = tmon_set_errno(pkt, err);
+ break;
+ default:
+ status = false;
+ }
+ } else if (isascii(c) && !isspace(c)) {
+ status = tmon_set_ascii(pkt, c);
+ } else {
+ status = tmon_set_custom(pkt, (uint16_t)c);
+ }
+
+ if (!status) {
+ return EDOM;
+ }
+
+ t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE");
+ if (t == NULL) {
+ return 0;
+ }
+
+ /*
+ * This is write-skip mode: tmon_write() is called directly
+ * here in the callback and TMON_WRITE_SKIP is returned. This
+ * allows tmon_write() to be exercised by reusing test cases
+ * rather than writing extra test code and test cases.
+ */
+
+ status = tmon_write(state->req, pkt);
+ if (!status) {
+ return EIO;
+ }
+
+ return TMON_STATUS_SKIP;
+}
+
+static void test_tmon_done(struct tevent_req *subreq);
+
+static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd,
+ const char *write_data)
+{
+ struct tevent_req *req, *subreq;
+ struct test_write_state *state;
+ struct tmon_actions actions = {
+ .write_callback = test_write_callback,
+ };
+
+ req = tevent_req_create(mem_ctx, &state, struct test_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->write_data = write_data;
+ state->offset = 0;
+
+ subreq = tmon_send(state,
+ ev,
+ fd,
+ TMON_FD_WRITE,
+ 0,
+ 1,
+ &actions,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test_tmon_done, req);
+
+ /* Nasty hack, but OK to cheapen testing - see test_write_callback() */
+ state->req = subreq;
+
+ return req;
+}
+
+static void test_tmon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool status;
+ int err;
+
+ status = tmon_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!status) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool test_write_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static int test_timeout_ok_callback(void *private_data)
+{
+ return 0;
+}
+
+static int test_read_callback(void *private_data, struct tmon_pkt *pkt)
+{
+ bool status;
+ char c;
+ uint16_t val;
+
+ status = tmon_parse_ping(pkt);
+ if (status) {
+ printf("PING\n");
+ fflush(stdout);
+ return 0;
+ }
+
+ status = tmon_parse_ascii(pkt, &c);
+ if (status) {
+ printf("ASCII %c\n", c);
+ fflush(stdout);
+ return 0;
+ }
+
+ status = tmon_parse_custom(pkt, &val);
+ if (status) {
+ printf("CUSTOM 0x%"PRIx16"\n", val);
+ fflush(stdout);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int test_close_ok_callback(void *private_data)
+{
+ return 0;
+}
+
+struct test_read_state {
+};
+
+static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int fd,
+ bool close_ok,
+ unsigned long timeout,
+ bool timeout_ok)
+{
+ struct tevent_req *req, *subreq;
+ struct test_read_state *state;
+ struct tmon_actions actions = {
+ .read_callback = test_read_callback,
+ };
+
+ req = tevent_req_create(mem_ctx, &state, struct test_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (timeout_ok) {
+ actions.timeout_callback = test_timeout_ok_callback;
+ }
+ if (close_ok) {
+ actions.close_callback = test_close_ok_callback;
+ }
+
+ subreq = tmon_send(state,
+ ev,
+ fd,
+ TMON_FD_READ,
+ timeout,
+ 0,
+ &actions,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, test_tmon_done, req);
+
+ return req;
+}
+
+static bool test_read_recv(struct tevent_req *req, int *perr)
+{
+ if (tevent_req_is_unix_error(req, perr)) {
+ return false;
+ }
+
+ return true;
+}
+
+static void test(const char *write_data,
+ bool close_ok,
+ unsigned long timeout,
+ bool timeout_ok)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int fd[2];
+ pid_t pid;
+ int wstatus;
+ bool status;
+ int err;
+ int ret;
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert(ev != NULL);
+
+ ret = pipe(fd);
+ assert(ret == 0);
+
+ pid = fork();
+ assert(pid != -1);
+
+ if (pid == 0) {
+ /* child */
+ close(fd[1]);
+
+ req = test_read_send(mem_ctx,
+ ev,
+ fd[0],
+ close_ok,
+ timeout,
+ timeout_ok);
+ assert(req != NULL);
+
+ status = tevent_req_poll(req, ev);
+ assert(status);
+
+ status = test_read_recv(req, &err);
+ if (status) {
+ err = 0;
+ printf("READER OK\n");
+ } else {
+ printf("READER ERR=%d\n", err);
+ }
+ fflush(stdout);
+
+ _exit(ret);
+ }
+
+ /* Parent */
+ close(fd[0]);
+
+ req = test_write_send(mem_ctx,
+ ev,
+ fd[1],
+ write_data);
+ assert(req != NULL);
+
+ status = tevent_req_poll(req, ev);
+ assert(status);
+
+ status = test_write_recv(req, &err);
+ if (status) {
+ err = 0;
+ printf("WRITER OK\n");
+ } else {
+ printf("WRITER ERR=%d\n", err);
+ }
+ fflush(stdout);
+
+ /* Close to mimick exit, so child status can be checked below */
+ close(fd[1]);
+
+ waitpid(pid, &wstatus, 0);
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "usage: %s <write_data> <close_ok> <timeout> <timeout_ok>\n\n"
+ " <write_data> is processed by test_write_callback(), "
+ "1 character per second:\n"
+ " 0: write EXIT\n"
+ " 1-9: write ERRNO 1-9\n"
+ " .: skip write\n"
+ " <space>: write CUSTOM containing <space>\n"
+ " other <ascii>: write ASCII containing <ascii>\n"
+ " other: write CUSTOM\n"
+ " See test_write_callback() for more details\n"
+ ,
+ prog);
+ exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+ bool close_ok, timeout_ok;
+ unsigned long timeout;
+
+ if (argc != 5) {
+ usage(argv[0]);
+ }
+
+ test_backtrace_setup();
+
+ close_ok = (strcmp(argv[2], "true") == 0);
+ timeout = strtoul(argv[3], NULL, 0);
+ if (timeout == 0) {
+ /*
+ * Default timeout that should not come into play but
+ * will cause tests to fail after a reasonable amount
+ * of time, if something unexpected happens.
+ */
+ timeout = 20;
+ }
+ timeout_ok = (strcmp(argv[4], "true") == 0);
+
+ test(argv[1], close_ok, timeout, timeout_ok);
+
+ return 0;
+}
diff --git a/ctdb/tests/src/transaction_loop.c b/ctdb/tests/src/transaction_loop.c
new file mode 100644
index 0000000..c6bf35d
--- /dev/null
+++ b/ctdb/tests/src/transaction_loop.c
@@ -0,0 +1,419 @@
+/*
+ simple ctdb benchmark for persistent databases
+
+ 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 "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+struct transaction_loop_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ int num_nodes;
+ int timelimit;
+ int interactive;
+ TDB_DATA key;
+ uint32_t pnn;
+ struct ctdb_transaction_handle *h;
+ uint32_t *old_counter, *counter;
+ struct tevent_req *subreq;
+ bool done;
+};
+
+static void transaction_loop_start(struct tevent_req *subreq);
+static void transaction_loop_started(struct tevent_req *subreq);
+static void transaction_loop_committed(struct tevent_req *subreq);
+static void transaction_loop_each_second(struct tevent_req *subreq);
+static bool transaction_loop_check_counters(struct tevent_req *req);
+static void transaction_loop_finish(struct tevent_req *subreq);
+
+static struct tevent_req *transaction_loop_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *ctdb_db,
+ int num_nodes, int timelimit, int interactive,
+ const char *keystr)
+{
+ struct tevent_req *req, *subreq;
+ struct transaction_loop_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct transaction_loop_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->ctdb_db = ctdb_db;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->interactive = interactive;
+ state->key.dptr = discard_const(keystr);
+ state->key.dsize = strlen(keystr);
+ state->pnn = ctdb_client_pnn(client);
+ state->old_counter = talloc_zero_array(state, uint32_t, num_nodes);
+ if (tevent_req_nomem(state->old_counter, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->counter = talloc_zero_array(state, uint32_t, num_nodes);
+ if (tevent_req_nomem(state->counter, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, transaction_loop_start, req);
+
+ return req;
+}
+
+static void transaction_loop_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ bool status;
+ int ret;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_transaction_start_send(state, state->ev, state->client,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0),
+ state->ctdb_db, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_started, req);
+ state->subreq = subreq;
+
+ if (ctdb_client_pnn(state->client) == 0) {
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_each_second,
+ req);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_finish, req);
+}
+
+static void transaction_loop_started(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ TDB_DATA data;
+ int ret;
+ uint32_t *counter;
+
+ state->h = ctdb_transaction_start_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (state->h == NULL) {
+ fprintf(stderr, "transaction start failed\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ctdb_transaction_fetch_record(state->h, state->key,
+ state, &data);
+ if (ret != 0) {
+ fprintf(stderr, "transaction fetch record failed\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (data.dsize < state->num_nodes * sizeof(uint32_t)) {
+ TALLOC_FREE(data.dptr);
+
+ data.dsize = state->num_nodes * sizeof(uint32_t);
+ data.dptr = (uint8_t *)talloc_zero_array(state, uint32_t,
+ state->num_nodes);
+ if (tevent_req_nomem(data.dptr, req)) {
+ return;
+ }
+ }
+
+ counter = (uint32_t *)data.dptr;
+ counter[state->pnn] += 1;
+ memcpy(state->counter, counter, state->num_nodes * sizeof(uint32_t));
+
+ ret = ctdb_transaction_store_record(state->h, state->key, data);
+ if (ret != 0) {
+ fprintf(stderr, "transaction store failed\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_transaction_commit_send(state, state->ev,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0),
+ state->h);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_committed, req);
+ state->subreq = subreq;
+}
+
+static void transaction_loop_committed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ int ret;
+ bool status;
+
+ status = ctdb_transaction_commit_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (! status) {
+ fprintf(stderr, "transaction commit failed - %s\n",
+ strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->pnn == 0) {
+ if (! transaction_loop_check_counters(req)) {
+ return;
+ }
+ }
+
+ if (state->done) {
+ int i;
+
+ printf("Transaction[%u]: ", ctdb_client_pnn(state->client));
+ for (i=0; i<state->num_nodes; i++) {
+ printf("%6u ", state->counter[i]);
+ }
+ printf("\n");
+
+ tevent_req_done(req);
+
+ return;
+ }
+
+ subreq = ctdb_transaction_start_send(state, state->ev, state->client,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0),
+ state->ctdb_db, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_started, req);
+}
+
+static void transaction_loop_each_second(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ bool status;
+ int i;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ fprintf(stderr, "tevent wakeup failed\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ if (state->interactive == 1) {
+ printf("Transaction[%u]: ", ctdb_client_pnn(state->client));
+ for (i=0; i<state->num_nodes; i++) {
+ printf("%6u ", state->counter[i]);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, transaction_loop_each_second, req);
+}
+
+static bool transaction_loop_check_counters(struct tevent_req *req)
+{
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ int i;
+ bool monotonous = true;
+
+ for (i=0; i<state->num_nodes; i++) {
+ if (state->counter[i] < state->old_counter[i]) {
+ fprintf(stderr,
+ "Counter reduced for node %d: %u -> %u\n",
+ i, state->old_counter[i], state->counter[i]);
+ monotonous = false;
+ break;
+ }
+ }
+
+ if (monotonous) {
+ memcpy(state->old_counter, state->counter,
+ state->num_nodes * sizeof(uint32_t));
+ }
+
+ return monotonous;
+}
+
+static void transaction_loop_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct transaction_loop_state *state = tevent_req_data(
+ req, struct transaction_loop_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ state->done = true;
+
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+}
+
+static bool transaction_loop_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ uint8_t db_flags;
+ int ret;
+ bool status;
+
+ setup_logging("transaction_loop", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ if (strcmp(opts->dbtype, "persistent") == 0) {
+ db_flags = CTDB_DB_FLAGS_PERSISTENT;
+ } else if (strcmp(opts->dbtype, "replicated") == 0) {
+ db_flags = CTDB_DB_FLAGS_REPLICATED;
+ } else {
+ fprintf(stderr, "Database must be persistent or replicated\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname,
+ db_flags, &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach to persistent DB %s\n",
+ opts->dbname);
+ exit(1);
+ }
+
+ req = transaction_loop_send(mem_ctx, ev, client, ctdb_db,
+ opts->num_nodes, opts->timelimit,
+ opts->interactive, opts->keystr);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = transaction_loop_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "transaction loop test failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/tunable_test.c b/ctdb/tests/src/tunable_test.c
new file mode 100644
index 0000000..ea94aec
--- /dev/null
+++ b/ctdb/tests/src/tunable_test.c
@@ -0,0 +1,71 @@
+/*
+ Test tunable handling
+
+ Copyright (C) Martin Schwenke, DataDirect Networks 2022
+
+ 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/filesys.h"
+
+#include <talloc.h>
+#include <assert.h>
+
+#include "common/tunable.c"
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ctdb_tunable_list tun_list;
+ struct ctdb_var_list *list;
+ bool status;
+ int ret = 0;
+ int i;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
+ return 1;
+ }
+
+ mem_ctx = talloc_new(NULL);
+ assert(mem_ctx != NULL);
+
+ status = ctdb_tunable_load_file(mem_ctx, &tun_list, argv[1]);
+ if (!status) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ list = ctdb_tunable_names(mem_ctx);
+ assert(list != NULL);
+
+ for (i = 0; i < list->count; i++) {
+ const char *var = list->var[i];
+ uint32_t val;
+
+ status = ctdb_tunable_get_value(&tun_list, var, &val);
+ if (!status) {
+ ret = EIO;
+ goto done;
+ }
+
+ printf("%s=%"PRIu32"\n", var, val);
+ fflush(stdout);
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
diff --git a/ctdb/tests/src/tunnel_cmd.c b/ctdb/tests/src/tunnel_cmd.c
new file mode 100644
index 0000000..73a2297
--- /dev/null
+++ b/ctdb/tests/src/tunnel_cmd.c
@@ -0,0 +1,199 @@
+/*
+ CTDB tunnel test
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 <talloc.h>
+#include <tevent.h>
+
+#include "lib/util/tevent_unix.h"
+
+#include "protocol/protocol_private.h"
+#include "client/client.h"
+
+#define TUNNEL_ID (CTDB_TUNNEL_TEST | 0xf0f0f0f0)
+
+struct listen_state {
+ TALLOC_CTX *mem_ctx;
+ bool done;
+};
+
+static void listen_callback(struct ctdb_tunnel_context *tctx,
+ uint32_t srcnode, uint32_t reqid,
+ uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct listen_state *state = (struct listen_state *)private_data;
+ const char *msg;
+ size_t np;
+ int ret;
+
+ ret = ctdb_stringn_pull(buf, buflen, state->mem_ctx, &msg, &np);
+ if (ret != 0) {
+ fprintf(stderr, "Invalid tunnel message, ret=%d\n", ret);
+ return;
+ }
+
+ fprintf(stderr, "%u: %s\n", srcnode, msg);
+
+ if (strcmp(msg, "quit") == 0) {
+ state->done = true;
+ }
+
+ talloc_free(discard_const(msg));
+}
+
+static int cmd_listen(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdb_client_context *client)
+{
+ struct ctdb_tunnel_context *tunnel;
+ struct listen_state state;
+ int ret;
+
+ state.mem_ctx = mem_ctx;
+ state.done = false;
+
+ ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID,
+ listen_callback, &state, &tunnel);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ctdb_client_wait(ev, &state.done);
+
+ ret = ctdb_tunnel_destroy(ev, tunnel);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static void send_callback(struct ctdb_tunnel_context *tctx,
+ uint32_t srcnode, uint32_t reqid,
+ uint8_t *buf, size_t buflen, void *private_data)
+{
+ fprintf(stderr, "send received a message - %u: %zu\n", srcnode, buflen);
+}
+
+static int cmd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ uint32_t destnode, const char *msg)
+{
+ struct ctdb_tunnel_context *tunnel;
+ uint8_t *buf;
+ size_t buflen, np;
+ int ret;
+
+ ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID,
+ send_callback, NULL, &tunnel);
+ if (ret != 0) {
+ return ret;
+ }
+
+ buflen = ctdb_stringn_len(&msg);
+ buf = talloc_size(mem_ctx, buflen);
+ if (buf == NULL) {
+ return ENOMEM;
+ }
+ ctdb_stringn_push(&msg, buf, &np);
+
+ ret = ctdb_tunnel_request(mem_ctx, ev, tunnel, destnode,
+ tevent_timeval_zero(), buf, buflen, false);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = ctdb_tunnel_destroy(ev, tunnel);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usage(const char *cmd)
+{
+ fprintf(stderr, "usage: %s <ctdb-socket> listen\n", cmd);
+ fprintf(stderr, "usage: %s <ctdb-socket> send <pnn> <msg>\n", cmd);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ const char *socket = NULL, *msg = NULL;
+ uint32_t pnn = CTDB_UNKNOWN_PNN;
+ int ret;
+ bool do_listen = false;
+ bool do_send = false;
+
+ if (argc != 3 && argc != 5) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ socket = argv[1];
+
+ if (strcmp(argv[2], "listen") == 0) {
+ do_listen = true;
+ } else if (strcmp(argv[2], "send") == 0) {
+ if (argc != 5) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ pnn = atol(argv[3]);
+ msg = argv[4];
+ do_send = true;
+ } else {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ talloc_free(mem_ctx);
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, socket, &client);
+ if (ret != 0) {
+ talloc_free(mem_ctx);
+ exit(1);
+ }
+
+ if (do_listen) {
+ ret = cmd_listen(mem_ctx, ev, client);
+ }
+ if (do_send) {
+ ret = cmd_send(mem_ctx, ev, client, pnn, msg);
+ }
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
diff --git a/ctdb/tests/src/tunnel_test.c b/ctdb/tests/src/tunnel_test.c
new file mode 100644
index 0000000..a6d44ba
--- /dev/null
+++ b/ctdb/tests/src/tunnel_test.c
@@ -0,0 +1,480 @@
+/*
+ CTDB tunnel test
+
+ Copyright (C) Amitay Isaacs 2017
+
+ 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 "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "protocol/protocol_private.h"
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+struct test_data {
+ uint32_t pnn;
+ uint32_t count;
+};
+
+static size_t test_data_len(struct test_data *in)
+{
+ return ctdb_uint32_len(&in->pnn) + ctdb_uint32_len(&in->count);
+}
+
+static void test_data_push(struct test_data *in, uint8_t *buf, size_t *npush)
+{
+ size_t offset = 0, np;
+
+ ctdb_uint32_push(&in->pnn, buf+offset, &np);
+ offset += np;
+
+ ctdb_uint32_push(&in->count, buf+offset, &np);
+ offset += np;
+
+ *npush = offset;
+}
+
+static int test_data_pull(uint8_t *buf, size_t buflen, struct test_data *out,
+ size_t *npull)
+{
+ size_t offset = 0, np;
+ int ret;
+
+ ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->pnn, &np);
+ if (ret != 0) {
+ return ret;
+ }
+ offset += np;
+
+ ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->count, &np);
+ if (ret != 0) {
+ return ret;
+ }
+ offset += np;
+
+ *npull = offset;
+ return 0;
+}
+
+/*
+ * Set up 2 tunnels from each node - one to the next node and one to the
+ * previous node. The tunnel to the next node is used for sending data and
+ * tunnel to the previous node is used for receiving data.
+ */
+
+struct tunnel_test_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ int num_nodes;
+ int timelimit;
+
+ uint32_t pnn;
+ uint32_t next_node;
+ uint32_t prev_node;
+ bool done;
+ struct ctdb_tunnel_context *send_tunnel;
+ struct ctdb_tunnel_context *recv_tunnel;
+ uint32_t count;
+ uint8_t *buf;
+};
+
+static void tunnel_test_send_tunnel_done(struct tevent_req *subreq);
+static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq);
+static void tunnel_test_start(struct tevent_req *subreq);
+static void tunnel_test_msg_send(struct tevent_req *req,
+ struct test_data *tdata);
+static void tunnel_test_msg_send_done(struct tevent_req *subreq);
+static void tunnel_test_handler(struct ctdb_tunnel_context *tctx,
+ uint32_t srcnode, uint32_t reqid,
+ uint8_t *buf, size_t buflen,
+ void *private_data);
+static void tunnel_test_done(struct tevent_req *subreq);
+static void tunnel_test_finish(struct tevent_req *subreq);
+static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq);
+static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq);
+
+static struct tevent_req *tunnel_test_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ int num_nodes, int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct tunnel_test_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct tunnel_test_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->num_nodes = num_nodes;
+ state->timelimit = timelimit;
+ state->pnn = ctdb_client_pnn(client);
+ state->prev_node = (state->pnn + num_nodes - 1) % num_nodes;
+ state->next_node = (state->pnn + 1) % num_nodes;
+ state->done = false;
+
+ subreq = ctdb_tunnel_setup_send(state, state->ev, state->client,
+ CTDB_TUNNEL_TEST | state->pnn,
+ tunnel_test_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tunnel_test_send_tunnel_done, req);
+
+ return req;
+}
+
+static void tunnel_test_send_tunnel_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ int ret;
+ bool status;
+
+ status = ctdb_tunnel_setup_recv(subreq, &ret, &state->send_tunnel);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = ctdb_tunnel_setup_send(state, state->ev, state->client,
+ CTDB_TUNNEL_TEST | state->prev_node,
+ tunnel_test_handler, req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_done, req);
+}
+
+static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ int ret;
+ bool status;
+
+ status = ctdb_tunnel_setup_recv(subreq, &ret, &state->recv_tunnel);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = cluster_wait_send(state, state->ev, state->client,
+ state->num_nodes);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_start, req);
+}
+
+static void tunnel_test_start(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ struct test_data tdata;
+ int ret;
+ bool status;
+
+ status = cluster_wait_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(state->timelimit, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_done, req);
+
+ tdata.pnn = state->pnn;
+ tdata.count = state->count;
+ tunnel_test_msg_send(req, &tdata);
+}
+
+static void tunnel_test_msg_send(struct tevent_req *req,
+ struct test_data *tdata)
+{
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ struct tevent_req *subreq;
+ size_t buflen, np;
+
+ buflen = test_data_len(tdata);
+ state->buf = talloc_size(state, buflen);
+ if (tevent_req_nomem(state->buf, req)) {
+ return;
+ }
+ test_data_push(tdata, state->buf, &np);
+
+ subreq = ctdb_tunnel_request_send(state, state->ev,
+ state->send_tunnel,
+ state->next_node,
+ tevent_timeval_zero(),
+ state->buf, buflen, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_msg_send_done, req);
+}
+
+static void tunnel_test_msg_send_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ int ret;
+ bool status;
+
+ status = ctdb_tunnel_request_recv(subreq, &ret, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ TALLOC_FREE(state->buf);
+}
+
+static void tunnel_test_handler(struct ctdb_tunnel_context *tctx,
+ uint32_t srcnode, uint32_t reqid,
+ uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ struct test_data tdata;
+ size_t np;
+ int ret;
+
+ if (state->done) {
+ return;
+ }
+
+ if (tctx == state->send_tunnel) {
+ fprintf(stderr, "pnn:%u Received data on send tunnel\n",
+ state->pnn);
+ tevent_req_error(req, EPROTO);
+ return;
+ }
+
+ ret = test_data_pull(buf, buflen, &tdata, &np);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (tdata.pnn == state->pnn) {
+ if (tdata.count != state->count) {
+ tevent_req_error(req, EPROTO);
+ return;
+ }
+
+ state->count = tdata.count + 1;
+ tdata.count = state->count;
+ }
+
+ tunnel_test_msg_send(req, &tdata);
+}
+
+static void tunnel_test_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ printf("pnn[%u] %.1lf msgs/sec\n",
+ state->pnn, (double)state->count / state->timelimit);
+
+ state->done = true;
+
+ /* wait few more seconds */
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(3, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_finish, req);
+}
+
+static void tunnel_test_finish(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ bool status;
+
+ status = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ subreq = ctdb_tunnel_destroy_send(state, state->ev,
+ state->send_tunnel);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_send_tunnel_closed, req);
+}
+
+static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ int ret;
+ bool status;
+
+ status = ctdb_tunnel_destroy_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ state->send_tunnel = NULL;
+
+ subreq = ctdb_tunnel_destroy_send(state, state->ev,
+ state->recv_tunnel);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_closed, req);
+}
+
+static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tunnel_test_state *state = tevent_req_data(
+ req, struct tunnel_test_state);
+ int ret;
+ bool status;
+
+ status = ctdb_tunnel_destroy_recv(subreq, &ret);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ state->recv_tunnel = NULL;
+
+ tevent_req_done(req);
+}
+
+static bool tunnel_test_recv(struct tevent_req *req, int *perr)
+{
+ int ret;
+
+ if (tevent_req_is_unix_error(req, &ret)) {
+ if (perr != NULL) {
+ *perr = ret;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("tunnel_test", DEBUG_STDERR);
+
+ status = process_options_basic(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client, ret=%d\n", ret);
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ req = tunnel_test_send(mem_ctx, ev, client, opts->num_nodes,
+ opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = tunnel_test_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "tunnel test failed, ret=%d\n", ret);
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/update_record.c b/ctdb/tests/src/update_record.c
new file mode 100644
index 0000000..11b6050
--- /dev/null
+++ b/ctdb/tests/src/update_record.c
@@ -0,0 +1,236 @@
+/*
+ Update a record and increase it's RSN
+
+ 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 "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "protocol/protocol_api.h"
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+struct update_record_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *db;
+ int timelimit;
+ TDB_DATA key;
+};
+
+static void update_record_fetch_done(struct tevent_req *subreq);
+static void update_record_update_done(struct tevent_req *subreq);
+
+static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *db,
+ const char *keystr,
+ int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct update_record_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct update_record_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->db = db;
+ state->timelimit = timelimit;
+ state->key.dptr = (uint8_t *)discard_const(keystr);
+ state->key.dsize = strlen(keystr);
+
+ subreq = ctdb_fetch_lock_send(state, state->ev, state->client,
+ state->db, state->key, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, update_record_fetch_done, req);
+
+ return req;
+}
+
+static void update_record_fetch_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct update_record_state *state = tevent_req_data(
+ req, struct update_record_state);
+ struct ctdb_record_handle *h;
+ struct ctdb_ltdb_header header;
+ struct ctdb_rec_buffer *recbuf;
+ struct ctdb_req_control request;
+ TDB_DATA data;
+ int ret;
+
+ h = ctdb_fetch_lock_recv(subreq, &header, NULL, NULL, &ret);
+ TALLOC_FREE(subreq);
+ if (h == NULL) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(h);
+
+ header.rsn += 10;
+
+ recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db));
+ if (tevent_req_nomem(recbuf, req)) {
+ return;
+ }
+
+ data.dptr = (uint8_t *)talloc_asprintf(recbuf, "%"PRIu64, header.rsn);
+ if (tevent_req_nomem(data.dptr, req)) {
+ return;
+ }
+ data.dsize = strlen((char *)data.dptr);
+
+ ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, state->key, data);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ctdb_req_control_update_record(&request, recbuf);
+ subreq = ctdb_client_control_send(state, state->ev, state->client,
+ CTDB_CURRENT_NODE,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0),
+ &request);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, update_record_update_done, req);
+
+ talloc_free(recbuf);
+}
+
+static void update_record_update_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct update_record_state *state = tevent_req_data(
+ req, struct update_record_state);
+ struct ctdb_reply_control *reply;
+ int ret;
+ bool status;
+
+ status = ctdb_client_control_recv(subreq, &ret, state, &reply);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ctdb_reply_control_update_record(reply);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(reply);
+
+ tevent_req_done(req);
+}
+
+static bool update_record_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("update_record", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client (%s), %s\n",
+ opts->socket, strerror(ret));
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname,
+ 0, &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach DB %s\n", opts->dbname);
+ exit(1);
+ }
+
+ req = update_record_send(mem_ctx, ev, client, ctdb_db,
+ opts->keystr, opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = update_record_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "update record failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}
diff --git a/ctdb/tests/src/update_record_persistent.c b/ctdb/tests/src/update_record_persistent.c
new file mode 100644
index 0000000..2d6d21e
--- /dev/null
+++ b/ctdb/tests/src/update_record_persistent.c
@@ -0,0 +1,218 @@
+/*
+ Update a record in persistent database
+
+ 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 "lib/util/debug.h"
+#include "lib/util/tevent_unix.h"
+
+#include "protocol/protocol_api.h"
+#include "client/client.h"
+#include "tests/src/test_options.h"
+#include "tests/src/cluster_wait.h"
+
+struct update_record_state {
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *db;
+ int timelimit;
+ TDB_DATA key, data;
+};
+
+static void update_record_update_done(struct tevent_req *subreq);
+
+static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdb_client_context *client,
+ struct ctdb_db_context *db,
+ const char *keystr,
+ const char *valuestr,
+ int timelimit)
+{
+ struct tevent_req *req, *subreq;
+ struct update_record_state *state;
+ struct ctdb_ltdb_header header;
+ struct ctdb_rec_buffer *recbuf;
+ struct ctdb_req_control request;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct update_record_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->client = client;
+ state->db = db;
+ state->timelimit = timelimit;
+ state->key.dptr = (uint8_t *)discard_const(keystr);
+ state->key.dsize = strlen(keystr);
+ state->data.dptr = (uint8_t *)discard_const(valuestr);
+ state->data.dsize = strlen(valuestr);
+
+ ret = ctdb_ltdb_fetch(state->db, state->key, &header, NULL, NULL);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ header.rsn += 1;
+
+ recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db));
+ if (tevent_req_nomem(recbuf, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ret = ctdb_rec_buffer_add(state, recbuf, 0, &header,
+ state->key, state->data);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+ }
+
+ ctdb_req_control_update_record(&request, recbuf);
+ subreq = ctdb_client_control_send(state, state->ev, state->client,
+ CTDB_CURRENT_NODE,
+ tevent_timeval_current_ofs(
+ state->timelimit, 0),
+ &request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, update_record_update_done, req);
+
+ talloc_free(recbuf);
+ return req;
+}
+
+static void update_record_update_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct update_record_state *state = tevent_req_data(
+ req, struct update_record_state);
+ struct ctdb_reply_control *reply;
+ int ret;
+ bool status;
+
+ status = ctdb_client_control_recv(subreq, &ret, state, &reply);
+ TALLOC_FREE(subreq);
+ if (! status) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = ctdb_reply_control_update_record(reply);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ talloc_free(reply);
+
+ tevent_req_done(req);
+}
+
+static bool update_record_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;
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct test_options *opts;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct ctdb_client_context *client;
+ struct ctdb_db_context *ctdb_db;
+ struct tevent_req *req;
+ int ret;
+ bool status;
+
+ setup_logging("update_record_persistene", DEBUG_STDERR);
+
+ status = process_options_database(argc, argv, &opts);
+ if (! status) {
+ exit(1);
+ }
+
+ if (opts->valuestr == NULL) {
+ fprintf(stderr, "Error: please specify key value (-v)\n");
+ exit(1);
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ev = tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to initialize client (%s), %s\n",
+ opts->socket, strerror(ret));
+ exit(1);
+ }
+
+ if (! ctdb_recovery_wait(ev, client)) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname,
+ CTDB_DB_FLAGS_PERSISTENT, &ctdb_db);
+ if (ret != 0) {
+ fprintf(stderr, "Failed to attach DB %s\n", opts->dbname);
+ exit(1);
+ }
+
+ req = update_record_send(mem_ctx, ev, client, ctdb_db,
+ opts->keystr, opts->valuestr,
+ opts->timelimit);
+ if (req == NULL) {
+ fprintf(stderr, "Memory allocation error\n");
+ exit(1);
+ }
+
+ tevent_req_poll(req, ev);
+
+ status = update_record_recv(req, &ret);
+ if (! status) {
+ fprintf(stderr, "update record failed\n");
+ exit(1);
+ }
+
+ talloc_free(mem_ctx);
+ return 0;
+}