libctdb: test infrastructure
authorRusty Russell <rusty@rustcorp.com.au>
Fri, 16 Jul 2010 04:42:40 +0000 (14:12 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Fri, 16 Jul 2010 04:42:40 +0000 (14:12 +0930)
This introduces 'ctdb-test', a program for testing libctdb.  It takes
commands on standard input (with reduced functionality) or an input file.

It still needs some cleaning up, but you can uncover a bug in libctdb
today simply by running a simple attachdb test:

$ ctdb-test tests/attachdb1.txt

It will print out a crash, and the path of successful and failed
operations which lead to it:

...
Child signalled 11 on failure path: [malloc]:1:S[socket]:1:S[connect]:1:S[malloc]:1:S[malloc]:1:S[malloc]:1:S[malloc]:4:S[malloc]:4:F

Feed that failure path into ctdb-test using --failpath (under a debugger):

gdb --args ctdb-test tests/attachdb1.txt --failpath=[malloc]:1:S[socket]:1:S[connect]:1:S[malloc]:1:S[malloc]:1:S[malloc]:1:S[malloc]:4:S[malloc]:4:F

And you hit the exact error.

It is based on the fork-to-fail model of nfsim.  The relevant parts are
from page 154 of the proceedings of 2005 Ottawa Linux Symposium Volume II:
http://www.linuxsymposium.org/2005/linuxsymposium_procv2.pdf

Or our presentation of same (from slide 21):
http://ozlabs.org/~jk/projects/nfsim/nfsim.sxi

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
22 files changed:
libctdb/test/Makefile [new file with mode: 0644]
libctdb/test/attachdb.c [new file with mode: 0644]
libctdb/test/ctdb-test.c [new file with mode: 0644]
libctdb/test/ctdb-test.h [new file with mode: 0644]
libctdb/test/expect.c [new file with mode: 0644]
libctdb/test/expect.h [new file with mode: 0644]
libctdb/test/failtest.c [new file with mode: 0644]
libctdb/test/failtest.h [new file with mode: 0644]
libctdb/test/log.c [new file with mode: 0644]
libctdb/test/log.h [new file with mode: 0644]
libctdb/test/tests/attachdb1.txt [new file with mode: 0644]
libctdb/test/tests/connect1.txt [new file with mode: 0644]
libctdb/test/tests/connect2.txt [new file with mode: 0644]
libctdb/test/tools/create-links [new file with mode: 0755]
libctdb/test/tools/extract-help [new file with mode: 0755]
libctdb/test/tools/gen-help [new file with mode: 0755]
libctdb/test/tools/gen-usage [new file with mode: 0755]
libctdb/test/tools/text.xsl [new file with mode: 0644]
libctdb/test/tools/usage.xsl [new file with mode: 0644]
libctdb/test/tui.c [new file with mode: 0644]
libctdb/test/tui.h [new file with mode: 0644]
libctdb/test/utils.h [new file with mode: 0644]

diff --git a/libctdb/test/Makefile b/libctdb/test/Makefile
new file mode 100644 (file)
index 0000000..e5aa09b
--- /dev/null
@@ -0,0 +1,23 @@
+CFLAGS=-Wall -g -I../../include/ -I../../lib/talloc/ -I../../lib/tdb/include/ -I../../lib/util/
+LDLIBS=-lreadline
+
+USAGE_SOURCES := $(shell grep -l 'XML Argument' *.c)
+HELP_SOURCES := $(shell grep -l 'XML Help' *.c)
+
+ctdb-test: $(patsubst %.c,%.o,$(wildcard *.c)) generated-usage.o ../../talloc.o ../../common/check.o ../../common/error.o ../../common/freelist.o ../../common/io.o ../../common/lock.o ../../common/open.o ../../common/tdb.o ../../common/transaction.o ../../common/traverse.o
+
+$(patsubst %.c,%.o,$(wildcard *.c)): .help-files
+
+.PHONY: links
+links:
+       cd tools && ./create-links
+
+generated-usage.o: generated-usage.c links .help-files
+generated-usage.c: $(USAGE_SOURCES) tools/gen-usage links
+       tools/gen-usage $(USAGE_SOURCES) >$@
+
+.help-files: $(HELP_SOURCES) links
+       set -e; for f in $(HELP_SOURCES); do tools/gen-help $$f; done; touch .help-files
+
+clean:
+       rm -f ctdb-test .help-files generated-* *.o
diff --git a/libctdb/test/attachdb.c b/libctdb/test/attachdb.c
new file mode 100644 (file)
index 0000000..bb4ce2d
--- /dev/null
@@ -0,0 +1,166 @@
+#include "utils.h"
+#include "log.h"
+#include "tui.h"
+#include "ctdb-test.h"
+#include <ctdb.h>
+#include <tdb.h>
+#include <talloc.h>
+#include <dlinklist.h>
+#include <errno.h>
+
+static unsigned int db_num;
+static struct db *dbs;
+
+struct db {
+       struct db *next, *prev;
+       struct ctdb_db *db;
+       const char *name;
+       unsigned int num;
+       bool persistent;
+       uint32_t tdb_flags;
+};
+
+static void attachdb_help(int agc, char **argv)
+{
+#include "generated-attachdb-help:attachdb"
+/*** XML Help:
+    <section id="c:attachdb">
+     <title><command>attachdb</command></title>
+     <para>Attach to a ctdb database</para>
+     <cmdsynopsis>
+      <command>attachdb</command>
+      <arg choice="req"><replaceable>name</replaceable></arg>
+      <arg choice="req"><replaceable>persistent</replaceable></arg>
+      <arg choice="opt"><replaceable>tdb-flags</replaceable></arg>
+     </cmdsynopsis>
+     <para>Attach to the database of the given <replaceable>name</replaceable>.
+       <replaceable>persistent</replaceable> is 'true' or 'false', an
+
+       <replaceable>tdb-flags</replaceable> an optional one or more
+       comma-separated values:</para>
+     <variablelist>
+      <varlistentry>
+       <term>SEQNUM</term>
+       <listitem>
+        <para>Use sequence numbers on the tdb</para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+
+     <para>It uses a consecutive number for each attached db to
+     identify it for other ctdb-test commands, starting with 1.</para>
+
+     <para>Without any options, the <command>attachdb</command>
+      command lists all databases attached.</para>
+     </section>
+*/
+}
+
+static void detachdb_help(int agc, char **argv)
+{
+#include "generated-attachdb-help:detachdb"
+/*** XML Help:
+    <section id="c:detachdb">
+     <title><command>detachdb</command></title>
+     <para>Detach from a ctdb database</para>
+     <cmdsynopsis>
+      <command>detachdb</command>
+      <arg choice="req"><replaceable>number</replaceable></arg>
+     </cmdsynopsis>
+     <para>Detach from the database returned by <command>attachdb</command>.
+     </para>
+     </section>
+*/
+}
+static int db_destructor(struct db *db)
+{
+       ctdb_detachdb(get_ctdb(), db->db);
+       DLIST_REMOVE(dbs, db);
+       return 0;
+}
+
+static bool detachdb(int argc, char **argv)
+{
+       struct db *db;
+
+       if (argc != 2) {
+               log_line(LOG_ALWAYS, "Need database number");
+               return false;
+       }
+
+       for (db = dbs; db; db = db->next) {
+               if (db->num == atoi(argv[1]))
+                       break;
+       }
+       if (!db) {
+               log_line(LOG_ALWAYS, "Unknown db number %s", argv[1]);
+               return false;
+       }
+       talloc_free(db);
+       return true;
+}
+
+static bool attachdb(int argc, char **argv)
+{
+       struct db *db;
+
+       if (!get_ctdb()) {
+               log_line(LOG_ALWAYS, "No ctdb connection");
+               return false;
+       }
+
+       if (argc == 1) {
+               log_line(LOG_UI, "Databases currently attached:");
+               for (db = dbs; db; db = db->next) {
+                       log_line(LOG_ALWAYS, "  %i: %s: %s %u",
+                                db->num, db->name,
+                                db->persistent
+                                ? "persistent" : "not persistent",
+                                db->tdb_flags);
+               }
+               return true;
+       }
+       if (argc != 3 && argc != 4) {
+               log_line(LOG_ALWAYS, "Need 2 or 3 args");
+               return false;
+       }
+       db = talloc(working, struct db);
+       db->name = talloc_strdup(db, argv[1]);
+       if (strcasecmp(argv[2], "true") == 0)
+               db->persistent = true;
+       else if (strcasecmp(argv[2], "false") == 0)
+               db->persistent = false;
+       else {
+               log_line(LOG_ALWAYS, "persistent should be true or false");
+               talloc_free(db);
+               return false;
+       }
+       db->tdb_flags = 0;
+       if (argc == 4) {
+               if (strcasecmp(argv[3], "seqnum") == 0)
+                       db->tdb_flags |= TDB_SEQNUM;
+               else {
+                       log_line(LOG_ALWAYS, "invalid tdb-flags");
+                       talloc_free(db);
+                       return false;
+               }
+       }
+       db->db = ctdb_attachdb(get_ctdb(), db->name, db->persistent,
+                              db->tdb_flags);
+       if (!db->db) {
+               log_line(LOG_UI, "ctdb_attachdb: %s", strerror(errno));
+               return false;
+       }
+       db->num = ++db_num;
+       DLIST_ADD(dbs, db);
+       talloc_set_destructor(db, db_destructor);
+       log_line(LOG_UI, "attached: %u", db->num);
+       return true;
+}
+
+static void attachdb_init(void)
+{
+       tui_register_command("attachdb", attachdb, attachdb_help);
+       tui_register_command("detachdb", detachdb, detachdb_help);
+}
+init_call(attachdb_init);
diff --git a/libctdb/test/ctdb-test.c b/libctdb/test/ctdb-test.c
new file mode 100644 (file)
index 0000000..b4c12ff
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+   test driver for libctdb
+
+   Copyright (C) Rusty Russell 2010
+
+   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 <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <talloc.h>
+#include <tdb.h>
+
+/* We replace the following functions, for finer control. */
+#define poll(fds, nfds, timeout) ctdb_test_poll((fds), (nfds), (timeout), __location__)
+#define malloc(size) ctdb_test_malloc((size), __location__)
+#define free(ptr) ctdb_test_free((ptr), __location__)
+#define realloc(ptr, size) ctdb_test_realloc((ptr), (size), __location__)
+#define read(fd, buf, count) ctdb_test_read((fd), (buf), (count), __location__)
+#define write(fd, buf, count) ctdb_test_write((fd), (buf), (count), __location__)
+#define socket(domain, type, protocol) ctdb_test_socket((domain), (type), (protocol), __location__)
+#define connect(sockfd, addr, addrlen) ctdb_test_connect((sockfd), (addr), (addrlen), __location__)
+
+#define tdb_open_ex(name, hash_size, tdb_flags, open_flags, mode, log_ctx, hash_fn) ctdb_test_tdb_open_ex((name), (hash_size), (tdb_flags), (open_flags), (mode), (log_ctx), (hash_fn), __location__)
+#define tdb_fetch(tdb, key) ctdb_test_tdb_fetch((tdb), (key))
+
+/* Implement these if they're ever used. */
+#define calloc ctdb_test_calloc
+#define select ctdb_test_select
+#define epoll_wait ctdb_test_epoll_wait
+#define epoll_ctl ctdb_test_epoll_ctl
+#define tdb_open ctdb_test_tdb_open
+
+static int ctdb_test_poll(struct pollfd *fds, nfds_t nfds, int timeout, const char *location);
+static void *ctdb_test_malloc(size_t size, const char *location);
+static void ctdb_test_free(void *ptr, const char *location);
+static void *ctdb_test_realloc(void *ptr, size_t size, const char *location);
+static ssize_t ctdb_test_read(int fd, void *buf, size_t count, const char *location);
+static ssize_t ctdb_test_write(int fd, const void *buf, size_t count, const char *location);
+static int ctdb_test_socket(int domain, int type, int protocol, const char *location);
+static int ctdb_test_connect(int sockfd, const struct sockaddr *addr,
+                            socklen_t addrlen, const char *location);
+static struct tdb_context *ctdb_test_tdb_open_ex(const char *name,
+                                                int hash_size, int tdb_flags,
+                                                int open_flags, mode_t mode,
+                                                const struct tdb_logging_context *log_ctx,
+                                                tdb_hash_func hash_fn, const char *location);
+static TDB_DATA ctdb_test_tdb_fetch(struct tdb_context *tdb, TDB_DATA key);
+
+#include "../sync.c"
+#include "../control.c"
+#include "../ctdb.c"
+#include "../io_elem.c"
+#include "../local_tdb.c"
+#include "../logging.c"
+#include "../messages.c"
+
+#undef poll
+#undef malloc
+#undef realloc
+#undef read
+#undef write
+#undef socket
+#undef connect
+#undef tdb_open_ex
+#undef calloc
+#undef select
+#undef epoll_wait
+#undef epoll_ctl
+#undef tdb_open
+#undef tdb_fetch
+
+#include "ctdb-test.h"
+#include "utils.h"
+#include "tui.h"
+#include "log.h"
+#include "failtest.h"
+#include "expect.h"
+#include <err.h>
+
+/* Talloc contexts */
+void *allocations;
+void *working;
+
+static void run_inits(void)
+{
+       /* Linker magic creates these to delineate section. */
+       extern initcall_t __start_init_call[], __stop_init_call[];
+       initcall_t *p;
+
+       for (p = __start_init_call; p < __stop_init_call; p++)
+               (*p)();
+}
+
+static void print_license(void)
+{
+       printf("ctdb-test, Copyright (C) 2010 Jeremy Kerr, Rusty Russell\n"
+              "ctdb-test comes with ABSOLUTELY NO WARRANTY; see COPYING.\n"
+              "This is free software, and you are welcome to redistribute\n"
+              "it under certain conditions; see COPYING for details.\n");
+}
+
+/*** XML Argument:
+    <section id="a:echo">
+     <title><option>--echo</option>, <option>-x</option></title>
+     <subtitle>Echo commands as they are executed</subtitle>
+     <para>ctdb-test will echo each command before it is executed. Useful when
+      commands are read from a file</para>
+    </section>
+*/
+static void cmdline_echo(struct option *opt)
+{
+       tui_echo_commands = 1;
+}
+cmdline_opt("echo", 0, 'x', cmdline_echo);
+
+/*** XML Argument:
+    <section id="a:quiet">
+     <title><option>--quiet</option>, <option>-q</option></title>
+     <subtitle>Run quietly</subtitle>
+     <para>Causes ctdb-test to reduce its output to the minimum possible - no prompt
+      is displayed, and most warning messages are suppressed
+     </para>
+    </section>
+*/
+static void cmdline_quiet(struct option *opt)
+{
+       tui_quiet = 1;
+}
+cmdline_opt("quiet", 0, 'q', cmdline_quiet);
+
+/*** XML Argument:
+    <section id="a:exit">
+     <title><option>--exit</option>, <option>-e</option></title>
+     <subtitle>Exit on error</subtitle>
+     <para>If <option>--exit</option> is specified, ctdb-test will exit (with a
+     non-zero error code) on the first script error it encounters (eg an
+     expect command does not match). This is the default when invoked as a
+     non-interactive script.</para>
+    </section>
+*/
+static void cmdline_abort_on_fail(struct option *opt)
+{
+       tui_abort_on_fail = 1;
+}
+cmdline_opt("exit", 0, 'e', cmdline_abort_on_fail);
+
+/*** XML Argument:
+    <section id="a:help">
+     <title><option>--help</option></title>
+     <subtitle>Print usage information</subtitle>
+     <para>Causes ctdb-test to print its command line arguments and then exit</para>
+    </section>
+*/
+static void cmdline_help(struct option *opt)
+{
+       print_license();
+       print_usage();
+       exit(EXIT_SUCCESS);
+}
+cmdline_opt("help", 0, 'h', cmdline_help);
+
+extern struct cmdline_option __start_cmdline[], __stop_cmdline[];
+
+static struct cmdline_option *get_cmdline_option(int opt)
+{
+       struct cmdline_option *copt;
+
+       /* if opt is < '0', we have been passed a long option, which is
+        * indexed directly */
+       if (opt < '0')
+               return __start_cmdline + opt;
+
+       /* otherwise search for the short option in the .val member */
+       for (copt = __start_cmdline; copt < __stop_cmdline; copt++)
+               if (copt->opt.val == opt)
+                       return copt;
+
+       return NULL;
+}
+
+static struct option *get_cmdline_options(void)
+{
+       struct cmdline_option *copts;
+       struct option *opts;
+       unsigned int x, n_opts;
+
+       n_opts = ((void *)__stop_cmdline - (void *)__start_cmdline) /
+               sizeof(struct cmdline_option);
+
+       opts = talloc_zero_array(NULL, struct option, n_opts + 1);
+       copts = __start_cmdline;
+
+       for (x = 0; x < n_opts; x++) {
+               unsigned int y;
+
+               if (copts[x].opt.has_arg > 2)
+                       errx(1, "Bad argument `%s'", copts[x].opt.name);
+
+               for (y = 0; y < x; y++)
+                       if ((copts[x].opt.val && copts[x].opt.val
+                                               == opts[y].val)
+                                       || streq(copts[x].opt.name,
+                                               opts[y].name))
+                               errx(1, "Conflicting arguments %s = %s\n",
+                                    copts[x].opt.name, opts[y].name);
+
+               opts[x] = copts[x].opt;
+               opts[x].val = x;
+       }
+
+       return opts;
+}
+
+static char *get_cmdline_optstr(void)
+{
+       struct cmdline_option *copts;
+       unsigned int x, n_opts;
+       char *optstr, tmpstr[3], *colonstr = "::";
+
+       n_opts = ((void *)__stop_cmdline - (void *)__start_cmdline) /
+               sizeof(struct cmdline_option);
+
+       optstr = talloc_size(NULL, 3 * n_opts * sizeof(*optstr) + 1);
+       *optstr = '\0';
+
+       copts = __start_cmdline;
+
+       for (x = 0; x < n_opts; x++) {
+               if (!copts[x].opt.val)
+                       continue;
+               snprintf(tmpstr, 4, "%c%s", copts[x].opt.val,
+                       colonstr + 2 - copts[x].opt.has_arg);
+               strcat(optstr, tmpstr);
+       }
+       return optstr;
+}
+
+static int ctdb_test_poll(struct pollfd *fds, nfds_t nfds, int timeout,
+                         const char *location)
+{
+       if (should_i_fail("poll")) {
+               errno = EINVAL;
+               return -1;
+       }
+       return poll(fds, nfds, timeout);
+}
+
+static void *ctdb_test_malloc(size_t size, const char *location)
+{
+       if (should_i_fail("malloc")) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       return talloc_named_const(allocations, size, location);
+}
+
+static void ctdb_test_free(void *ptr, const char *location)
+{
+       talloc_free(ptr);
+}
+
+static void *ctdb_test_realloc(void *ptr, size_t size, const char *location)
+{
+       if (should_i_fail("realloc")) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       ptr = _talloc_realloc(allocations, ptr, size, location);
+       if (ptr)
+               talloc_set_name(ptr, "%s (reallocated to %u at %s)",
+                               talloc_get_name(ptr), size, location);
+       return ptr;
+}
+
+static ssize_t ctdb_test_read(int fd, void *buf, size_t count,
+                             const char *location)
+{
+       if (should_i_fail("read")) {
+               errno = EBADF;
+               return -1;
+       }
+       /* FIXME: We only let parent read and write.
+        * We should have child do short read, at least until whole packet is
+        * read.  Then we terminate child. */
+       if (!am_parent()) {
+               log_line(LOG_DEBUG, "Child reading fd");
+               return 0;
+       }
+       return read(fd, buf, count);
+}
+
+static ssize_t ctdb_test_write(int fd, const void *buf, size_t count,
+                              const char *location)
+{
+       if (should_i_fail("write")) {
+               errno = EBADF;
+               return -1;
+       }
+       /* FIXME: We only let parent read and write.
+        * We should have child do short write, at least until whole packet is
+        * written, then terminate child.  Check that all children and parent
+        * write the same data. */
+       if (!am_parent()) {
+               log_line(LOG_DEBUG, "Child writing fd");
+               return 0;
+       }
+       return write(fd, buf, count);
+}
+
+static int ctdb_test_socket(int domain, int type, int protocol,
+                           const char *location)
+{
+       if (should_i_fail("socket")) {
+               errno = EINVAL;
+               return -1;
+       }
+       return socket(domain, type, protocol);
+}
+
+static int ctdb_test_connect(int sockfd, const struct sockaddr *addr,
+                            socklen_t addrlen, const char *location)
+{
+       if (should_i_fail("connect")) {
+               errno = EINVAL;
+               return -1;
+       }
+       return connect(sockfd, addr, addrlen);
+}
+
+static struct tdb_context *ctdb_test_tdb_open_ex(const char *name,
+                                                int hash_size, int tdb_flags,
+                                                int open_flags, mode_t mode,
+                                                const struct tdb_logging_context *log_ctx,
+                                                tdb_hash_func hash_fn,
+                                                const char *location)
+{
+       if (should_i_fail("tdb_open_ex")) {
+               errno = ENOENT;
+               return NULL;
+       }
+       return tdb_open_ex(name, hash_size, tdb_flags, open_flags, mode,
+                          log_ctx, hash_fn);
+}
+
+/* We don't need to fail this, but as library expects to be able to free()
+   dptr, we need to make sure it's talloced (see ctdb_test_free) */
+static TDB_DATA ctdb_test_tdb_fetch(struct tdb_context *tdb, TDB_DATA key)
+{
+       TDB_DATA ret = tdb_fetch(tdb, key);
+       if (ret.dptr) {
+               ret.dptr = talloc_memdup(allocations, ret.dptr, ret.dsize);
+               if (!ret.dptr) {
+                       err(1, "Could not memdup %zu bytes", ret.dsize);
+               }
+       }
+       return ret;
+}
+
+void check_allocations(void)
+{
+       talloc_free(working);
+
+       if (talloc_total_blocks(allocations) != 1) {
+               log_line(LOG_ALWAYS, "Resource leak:");
+               talloc_report_full(allocations, stdout);
+               exit(1);
+       }
+}
+
+/* This version adds one byte (for nul term) */
+void *grab_fd(int fd, size_t *size)
+{
+       size_t max = 16384;
+       int ret;
+       void *buffer = talloc_array(NULL, char, max+1);
+
+       *size = 0;
+       while ((ret = read(fd, buffer + *size, max - *size)) > 0) {
+               *size += ret;
+               if (*size == max)
+                       buffer = talloc_realloc(NULL, buffer, char, max *= 2 + 1);
+       }
+       if (ret < 0) {
+               talloc_free(buffer);
+               buffer = NULL;
+       }
+       return buffer;
+}
+
+int main(int argc, char *argv[])
+{
+       int input_fd, c;
+       const char *optstr;
+       struct option *options;
+
+       allocations = talloc_named_const(NULL, 1, "ctdb-test");
+       working = talloc_named_const(NULL, 1, "ctdb-test-working");
+
+       options = get_cmdline_options();
+       optstr = get_cmdline_optstr();
+
+       while ((c = getopt_long(argc, argv, optstr, options, NULL)) != EOF) {
+               struct cmdline_option *copt = get_cmdline_option(c);
+               if (!copt)
+                       errx(1, "Unknown argument");
+
+               copt->parse(&copt->opt);
+       }
+
+       if (optind == argc) {
+               log_line(LOG_DEBUG, "Disabling failtest due to stdin.");
+               failtest = false;
+               input_fd = STDIN_FILENO;
+       } else if (optind + 1 != argc)
+               errx(1, "Need a single argument: input filename");
+       else {
+               input_fd = open(argv[optind], O_RDONLY);
+               if (input_fd < 0)
+                       err(1, "Opening %s", argv[optind]);
+               tui_abort_on_fail = true;
+       }
+
+       run_inits();
+       if (!tui_quiet)
+               print_license();
+
+       log_line(LOG_VERBOSE, "initialisation done");
+
+       tui_run(input_fd);
+
+       /* Everyone loves a good error haiku! */
+       if (expects_remaining())
+               errx(1, "Expectations still / "
+                    "unfulfilled remaining. / "
+                    "Testing blossoms fail.");
+       check_allocations();
+       dump_failinfo();
+
+       return EXIT_SUCCESS;
+}
diff --git a/libctdb/test/ctdb-test.h b/libctdb/test/ctdb-test.h
new file mode 100644 (file)
index 0000000..68ab2ea
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef __HAVE_CTDB_TEST_H
+#define __HAVE_CTDB_TEST_H
+#include <stdlib.h>
+
+/* We hang all libctdb allocations off this talloc tree. */
+extern void *allocations;
+
+void check_allocations(void);
+
+/* Our own working state gets hung off this tree. */
+extern void *working;
+
+/* The ctdb connection; created by 'connect' command. */
+struct ctdb_connection *get_ctdb(void);
+
+/* Talloc bytes from an fd until EOF.  Nul terminate. */
+void *grab_fd(int fd, size_t *size);
+
+#endif /* __HAVE_CTDB_TEST_H */
diff --git a/libctdb/test/expect.c b/libctdb/test/expect.c
new file mode 100644 (file)
index 0000000..8e23a6d
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "tui.h"
+#include "log.h"
+#include "expect.h"
+#include <fnmatch.h>
+#include <talloc.h>
+#include "utils.h"
+
+/* Expect is used to set up expectations on logging, for automated
+ * testing. */
+struct expect {
+       struct expect *next;
+       int invert;
+       int matched;
+       char *command;
+       char *pattern;
+};
+
+static struct expect *expect;
+struct cmdstack
+{
+       struct cmdstack *prev;
+       char *command;
+};
+static struct cmdstack *current_command;
+
+/* We don't need to try to match every pattern length: we only need
+ * lengths where the next char matches. */
+static unsigned int maybe_skip(char next_char, const char *line)
+{
+       char str[2];
+
+       /* If next one is *, we can't skip any. */
+       if (next_char == '*')
+               return 1;
+
+       /* No more string? */
+       if (*line == '\0')
+               return 1;
+
+       /* Next one is space, skip up to next space. */
+       if (next_char == '\t' || next_char == ' ')
+               return strcspn(line+1, "\t ") + 1;
+
+       str[0] = next_char;
+       str[1] = '\0';
+       return strcspn(line+1, str) + 1;
+}
+
+/* Loose match for strings: whitespace can be any number of
+ * whitespace, and * matches anything.  Approximately. */
+static bool loose_match(const char *pattern, const char *line)
+{
+       /* Any whitespace in pattern matches any whitespace in line. */
+       if (*pattern == '\t' || *pattern == ' ') {
+               int i, j, pat_space, line_space;
+
+               pat_space = strspn(pattern, "\t ");
+               line_space = strspn(line, "\t ");
+
+               for (i = 1; i <= pat_space; i++)
+                       for (j = 1; j <= line_space; j++)
+                               if (loose_match(pattern+i, line+j))
+                                       return true;
+               return false;
+       }
+
+       if (*pattern == '*') {
+               int i, len = strlen(line);
+               for (i = 0; i <= len; i += maybe_skip(pattern[1],line+i))
+                       if (loose_match(pattern+1, line+i))
+                               return true;
+
+               return false;
+       }
+
+       if (*pattern == *line) {
+               if (*pattern == '\0')
+                       return true;
+               return loose_match(pattern+1, line+1);
+       }
+
+       return false;
+}
+
+/* Pattern can't have whitespace at start and end, due to our parser.
+ * Strip ot here. */
+static bool matches(const char *pattern, const char *line)
+{
+       unsigned int len;
+
+       line += strspn(line, "\t ");
+       len = strlen(line);
+       if (len > 0 && (line[len-1] == '\t' || line[len-1] == ' ')) {
+               char trimmed[len];
+               memcpy(trimmed, line, len);
+               while (trimmed[len-1] == '\t' || trimmed[len-1] == ' ') {
+                       if (len == 1)
+                               break;
+                       len--;
+               }
+               trimmed[len] = '\0';
+               return loose_match(pattern, trimmed);
+       }
+       return loose_match(pattern, line);
+}
+
+bool expect_log_hook(const char *line)
+{
+       struct expect *e;
+       bool ret = false;
+
+       if (current_command == NULL)
+               return false;
+
+       /* Only allow each pattern to match once, so we can easily
+        * expect something to happen twice. */
+       for (e = expect; e; e = e->next) {
+               if (!e->matched
+                   && streq(current_command->command, e->command)
+                   && matches(e->pattern, line)) {
+                       e->matched = 1;
+                       ret = true;
+               }
+       }
+       return ret;
+}
+
+bool expects_remaining(void)
+{
+       return expect != NULL;
+}
+
+static void expect_pre_command(const char *command)
+{
+       struct cmdstack *new = talloc(NULL, struct cmdstack);
+       new->prev = current_command;
+       new->command = talloc_strdup(new, command);
+       current_command = new;
+}
+
+static bool expect_post_command(const char *command)
+{
+       struct expect **e, **next, *old;
+       bool ret = true;
+       struct cmdstack *oldcmd;
+
+       for (e = &expect; *e; e = next) {
+               next = &(*e)->next;
+
+               if (!streq(current_command->command, (*e)->command))
+                       continue;
+
+               if (!(*e)->invert && !(*e)->matched) {
+                       if (tui_abort_on_fail)
+                               script_fail("Pattern '%s' did not match",
+                                           (*e)->pattern);
+                       log_line(LOG_VERBOSE, "Pattern '%s' did not match",
+                                (*e)->pattern);
+                       ret = false;
+               } else if ((*e)->invert && (*e)->matched) {
+                       if (tui_abort_on_fail)
+                               script_fail("Pattern '%s' matched",
+                                           (*e)->pattern);
+                       log_line(LOG_VERBOSE, "Pattern '%s' matched",
+                                (*e)->pattern);
+                       ret = false;
+               }
+
+               /* Unlink from list and free. */
+               old = *e;
+               *e = (*e)->next;
+               next = e;
+
+               talloc_free(old);
+       }
+
+       oldcmd = current_command;
+       current_command = current_command->prev;
+       talloc_free(oldcmd);
+       return ret;
+}
+
+static bool expect_cmd(int argc, char **argv)
+{
+       struct expect *e;
+       unsigned int i, len;
+       bool invert = false;
+
+       if (argc == 1) {
+               for (e = expect; e; e = e->next)
+                       log_line(LOG_UI, "%s: %s\"%s\"",
+                                e->command,
+                                e->invert ? "! " : "",
+                                e->pattern);
+               return true;
+       }
+
+       if (argv[1] && streq(argv[1], "!")) {
+               invert = true;
+               argv++;
+               argc--;
+       }
+
+       if (argc < 3)
+               return false;
+
+       if (!tui_is_command(argv[1])) {
+               log_line(LOG_ALWAYS, "expect: %s is not a command\n",
+                        argv[1]);
+               return false;
+       }
+
+       e = talloc(NULL, struct expect);
+       e->matched = 0;
+       e->invert = invert;
+       e->next = expect;
+
+       e->command = talloc_strdup(e, argv[1]);
+
+       for (len = 0, i = 2; i < argc; i++)
+               len += strlen(argv[i])+1;
+       e->pattern = talloc_array(e, char, len + 1);
+
+       e->pattern[0] = '\0';
+       for (i = 2; i < argc; i++) {
+               if (i == 2)
+                       sprintf(e->pattern+strlen(e->pattern), "%s", argv[i]);
+               else
+                       sprintf(e->pattern+strlen(e->pattern), " %s", argv[i]);
+       }
+       expect = e;
+       return true;
+}
+
+static void expect_help(int argc, char **argv)
+{
+#include "generated-expect-help:expect"
+/*** XML Help:
+    <section id="c:expect">
+     <title><command>expect</command></title>
+     <para>Catch logging information for automated testing</para>
+     <cmdsynopsis>
+      <command>expect</command>
+     </cmdsynopsis>
+     <cmdsynopsis>
+      <command>expect</command>
+      <arg choice="opt">!</arg>
+      <arg choice="req"><replaceable>command</replaceable></arg>
+      <arg choice="req"><replaceable>pattern</replaceable></arg>
+     </cmdsynopsis>
+     <para><command>expect</command> will set up a set of patterns to expect in
+      logging messages for a particular command. If that command finishes
+      without matching the specified pattern, the simulator will exit with a
+      non-zero return value.  After the command is run, all expectations on that
+      command are deleted.</para>
+     <para><command>expect</command> with no arguments will print out the
+      current list of expectations, as a command and a pattern.</para>
+     <para><command>expect <replaceable>command pattern</replaceable></command>
+      will expect the specified pattern to occur the next time
+      <replaceable>command</replaceable> is invoked. If an '!' appears before
+      the command, then the expectation is negated - if the pattern appears in
+      the output, then the simulator will exit with an error
+     </para>
+     <para>The pattern itself is similar to a simple shell wildcard,
+      except whitespace is loosely matched.  The * character will
+      match any a string of any length.</para>
+     </section>
+*/
+}
+
+static void init(void)
+{
+       tui_register_command("expect", expect_cmd, expect_help);
+       tui_register_pre_post_hook(expect_pre_command, expect_post_command);
+}
+
+init_call(init);
diff --git a/libctdb/test/expect.h b/libctdb/test/expect.h
new file mode 100644 (file)
index 0000000..d42ebdc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef __HAVE_EXPECT_H
+#define __HAVE_EXPECT_H
+
+/* Expect interface */
+void expect_before_command(const char *command);
+bool expect_log_hook(const char *line);
+void expect_after_command(void);
+
+/* Are there any expect commands unresolved? */
+bool expects_remaining(void);
+
+#endif /* __HAVE_EXPECT_H */
diff --git a/libctdb/test/failtest.c b/libctdb/test/failtest.c
new file mode 100644 (file)
index 0000000..2560e3e
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2004 Jeremy Kerr & Rusty Russell
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+#include "utils.h"
+#include "tui.h"
+#include "log.h"
+#include "failtest.h"
+#include "talloc.h"
+#include "dlinklist.h"
+#include <stdlib.h>
+#include <stdbool.h>
+#include <getopt.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+
+static unsigned int fails = 0, excessive_fails = 2;
+unsigned int failpoints = 0;
+static const char *failtest_no_report = NULL;
+bool failtest = true;
+
+struct fail_decision {
+       struct fail_decision *next, *prev;
+       char *location;
+       unsigned int tui_line;
+       bool failed;
+};
+
+static struct fail_decision *decisions;
+
+/* Failure path to follow (initialized by --failpath). */
+static const char *failpath = NULL, *orig_failpath;
+
+/*** XML Argument:
+    <section id="a:failpath">
+     <title><option>--failpath
+      <replaceable>path</replaceable></option></title>
+     <subtitle>Replay a failure path</subtitle>
+     <para>Given a failure path, (from <option>--failtest</option>), this will
+      replay the sequence of sucesses/failures, allowing debugging.  The input
+      should be the same as the original which caused the failure.
+      </para>
+
+     <para>This testing can be slow, but allows for testing of failure
+      paths which would otherwise be very difficult to test
+     automatically.</para>
+    </section>
+*/
+static void cmdline_failpath(struct option *opt)
+{
+       extern char *optarg;
+       if (!optarg)
+               errx(1, "failtest option requires an argument");
+       orig_failpath = failpath = optarg;
+}
+cmdline_opt("failpath", 1, 0, cmdline_failpath);
+
+/*** XML Argument:
+    <section id="a:failtest-no-report">
+     <title><option>--failtest-no-report
+      <replaceable>function</replaceable></option></title>
+     <subtitle>Exclude a function from excessive failure reports</subtitle>
+
+     <para>Sometimes code deliberately ignores the failures of a
+     certain function.  This suppresses complaints about that for any
+     functions containing this name.</para> </section>
+*/
+static void cmdline_failtest_no_report(struct option *opt)
+{
+       extern char *optarg;
+       if (!optarg)
+               errx(1, "failtest-no-report option requires an argument");
+       failtest_no_report = optarg;
+}
+cmdline_opt("failtest-no-report", 1, 0, cmdline_failtest_no_report);
+
+/* Separate function to make .gdbinit easier */
+static bool failpath_fail(void)
+{
+       return true;
+}
+
+static bool do_failpath(const char *func)
+{
+       if (*failpath == '[') {
+               failpath++;
+               if (strncmp(failpath, func, strlen(func)) != 0
+                   || failpath[strlen(func)] != ']')
+                       errx(1, "Failpath expected %.*s not %s\n",
+                            strcspn(failpath, "]"), failpath, func);
+               failpath += strlen(func) + 1;
+       }
+
+       if (*failpath == ':') {
+               unsigned long line;
+               char *after;
+               failpath++;
+               line = strtoul(failpath, &after, 10);
+               if (*after != ':')
+                       errx(1, "Bad failure path line number %s\n",
+                            failpath);
+               if (line != tui_linenum)
+                       errx(1, "Unexpected line number %lu vs %u\n",
+                            line, tui_linenum);
+               failpath = after+1;
+       }
+
+       switch ((failpath++)[0]) {
+       case 'F':
+       case 'f':
+               return failpath_fail();
+       case 'S':
+       case 's':
+               return false;
+       case 0:
+               failpath = NULL;
+               return false;
+       default:
+               errx(1, "Failpath '%c' failed to path",
+                    failpath[-1]);
+       }
+}
+
+static char *failpath_string_for_line(struct fail_decision *dec)
+{
+       char *ret = NULL;
+       struct fail_decision *i;
+
+       for (i = decisions; i; i = i->next) {
+               if (i->tui_line != dec->tui_line)
+                       continue;
+               ret = talloc_asprintf_append(ret, "[%s]%c",
+                                            i->location,
+                                            i->failed ? 'F' : 'S');
+       }
+       return ret;
+}
+
+static char *failpath_string(void)
+{
+       char *ret = NULL;
+       struct fail_decision *i;
+
+       for (i = decisions; i; i = i->next)
+               ret = talloc_asprintf_append(ret, "[%s]:%i:%c",
+                                            i->location, i->tui_line,
+                                            i->failed ? 'F' : 'S');
+       return ret;
+}
+
+static void warn_failure(void)
+{
+       char *warning;
+       struct fail_decision *i;
+       int last_warning = 0;
+
+       log_line(LOG_ALWAYS, "WARNING: test ignores failures at %s",
+                failpath_string());
+
+       for (i = decisions; i; i = i->next) {
+               if (i->failed && i->tui_line > last_warning) {
+                       warning = failpath_string_for_line(i);
+                       log_line(LOG_ALWAYS, " Line %i: %s",
+                                i->tui_line, warning);
+                       talloc_free(warning);
+                       last_warning = i->tui_line;
+               }
+       }
+}
+
+bool am_parent(void)
+{
+       struct fail_decision *i;
+
+       for (i = decisions; i; i = i->next) {
+               if (i->failed)
+                       return false;
+       }
+       return true;
+}
+
+/* Should I fail at this point?  Once only: it would be too expensive
+ * to fail at every possible call. */
+bool should_i_fail_once(const char *location)
+{
+       char *p;
+       struct fail_decision *i;
+
+       if (failpath) {
+               p = strstr(orig_failpath ?: "", location);
+               if (p && p <= failpath
+                   && p[-1] == '[' && p[strlen(location)] == ']')
+                       return false;
+
+               return do_failpath(location);
+       }
+
+       for (i = decisions; i; i = i->next)
+               if (streq(location, i->location))
+                       return false;
+
+       if (should_i_fail(location)) {
+               excessive_fails++;
+               return true;
+       }
+       return false;
+}
+
+/* Should I fail at this point? */
+bool should_i_fail(const char *func)
+{
+       pid_t child;
+       int status;
+       struct fail_decision *dec;
+
+       if (failpath)
+               return do_failpath(func);
+
+       failpoints++;
+       if (!failtest)
+               return false;
+
+       /* If a testcase ignores a spuriously-inserted failure, it's
+        * not specific enough, and we risk doing 2^n tests! */
+       if (fails > excessive_fails) {
+               static bool warned = false;
+               if (!warned++)
+                       warn_failure();
+       }
+
+       dec = talloc(NULL, struct fail_decision);
+       dec->location = talloc_strdup(dec, func);
+       dec->tui_line = tui_linenum;
+
+       DLIST_ADD_END(decisions, dec, struct fail_decision);
+
+       fflush(stdout);
+       child = fork();
+       if (child == -1)
+               err(1, "fork failed for failtest!");
+
+       /* The child actually fails.  The script will screw up at this
+        * point, but should not crash. */
+       if (child == 0) {
+               dec->failed = true;
+               if (!failtest_no_report || !strstr(func, failtest_no_report))
+                       fails++;
+               return true;
+       }
+
+       dec->failed = false;
+       while (waitpid(child, &status, 0) < 0) {
+               if (errno != EINTR)
+                       err(1, "failtest waitpid failed for child %i",
+                           (int)child);
+       }
+
+       /* If child succeeded, or mere script failure, continue. */
+       if (WIFEXITED(status) && (WEXITSTATUS(status) == EXIT_SUCCESS
+                                 || WEXITSTATUS(status) == EXIT_SCRIPTFAIL))
+               return false;
+
+       /* Report unless child already reported it. */
+       if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SILENT) {
+               /* Reproduce child's path */
+               dec->failed = true;
+               log_line(LOG_ALWAYS, "Child %s %i on failure path: %s",
+                        WIFEXITED(status) ? "exited" : "signalled",
+                        WIFEXITED(status) ? WEXITSTATUS(status)
+                        : WTERMSIG(status), failpath_string());
+       }
+       exit(EXIT_SILENT);
+}
+
+void dump_failinfo(void)
+{
+       log_line(LOG_VERBOSE, "Hit %u failpoints: %s",
+                failpoints, failpath_string());
+}
diff --git a/libctdb/test/failtest.h b/libctdb/test/failtest.h
new file mode 100644 (file)
index 0000000..c361f84
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef FAILTEST_H
+#define FAILTEST_H
+#include <stdbool.h>
+
+bool should_i_fail_once(const char *location);
+bool should_i_fail(const char *func);
+
+bool failtest;
+
+/* Parent never has artificial failures. */
+bool am_parent(void);
+
+void dump_failinfo(void);
+
+#endif /* FAILTEST_H */
diff --git a/libctdb/test/log.c b/libctdb/test/log.c
new file mode 100644 (file)
index 0000000..a4a9be6
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+#include "log.h"
+#include "tui.h"
+#include "utils.h"
+#include "expect.h"
+#include <string.h>
+#include <talloc.h>
+
+static struct {
+       enum log_type   type;
+       char *          name;
+} log_names[] = {
+       { LOG_WRITE,    "write" },
+       { LOG_READ,     "read" },
+       { LOG_LIB,      "lib" },
+       { LOG_VERBOSE,  "verbose" },
+};
+
+static FILE *logstream;
+static int typemask = ~LOG_VERBOSE;
+
+bool log_line(enum log_type type, const char *format, ...)
+{
+       va_list ap;
+       char *line;
+       bool ret;
+
+       va_start(ap, format);
+       line = talloc_vasprintf(NULL, format, ap);
+       va_end(ap);
+
+       if (!type || (type & typemask))
+               fprintf(logstream ?: stderr, "%s\n", line);
+
+       ret = expect_log_hook(line);
+       talloc_free(line);
+       return ret;
+}
+
+static void log_partial_v(enum log_type type,
+                         char *buf,
+                         unsigned bufsize,
+                         const char *format,
+                         va_list ap)
+{
+       char *ptr;
+       int len = strlen(buf);
+
+       /* write to the end of buffer */
+       if (vsnprintf(buf + len, bufsize - len - 1, format, ap)
+                       > bufsize - len - 1)
+               log_line(LOG_ALWAYS, "log_line_partial buffer is full!");
+
+       ptr = buf;
+
+       /* print each bit that ends in a newline */
+       for (len = strcspn(ptr, "\n"); *(ptr + len);
+                       ptr += len, len = strcspn(ptr, "\n")) {
+               log_line(type, "%.*s", len++, ptr);
+       }
+
+       /* if we've printed, copy any remaining (non-newlined)
+          parts (including the \0) to the front of buf */
+       memmove(buf, ptr, strlen(ptr) + 1);
+}
+
+void log_partial(enum log_type type, char *buf, unsigned bufsize,
+                const char *format, ...)
+{
+       va_list ap;
+
+       va_start(ap, format);
+       log_partial_v(type, buf, bufsize, format, ap);
+       va_end(ap);
+}
+
+static inline int parsetype(const char *type)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(log_names); i++)
+               if (streq(log_names[i].name, type))
+                       return log_names[i].type;
+
+       return 0;
+}
+
+static bool log_admin(int argc, char **argv)
+{
+       int i;
+       int newtypemask = 0;
+
+       if (argc == 1) {
+               log_line(LOG_UI, "current log types:", typemask);
+
+               for (i = 0; i < ARRAY_SIZE(log_names); i++) {
+                       if (typemask & log_names[i].type)
+                               log_line(LOG_UI, "\t%s", log_names[i].name);
+               }
+               return true;
+       }
+
+       if (argc == 2) {
+               log_line(LOG_ALWAYS, "Expected =, + or - then args");
+               return false;
+       }
+
+       for (i = 2; i < argc; i++) {
+               int type;
+
+               if (!(type = parsetype(argv[i]))) {
+                       log_line(LOG_ALWAYS, "no such type %s", argv[i]);
+                       return false;
+               }
+               newtypemask |= type;
+       }
+
+       switch (*argv[1]) {
+       case '=':
+               typemask = newtypemask;
+               break;
+       case '-':
+               typemask &= ~newtypemask;
+               break;
+       case '+':
+               typemask |= newtypemask;
+               break;
+       default:
+               log_line(LOG_ALWAYS, "unknown modifer: %c", *argv[1]);
+               return false;
+       }
+
+       return true;
+}
+
+static void log_admin_help(int agc, char **argv)
+{
+#include "generated-log-help:log"
+/*** XML Help:
+    <section id="c:log">
+     <title><command>log</command></title>
+     <para>Manage logging settings</para>
+     <cmdsynopsis>
+      <command>log</command>
+      <group choice="opt">
+       <arg choice="plain">=</arg>
+       <arg choice="plain">+</arg>
+       <arg choice="plain">-</arg>
+      </group>
+      <arg choice="req"><replaceable>type, ...</replaceable></arg>
+     </cmdsynopsis>
+     <para>Each log message is classified into one of the following
+     types:</para>
+      <varlistentry>
+       <term>UI</term>
+       <listitem>
+        <para>Normal response from command lines.</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>LIB</term>
+       <listitem>
+        <para>Logging output from libctdb</para>
+       </listitem>
+      </varlistentry>
+     <variablelist>
+      <varlistentry>
+       <term>READ</term>
+       <listitem>
+        <para>Messages from ctdbd</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>WRITE</term>
+       <listitem>
+        <para>Messages to ctdbd</para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>VERBOSE</term>
+       <listitem>
+        <para>Verbose debug output</para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+
+     <para>The <command>log</command> command allows you to select
+      which messages are displayed. By default, all messages except
+      debug will be shown.</para>
+
+     <para>Without any arguments, the current logged types are listed.</para>
+
+     <para>With +, - or = character, those types will be added,
+      removed or set as the current types of messages to be logged
+      (repectively).</para>
+
+      <para>Messages generated as a result of user input are always
+      logged.  </para> </section>
+*/
+}
+
+static void log_init(void)
+{
+       logstream = stdout;
+       if (tui_quiet)
+               typemask = 0;
+       tui_register_command("log", log_admin, log_admin_help);
+}
+
+init_call(log_init);
diff --git a/libctdb/test/log.h b/libctdb/test/log.h
new file mode 100644 (file)
index 0000000..d19d6b2
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef __HAVE_LOG_H
+#define __HAVE_LOG_H
+#include <stdbool.h>
+
+enum log_type {
+       LOG_ALWAYS      = 0x00,
+       /* Packets that are written to ctdbd. */
+       LOG_WRITE       = 0x02,
+       /* Packets that are read from ctdbd. */
+       LOG_READ        = 0x04,
+       /* Logging from libctdb. */
+       LOG_LIB         = 0x08,
+       /* Logging from normal operations. */
+       LOG_UI          = 0x10,
+       /* Verbose debugging. */
+       LOG_VERBOSE     = 0x20,
+};
+
+/* Adds a \n for convenient logging.  Returns true if it was expected. */
+bool log_line(enum log_type type, const char *format, ...);
+/* Builds up buffer and prints out line at a time. */
+void log_partial(enum log_type type, char *buf, unsigned bufsize,
+                const char *format, ...);
+
+#endif /* __HAVE_LOG_H */
diff --git a/libctdb/test/tests/attachdb1.txt b/libctdb/test/tests/attachdb1.txt
new file mode 100644 (file)
index 0000000..d258376
--- /dev/null
@@ -0,0 +1,8 @@
+connect
+# This is just a sanity check
+expect attachdb attached: 1
+attachdb test.tdb false
+expect attachdb attached: 2
+attachdb test2.tdb false
+detachdb 2
+detachdb 1
diff --git a/libctdb/test/tests/connect1.txt b/libctdb/test/tests/connect1.txt
new file mode 100644 (file)
index 0000000..d2bfeca
--- /dev/null
@@ -0,0 +1,3 @@
+# Simple connect, disconnect
+connect
+disconnect
diff --git a/libctdb/test/tests/connect2.txt b/libctdb/test/tests/connect2.txt
new file mode 100644 (file)
index 0000000..d165a2c
--- /dev/null
@@ -0,0 +1,3 @@
+# Simple connect with explicit path, then disconnect
+connect /tmp/ctdb.socket
+disconnect
diff --git a/libctdb/test/tools/create-links b/libctdb/test/tools/create-links
new file mode 100755 (executable)
index 0000000..265efc1
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Create docbook-xml links
+
+# most things will be under this path.
+DOCB=/usr/share/sgml/docbook
+
+# potential location for xhtml/docbook.xsl
+XSLDIRS="$DOCB/xsl-stylesheets* $DOCB/stylesheet/xsl/nwalsh"
+
+# potential location for docbookx.dtd
+DTDDIRS="$DOCB/xml-dtd* $DOCB/dtd/xml/*"
+
+# look for a file (arg 1) in a set of dirs (arg 2). If it exists, create a link
+# (arg 3), in the current directory to the dir.
+condlink() {
+       file=$1
+       dirs=$2
+       link=$3
+
+       for d in $dirs
+       do
+               if [ -f $d/$file ]
+               then
+                       dir=$d
+                       break
+               fi
+       done
+
+       if [ -z "$dir" ]
+       then
+               echo Docbook support not found.  See README.  Faking it. >&2
+               exit 1
+       else
+               ln -sfn "$dir" "$link"
+       fi
+}
+
+condlink "xhtml/docbook.xsl" "$XSLDIRS" "link-xhtml"
+condlink "docbookx.dtd" "$DTDDIRS" "link-dtd"
diff --git a/libctdb/test/tools/extract-help b/libctdb/test/tools/extract-help
new file mode 100755 (executable)
index 0000000..4468956
--- /dev/null
@@ -0,0 +1,10 @@
+#! /bin/sh
+# Extract XML help from .c file.
+
+FILE=$1
+LINE=`expr $2 + 1`
+
+END=`tail -n +$LINE $1 | fgrep -n '*/' | cut -d: -f1 | head -n +1`
+END=`expr $END - 1`
+
+tail -n +$LINE $1 | head -n $END
diff --git a/libctdb/test/tools/gen-help b/libctdb/test/tools/gen-help
new file mode 100755 (executable)
index 0000000..b8839ae
--- /dev/null
@@ -0,0 +1,52 @@
+#! /bin/bash
+
+# We could have multiple occurances.  Create all of them.
+FILE=$1
+
+TMPF=`mktemp /tmp/gen-help.XXXXXX`
+trap "rm -f $TMPF*" EXIT
+cmdsed='s,.*<command>[         ]*\([^  ]*\)[   ]*</command>.*,\1,p'
+
+STARTLINE=1
+for LINE in `fgrep -n '/*** XML Help:' < $FILE | cut -d: -f1`; do
+    if [ -L tools/link-dtd ]; then
+       echo '<?xml version="1.0"?>' > $TMPF
+       echo '<!DOCTYPE article PUBLIC "-//OASIS//DTD Docbook XML V4.1.2//EN"' \
+           >> $TMPF
+       echo '"'`pwd`'/tools/link-dtd/docbookx.dtd">' >> $TMPF
+       echo '<article><section>' >> $TMPF
+       tools/extract-help $FILE $LINE >> $TMPF
+       echo '</section></article>' >> $TMPF
+
+       tr '\n' ' ' < $TMPF | sed -e 's/[[:space:]]\{2,\}/ /g' |
+        xsltproc tools/text.xsl - | fold -w80 -s > $TMPF.txt
+
+       COMMAND=`sed -n "$cmdsed" < $TMPF | head -n +1`
+       COMMAND_FILE=generated-`basename $FILE .c`-help:$COMMAND
+       #echo Creating $COMMAND_FILE
+
+        # Output description, in quotes.
+       echo 'log_line(LOG_ALWAYS,' > $COMMAND_FILE
+
+       TXTSTART=`grep -n '^  1\.1\.' $TMPF.txt | cut -d: -f1`
+       tail -n +`expr $TXTSTART + 2` $TMPF.txt | while read -r TXTLINE; do
+           echo "$TXTLINE" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' \
+               -e 's/$/\\n"/' >> $COMMAND_FILE
+       done
+       echo ');' >> $COMMAND_FILE
+    else
+       tools/extract-help $FILE $LINE > $TMPF
+
+       COMMAND=`sed -n "$cmdsed" < $TMPF | head -n +1`
+       COMMAND_FILE=generated-`basename $FILE .c`-help:$COMMAND
+       echo Faking up $COMMAND_FILE
+
+       echo 'log_line(LOG_ALWAYS,' > $COMMAND_FILE
+       sed 's/<arg [^>]*>/   /;s/<[^>]*>//g' < $TMPF |
+           sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' -e 's/$/\\n"/' \
+               >> $COMMAND_FILE
+       echo ');' >> $COMMAND_FILE
+    fi
+
+    STARTLINE=$LINE
+done
diff --git a/libctdb/test/tools/gen-usage b/libctdb/test/tools/gen-usage
new file mode 100755 (executable)
index 0000000..6fad0e1
--- /dev/null
@@ -0,0 +1,56 @@
+#! /bin/sh
+
+# read a list of C files with inline XML usage given on the command line, and
+# write a usage function on stdout
+
+tmpf=`mktemp /tmp/gen-help.XXXXXX`
+trap "rm -f $tmpf*" EXIT
+quote() {
+    sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' -e 's/$/\\n"/'
+}
+
+cat <<EOF
+#include <stdio.h>
+#include "utils.h"
+
+void print_usage(void)
+{
+       fprintf(stderr,
+"Usage: ctdb-test [options]\n"
+"Options available:\n"
+"\n"
+EOF
+
+for file in "$@"
+do
+    for line in `fgrep -n '/*** XML Argument:' < $file | cut -d: -f1`;
+    do
+       if [ -L tools/link-dtd ]; then
+
+           cat > $tmpf <<EOF
+<?xml version="1.0"?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD Docbook XML V4.1.2//EN"
+"`pwd`/tools/link-dtd/docbookx.dtd">
+<article>
+EOF
+           tools/extract-help $file $line >> $tmpf
+           echo '</article>' >> $tmpf
+
+           tr '\n' ' ' < $tmpf | sed -e 's/[[:space:]]\{2,\}/ /g' |
+               xsltproc tools/usage.xsl - | fold -w80 -s > $tmpf.txt
+
+           quote < $tmpf.txt
+       else
+           # if we don't have docbook, just strip out the tags and grab
+           # the first two lines
+           tools/extract-help $file $line > $tmpf
+           sed 's/<arg [^>]*>/   /;s/<[^>]*>//g;' < $tmpf | head -3 | quote
+       fi
+
+    done
+done
+
+cat <<EOF
+);
+}
+EOF
diff --git a/libctdb/test/tools/text.xsl b/libctdb/test/tools/text.xsl
new file mode 100644 (file)
index 0000000..dcbc64e
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+ <xsl:output method="text"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:template match="/">
+  <xsl:apply-templates select="article"/>
+ </xsl:template>
+
+ <xsl:template match="article">
+  <xsl:apply-templates select="section"/><xsl:text>
+</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="section">
+  <xsl:apply-templates select="title|para|cmdsynopsis|section"/>
+ </xsl:template>
+
+ <xsl:template match="title">
+  <xsl:apply-templates/><xsl:text>
+</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="subtitle">
+  <xsl:apply-templates/><xsl:text>
+</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="command|filename|varname|computeroutput|constant">
+  <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="option">
+  <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="screen">
+  <xsl:text>
+</xsl:text><xsl:apply-templates/><xsl:text>
+</xsl:text>
+ </xsl:template>
+
+
+ <xsl:template match="arg">
+  <xsl:choose>
+   <xsl:when test="@choice='opt'">
+    <xsl:text> [</xsl:text><xsl:apply-templates/><xsl:text>]</xsl:text>
+   </xsl:when>
+   <xsl:otherwise>
+    <xsl:text> </xsl:text><xsl:apply-templates/>
+   </xsl:otherwise>
+  </xsl:choose>
+ </xsl:template>
+
+ <xsl:template match="para">
+  <xsl:text>
+</xsl:text>
+<xsl:apply-templates/><xsl:text>
+</xsl:text>
+</xsl:template>
+
+ <xsl:template match="cmdsynopsis">
+  <xsl:text>
+</xsl:text>
+  <xsl:apply-templates select="command|sbr|arg"/><xsl:text>
+</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="synopfragmentref">
+  <xsl:variable name="target" select="id(@linkend)"/>
+  <xsl:apply-templates select="$target"/>
+ </xsl:template>
+
+ <xsl:template match="synopfragment">
+  <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="group">
+  <xsl:text>{ </xsl:text><xsl:for-each select="arg">
+   <xsl:apply-templates/>
+   <xsl:if test="position() != last()"><xsl:text> | </xsl:text></xsl:if>
+  </xsl:for-each><xsl:text> }</xsl:text>
+ </xsl:template>
+
+
+ <xsl:template match="replaceable">{<xsl:apply-templates/>}</xsl:template>
+
+
+ <xsl:template match="sbr">
+  <xsl:text>
+</xsl:text>
+</xsl:template>
+
+ <xsl:template match="text()"><xsl:value-of select="."/></xsl:template>
+
+ <xsl:template match="node()">
+  <xsl:message terminate="yes">Unknown node <xsl:value-of select="name()"/>
+</xsl:message>
+ </xsl:template>
+
+ <xsl:template match="simplelist">
+  <xsl:for-each select="member">
+   <xsl:apply-templates/>
+   <xsl:if test="position() != last()"><xsl:text>, </xsl:text></xsl:if>
+  </xsl:for-each>
+ </xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/libctdb/test/tools/usage.xsl b/libctdb/test/tools/usage.xsl
new file mode 100644 (file)
index 0000000..1a6150c
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+ <xsl:output method="text"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:include href="text.xsl"/>
+
+ <xsl:template match="section">
+  <xsl:apply-templates select="title"/>
+  <xsl:text>    </xsl:text><xsl:apply-templates select="subtitle"/>
+ </xsl:template>
+
+ <xsl:template match="para">
+  <xsl:apply-templates/>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/libctdb/test/tui.c b/libctdb/test/tui.c
new file mode 100644 (file)
index 0000000..d411e6f
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "tui.h"
+#include "log.h"
+#include "ctdb-test.h"
+#include "utils.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <assert.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <talloc.h>
+#include <dlinklist.h>
+
+int tui_echo_commands;
+int tui_abort_on_fail;
+int tui_quiet;
+int tui_linenum = 1;
+char *extension_path;
+static bool stop;
+
+struct command {
+       struct command *next, *prev;
+       char    name[TUI_MAX_CMD_LEN+1];
+       bool    (*handler)(int, char **);
+       void    (*helpfn) (int, char **);
+};
+
+struct pre_post_hook {
+       struct pre_post_hook *next, *prev;
+       void    (*pre)(const char *);
+       bool    (*post)(const char *);
+};
+
+static struct command *commands;
+static struct pre_post_hook *pre_post_hooks;
+
+static bool tui_exit(int argc, char **argv)
+{
+       stop = true;
+       return true;
+}
+
+static bool tui_argtest(int argc, char **argv)
+{
+       int i;
+
+       for (i = 0; i < argc; i++)
+               log_line(LOG_UI, "argv[%d]: \"%s\"", i, argv[i]);
+
+       return true;
+}
+
+static inline struct command *find_command(const char *name)
+{
+       struct command *cmd;
+       for (cmd = commands; cmd; cmd = cmd->next)
+               if (!strcmp(name, cmd->name))
+                       return cmd;
+
+       return NULL;
+}
+
+bool tui_is_command(const char *name)
+{
+       return find_command(name) != NULL;
+}
+
+static void do_pre_commands(const char *cmd)
+{
+       struct pre_post_hook *i;
+       for (i = pre_post_hooks; i; i = i->next)
+               if (i->pre)
+                       i->pre(cmd);
+}
+
+static bool do_post_commands(const char *cmd)
+{
+       struct pre_post_hook *i;
+       bool ret = true;
+
+       for (i = pre_post_hooks; i; i = i->next)
+               if (i->post && !i->post(cmd))
+                       ret = false;
+       return ret;
+}
+
+static bool tui_help(int argc, char **argv)
+{
+       struct command *cmd;
+
+       if (argc == 1) {
+               log_line(LOG_UI, "CTDB tester\n"
+               "help is available on the folowing commands:");
+               for (cmd = commands; cmd; cmd = cmd->next) {
+                       if (cmd->helpfn)
+                               log_line(LOG_UI, "\t%s", cmd->name);
+               }
+       } else {
+               if (!(cmd = find_command(argv[1]))) {
+                       log_line(LOG_ALWAYS, "No such command '%s'", argv[1]);
+                       return false;
+               }
+               if (!cmd->helpfn) {
+                       log_line(LOG_UI, "No help for the '%s' function",
+                               argv[1]);
+                       return false;
+               }
+               cmd->helpfn(argc-1, argv+1);
+       }
+       return true;
+
+
+}
+
+static void tui_help_help(int argc, char **argv)
+{
+#include "generated-tui-help:help"
+/*** XML Help:
+    <section id="c:help">
+     <title><command>help</command></title>
+     <para>Displays general help, or help for a specified command</para>
+     <cmdsynopsis>
+      <command>help</command>
+      <arg choice="opt">command</arg>
+     </cmdsynopsis>
+     <para>With no arguments, <command>help</command> will show general system
+      help, and list the available commands. If an argument is specified, then
+      <command>help</command> will show help for that command, if
+      available.</para>
+    </section>
+*/
+}
+
+static void tui_exit_help(int argc, char **argv)
+{
+#include "generated-tui-help:exit"
+/*** XML Help:
+    <section id="c:exit">
+     <title><command>exit</command>,
+     <command>quit</command></title>
+     <para>Exit the simulator</para>
+     <cmdsynopsis>
+      <command>exit</command>
+     </cmdsynopsis>
+     <cmdsynopsis>
+      <command>quit</command>
+     </cmdsynopsis>
+
+     <para>The <command>exit</command> and <command>quit</command>
+      commands are synonomous.  They both exit the simulator.
+     </para>
+    </section>
+ */
+}
+
+void script_fail(const char *fmt, ...)
+{
+       char *str;
+       va_list arglist;
+
+       log_line(LOG_ALWAYS, "Script failed at line %i: ", tui_linenum);
+
+       va_start(arglist, fmt);
+       str = talloc_vasprintf(NULL, fmt, arglist);
+       va_end(arglist);
+
+       log_line(LOG_ALWAYS, "%s", str);
+       talloc_free(str);
+
+       check_allocations();
+       exit(EXIT_SCRIPTFAIL);
+}
+
+bool tui_do_command(int argc, char *argv[], bool abort)
+{
+       struct command *cmd;
+       bool ret = true;
+
+       if ((cmd = find_command(argv[0]))) {
+               do_pre_commands(cmd->name);
+               if (!cmd->handler(argc, argv)) {
+                       /* Abort on UNEXPECTED failure. */
+                       if (!log_line(LOG_UI, "%s: command failed", argv[0])
+                           && abort)
+                               script_fail("%s failed", argv[0]);
+                       ret = false;
+               }
+               if (!do_post_commands(cmd->name))
+                       ret = false;
+               return ret;
+       }
+
+       if (abort)
+               script_fail("%s not found", argv[0]);
+
+       log_line(LOG_ALWAYS, "%s: command not found", argv[0]);
+       return false;
+}
+
+/**
+ * backslash-escape a binary data block into a newly allocated
+ * string
+ *
+ * @param src a pointer to the data block
+ * @param src_len the length of the data block
+ * @return NULL if out of memory, or a pointer to the allocated escaped
+ *    string, which is terminated with a '\0' character
+ */
+static char *escape(const char *src, size_t src_len)
+{
+       static const char hexbuf[]= "0123456789abcdef";
+       char *dest, *p;
+       size_t i;
+
+       /* src_len * 4 is always safe, it's the longest escape
+          sequence for all characters */
+       dest = talloc_array(src, char, src_len * 4 + 1);
+       p = dest;
+
+       for (i = 0; i < src_len; i++) {
+               if (src[i] == '\n') {
+                       *p++ = '\\';
+                       *p++ = 'n';
+               } else if (src[i] == '\r') {
+                       *p++ = '\\';
+                       *p++ = 'r';
+               } else if (src[i] == '\0') {
+                       *p++ = '\\';
+                       *p++ = '0';
+               } else if (src[i] == '\t') {
+                       *p++ = '\\';
+                       *p++ = 't';
+               } else if (src[i] == '\\') {
+                       *p++ = '\\';
+                       *p++ = '\\';
+               } else if (src[i] & 0x80 || (src[i] & 0xe0) == 0) {
+                       *p++ = '\\';
+                       *p++ = 'x';
+                       *p++ = hexbuf[(src[i] >> 4) & 0xf];
+                       *p++ = hexbuf[src[i] & 0xf];
+               } else
+                       *p++ = src[i];
+       }
+
+       *p++ = 0;
+       return dest;
+}
+
+/* Process `command`: update off to point to tail backquote */
+static char *backquote(char *line, unsigned int *off)
+{
+       char *end, *cmdstr, *str;
+       FILE *cmdfile;
+       size_t used, len, i;
+       int status;
+
+       /* Skip first backquote, look for next one. */
+       (*off)++;
+       end = strchr(line + *off, '`');
+       if (!end)
+               script_fail("no matching \"`\" found");
+
+       len = end - (line + *off);
+       cmdstr = talloc_asprintf(line, "PATH=%s; %.*s",
+                                extension_path, (int)len, line + *off);
+       cmdfile = popen(cmdstr, "r");
+       if (!cmdfile)
+               script_fail("failed to popen '%s': %s\n",
+                           cmdstr, strerror(errno));
+
+       /* Jump to backquote. */
+       *off += len;
+
+       /* Read command output. */
+       used = 0;
+       len = 1024;
+       str = talloc_array(line, char, len);
+
+       while ((i = fread(str + used, 1, len - used, cmdfile)) != 0) {
+               used += i;
+               if (used == len) {
+                       if (len > 1024*1024)
+                               script_fail("command '%s' output too long\n",
+                                           cmdstr);
+                       len *= 2;
+                       str = talloc_realloc(line, str, char, len);
+               }
+       }
+       status = pclose(cmdfile);
+       if (status == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+               script_fail("command '%s' failed\n", cmdstr);
+
+       return escape(str, used);
+}
+
+static char *append_char(char **argv, unsigned int argc, char c)
+{
+       if (!argv[argc])
+               return talloc_asprintf(argv, "%c", c);
+       return talloc_asprintf_append(argv[argc], "%c", c);
+}
+
+static char *append_string(char **argv, unsigned int argc, const char *str)
+{
+       if (!argv[argc])
+               return talloc_asprintf(argv, "%s", str);
+       return talloc_asprintf_append(argv[argc], "%s", str);
+}
+
+static void process_line(char *line, unsigned int off)
+{
+       unsigned int argc, i;
+       char **argv;
+
+       if (tui_echo_commands)
+               printf("%u:%s\n", tui_linenum, line + off);
+
+       /* Talloc argv off line so commands can use it for auto-cleanup. */
+       argv = talloc_zero_array(line, char *, TUI_MAX_ARGS+1);
+       argc = 0;
+       for (i = off; line[i]; i++) {
+               if (isspace(line[i])) {
+                       /* If anything in this arg, move to next. */
+                       if (argv[argc])
+                               argc++;
+               } else if (line[i] == '`') {
+                       char *inside = backquote(line, &i);
+                       argv[argc] = append_string(argv, argc, inside);
+               } else {
+                       /* If it is a comment, stop before we process `` */
+                       if (!argv[0] && line[i] == '#')
+                               goto out;
+
+                       argv[argc] = append_char(argv, argc, line[i]);
+               }
+       }
+
+       if (argv[0]) {
+               if (argv[argc])
+                       argv[++argc] = NULL;
+               tui_do_command(argc, argv, tui_abort_on_fail);
+       }
+
+out:
+       tui_linenum++;
+       return;
+}
+
+static void readline_process_line(char *line)
+{
+       char *talloc_line;
+       if (!line) {
+               stop = true;
+               return;
+       }
+
+       add_history(line);
+
+       /* Readline isn't talloc-aware, so copy string: functions can
+        * hang temporary variables off this. */
+       talloc_line = talloc_strdup(NULL, line);
+       process_line(talloc_line, 0);
+       talloc_free(talloc_line);
+}
+
+static void run_whole_file(int fd)
+{
+       char *file, *p;
+       size_t size, len;
+
+       file = grab_fd(fd, &size);
+       if (!file)
+               err(1, "Grabbing file");
+
+       for (p = file; p < file + size; p += len+1) {
+               len = strcspn(p, "\n");
+               p[len] = '\0';
+               process_line(file, p - file);
+       }
+}
+
+void tui_run(int fd)
+{
+       tui_register_command("exit", tui_exit, tui_exit_help);
+       tui_register_command("quit", tui_exit, tui_exit_help);
+       tui_register_command("q", tui_exit, tui_exit_help);
+       tui_register_command("test", tui_argtest, NULL);
+       tui_register_command("help", tui_help, tui_help_help);
+
+       if (fd == STDIN_FILENO) {
+               stop = false;
+               rl_callback_handler_install(tui_quiet ? "" : "> ",
+                                           readline_process_line);
+               while (!stop)
+                       rl_callback_read_char();
+               rl_callback_handler_remove();
+               if (!tui_quiet)
+                       printf("\n");
+       } else
+               run_whole_file(fd);
+}
+
+int tui_register_pre_post_hook(void (*pre)(const char *),
+                              bool (*post)(const char *))
+{
+       struct pre_post_hook *h;
+
+       h = talloc(NULL, struct pre_post_hook);
+       h->pre = pre;
+       h->post = post;
+       DLIST_ADD(pre_post_hooks, h);
+       return 0;
+}
+
+int tui_register_command(const char *command,
+                        bool (*handler)(int, char **),
+                        void (*helpfn)(int, char **))
+{
+       struct command *cmd;
+
+       assert(strlen(command) < TUI_MAX_CMD_LEN);
+
+       cmd = talloc(NULL, struct command);
+       strncpy(cmd->name, command, TUI_MAX_CMD_LEN);
+       cmd->handler = handler;
+       cmd->helpfn  = helpfn;
+
+       DLIST_ADD(commands, cmd);
+
+       return 0;
+}
diff --git a/libctdb/test/tui.h b/libctdb/test/tui.h
new file mode 100644 (file)
index 0000000..3861599
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef __HAVE_TUI_H
+#define __HAVE_TUI_H
+
+#include <stdbool.h>
+
+#define TUI_MAX_CMD_LEN                1024
+#define TUI_MAX_ARGS           128
+
+int tui_register_command(const char *command,
+                        bool (*handler)(int argc, char **argv),
+                        void (*helpfn)(int argc, char **argv));
+
+int tui_register_pre_post_hook(void (*pre)(const char *),
+                              bool (*post)(const char *));
+
+void tui_run(int fd);
+
+bool tui_do_command(int argc, char *argv[], bool abort);
+
+/* Is this a valid command?  Sanity check for expect. */
+bool tui_is_command(const char *name);
+
+/* A script test failed (a command failed with -e, or an expect failed). */
+void script_fail(const char *fmt, ...) __attribute__((noreturn));
+
+extern int tui_echo_commands;
+extern int tui_abort_on_fail;
+extern int tui_quiet;
+extern int tui_linenum;
+
+#endif /* __HAVE_TUI_H */
diff --git a/libctdb/test/utils.h b/libctdb/test/utils.h
new file mode 100644 (file)
index 0000000..eb84913
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+
+This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
+
+Copyright (c) 2003,2004 Rusty Russell
+
+This file is part of nfsim.
+
+nfsim is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+nfsim 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 nfsim; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef _UTILS_H
+#define _UTILS_H
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+/* Is A == B ? */
+#define streq(a,b) (strcmp((a),(b)) == 0)
+
+/* Does A start with B ? */
+#define strstarts(a,b) (strncmp((a),(b),strlen(b)) == 0)
+
+/* Does A end in B ? */
+static inline bool strends(const char *a, const char *b)
+{
+       if (strlen(a) < strlen(b))
+               return false;
+
+       return streq(a + strlen(a) - strlen(b), b);
+}
+
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+/* Paste two tokens together. */
+#define ___cat(a,b) a ## b
+#define __cat(a,b) ___cat(a,b)
+
+/* Try to give a unique identifier: this comes close, iff used as static. */
+#define __unique_id(stem) __cat(__cat(__uniq,stem),__LINE__)
+
+enum exitcodes {
+       /* EXIT_SUCCESS, EXIT_FAILURE is in stdlib.h */
+       EXIT_SCRIPTFAIL = EXIT_FAILURE + 1,
+       EXIT_SILENT,
+};
+
+/* init code */
+typedef void (*initcall_t)(void);
+#define init_call(fn) \
+       static initcall_t __initcall_##fn \
+       __attribute__((__unused__)) \
+       __attribute__((__section__("init_call"))) = &fn
+
+/* distributed command line options */
+struct cmdline_option
+{
+       struct option opt;
+       void (*parse)(struct option *opt);
+}  __attribute__((aligned(64)));       /* align it to 64 for 64bit arch,
+ * <rusty> LaF0rge: space is cheap.  A comment might be nice. */
+
+#define cmdline_opt(_name, _has_arg, _c, _fn)                                \
+       static struct cmdline_option __cat(__cmdlnopt_,__unique_id(_fn))      \
+       __attribute__((__unused__))                                           \
+       __attribute__((__section__("cmdline")))                               \
+       = { .opt = { .name = _name, .has_arg = _has_arg, .val = _c },         \
+          .parse = _fn }
+
+/* In generated-usage.c */
+void print_usage(void);
+
+#endif /* _UTILS_H */