Browse Source

tdb2: tools/

As per-tdb's tools.
Rusty Russell 15 years ago
parent
commit
81defbc32d
4 changed files with 1346 additions and 0 deletions
  1. 8 0
      ccan/tdb2/tools/Makefile
  2. 29 0
      ccan/tdb2/tools/mktdb.c
  3. 813 0
      ccan/tdb2/tools/tdbtool.c
  4. 496 0
      ccan/tdb2/tools/tdbtorture.c

+ 8 - 0
ccan/tdb2/tools/Makefile

@@ -0,0 +1,8 @@
+LDLIBS:=../../tdb2.o ../../hash.o ../../tally.o
+CFLAGS:=-I../../.. -Wall -g #-g -O3 #-g -pg
+LDFLAGS:=-L../../..
+
+default: tdbtorture tdbtool mktdb
+
+clean:
+	rm -f tdbtorture tdbtool mktdb

+ 29 - 0
ccan/tdb2/tools/mktdb.c

@@ -0,0 +1,29 @@
+#include <ccan/tdb2/tdb2.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <err.h>
+
+int main(int argc, char *argv[])
+{
+	unsigned int i, num_recs;
+	struct tdb_context *tdb;
+
+	if (argc != 3 || (num_recs = atoi(argv[2])) == 0)
+		errx(1, "Usage: mktdb <tdbfile> <numrecords>");
+
+	tdb = tdb_open(argv[1], TDB_DEFAULT, O_CREAT|O_TRUNC|O_RDWR, 0600,NULL);
+	if (!tdb)
+		err(1, "Opening %s", argv[1]);
+
+	for (i = 0; i < num_recs; i++) {
+		TDB_DATA d;
+
+		d.dptr = (void *)&i;
+		d.dsize = sizeof(i);
+		if (tdb_store(tdb, d, d, TDB_INSERT) != 0)
+			err(1, "Failed to store record %i", i);
+	}
+	printf("Done\n");
+	return 0;
+}

+ 813 - 0
ccan/tdb2/tools/tdbtool.c

@@ -0,0 +1,813 @@
+/* 
+   Unix SMB/CIFS implementation.
+   Samba database functions
+   Copyright (C) Andrew Tridgell              1999-2000
+   Copyright (C) Paul `Rusty' Russell		   2000
+   Copyright (C) Jeremy Allison			   2000
+   Copyright (C) Andrew Esh                        2001
+
+   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 <ccan/tdb2/tdb2.h>
+#include <ccan/hash/hash.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+
+static int do_command(void);
+const char *cmdname;
+char *arg1, *arg2;
+size_t arg1len, arg2len;
+int bIterate = 0;
+char *line;
+TDB_DATA iterate_kbuf;
+char cmdline[1024];
+static int disable_mmap;
+
+enum commands {
+	CMD_CREATE_TDB,
+	CMD_OPEN_TDB,
+	CMD_OPENJH_TDB,
+#if 0
+	CMD_TRANSACTION_START,
+	CMD_TRANSACTION_COMMIT,
+	CMD_TRANSACTION_CANCEL,
+#endif
+	CMD_ERASE,
+	CMD_DUMP,
+	CMD_INSERT,
+	CMD_MOVE,
+	CMD_STORE,
+	CMD_SHOW,
+	CMD_KEYS,
+	CMD_HEXKEYS,
+	CMD_DELETE,
+#if 0
+	CMD_LIST_HASH_FREE,
+	CMD_LIST_FREE,
+#endif
+	CMD_INFO,
+	CMD_MMAP,
+	CMD_SPEED,
+	CMD_FIRST,
+	CMD_NEXT,
+	CMD_SYSTEM,
+	CMD_CHECK,
+	CMD_QUIT,
+	CMD_HELP
+};
+
+typedef struct {
+	const char *name;
+	enum commands cmd;
+} COMMAND_TABLE;
+
+COMMAND_TABLE cmd_table[] = {
+	{"create",	CMD_CREATE_TDB},
+	{"open",	CMD_OPEN_TDB},
+	{"openjh",	CMD_OPENJH_TDB},
+#if 0
+	{"transaction_start",	CMD_TRANSACTION_START},
+	{"transaction_commit",	CMD_TRANSACTION_COMMIT},
+	{"transaction_cancel",	CMD_TRANSACTION_CANCEL},
+#endif
+	{"erase",	CMD_ERASE},
+	{"dump",	CMD_DUMP},
+	{"insert",	CMD_INSERT},
+	{"move",	CMD_MOVE},
+	{"store",	CMD_STORE},
+	{"show",	CMD_SHOW},
+	{"keys",	CMD_KEYS},
+	{"hexkeys",	CMD_HEXKEYS},
+	{"delete",	CMD_DELETE},
+#if 0
+	{"list",	CMD_LIST_HASH_FREE},
+	{"free",	CMD_LIST_FREE},
+#endif
+	{"info",	CMD_INFO},
+	{"speed",	CMD_SPEED},
+	{"mmap",	CMD_MMAP},
+	{"first",	CMD_FIRST},
+	{"1",		CMD_FIRST},
+	{"next",	CMD_NEXT},
+	{"n",		CMD_NEXT},
+	{"check",	CMD_CHECK},
+	{"quit",	CMD_QUIT},
+	{"q",		CMD_QUIT},
+	{"!",		CMD_SYSTEM},
+	{NULL,		CMD_HELP}
+};
+
+struct timeval tp1,tp2;
+
+static void _start_timer(void)
+{
+	gettimeofday(&tp1,NULL);
+}
+
+static double _end_timer(void)
+{
+	gettimeofday(&tp2,NULL);
+	return((tp2.tv_sec - tp1.tv_sec) + 
+	       (tp2.tv_usec - tp1.tv_usec)*1.0e-6);
+}
+
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, void *priv, const char *format, ...)
+{
+	va_list ap;
+    
+	va_start(ap, format);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+}
+
+/* a tdb tool for manipulating a tdb database */
+
+static struct tdb_context *tdb;
+
+static int print_rec(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+static int print_key(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+static int print_hexkey(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state);
+
+static void print_asc(const char *buf,int len)
+{
+	int i;
+
+	/* We're probably printing ASCII strings so don't try to display
+	   the trailing NULL character. */
+
+	if (buf[len - 1] == 0)
+	        len--;
+
+	for (i=0;i<len;i++)
+		printf("%c",isprint(buf[i])?buf[i]:'.');
+}
+
+static void print_data(const char *buf,int len)
+{
+	int i=0;
+	if (len<=0) return;
+	printf("[%03X] ",i);
+	for (i=0;i<len;) {
+		printf("%02X ",(int)((unsigned char)buf[i]));
+		i++;
+		if (i%8 == 0) printf(" ");
+		if (i%16 == 0) {      
+			print_asc(&buf[i-16],8); printf(" ");
+			print_asc(&buf[i-8],8); printf("\n");
+			if (i<len) printf("[%03X] ",i);
+		}
+	}
+	if (i%16) {
+		int n;
+		
+		n = 16 - (i%16);
+		printf(" ");
+		if (n>8) printf(" ");
+		while (n--) printf("   ");
+		
+		n = i%16;
+		if (n > 8) n = 8;
+		print_asc(&buf[i-(i%16)],n); printf(" ");
+		n = (i%16) - n;
+		if (n>0) print_asc(&buf[i-n],n); 
+		printf("\n");    
+	}
+}
+
+static void help(void)
+{
+	printf("\n"
+"tdbtool: \n"
+"  create    dbname     : create a database\n"
+"  open      dbname     : open an existing database\n"
+"  openjh    dbname     : open an existing database (jenkins hash)\n"
+"  transaction_start    : start a transaction\n"
+"  transaction_commit   : commit a transaction\n"
+"  transaction_cancel   : cancel a transaction\n"
+"  erase                : erase the database\n"
+"  dump                 : dump the database as strings\n"
+"  keys                 : dump the database keys as strings\n"
+"  hexkeys              : dump the database keys as hex values\n"
+"  info                 : print summary info about the database\n"
+"  insert    key  data  : insert a record\n"
+"  move      key  file  : move a record to a destination tdb\n"
+"  store     key  data  : store a record (replace)\n"
+"  show      key        : show a record by key\n"
+"  delete    key        : delete a record by key\n"
+"  list                 : print the database hash table and freelist\n"
+"  free                 : print the database freelist\n"
+"  check                : check the integrity of an opened database\n"
+"  speed                : perform speed tests on the database\n"
+"  ! command            : execute system command\n"
+"  1 | first            : print the first record\n"
+"  n | next             : print the next record\n"
+"  q | quit             : terminate\n"
+"  \\n                   : repeat 'next' command\n"
+"\n");
+}
+
+static void terror(const char *why)
+{
+	printf("%s\n", why);
+}
+
+static void create_tdb(const char *tdbname)
+{
+	union tdb_attribute log_attr;
+	log_attr.base.attr = TDB_ATTRIBUTE_LOG;
+	log_attr.base.next = NULL;
+	log_attr.log.log_fn = tdb_log;
+
+	if (tdb) tdb_close(tdb);
+	tdb = tdb_open(tdbname, (disable_mmap?TDB_NOMMAP:0),
+		       O_RDWR | O_CREAT | O_TRUNC, 0600, &log_attr);
+	if (!tdb) {
+		printf("Could not create %s: %s\n", tdbname, strerror(errno));
+	}
+}
+
+static uint64_t jenkins_hash(const void *key, size_t len, uint64_t seed,
+			     void *priv)
+{
+	return hash_any(key, len, seed);
+}
+
+static void open_tdb(const char *tdbname, tdb_hashfn_t hashfn)
+{
+	union tdb_attribute log_attr, hash_attr;
+	log_attr.base.attr = TDB_ATTRIBUTE_LOG;
+	log_attr.base.next = NULL;
+	log_attr.log.log_fn = tdb_log;
+
+	hash_attr.base.attr = TDB_ATTRIBUTE_HASH;
+	hash_attr.base.next = &log_attr;
+	hash_attr.hash.hash_fn = hashfn;
+
+	if (tdb) tdb_close(tdb);
+	tdb = tdb_open(tdbname, disable_mmap?TDB_NOMMAP:0, O_RDWR, 0600,
+		       hashfn ? &hash_attr : &log_attr);
+	if (!tdb) {
+		printf("Could not open %s: %s\n", tdbname, strerror(errno));
+	}
+}
+
+static void insert_tdb(char *keyname, size_t keylen, char* data, size_t datalen)
+{
+	TDB_DATA key, dbuf;
+
+	if ((keyname == NULL) || (keylen == 0)) {
+		terror("need key");
+		return;
+	}
+
+	key.dptr = (unsigned char *)keyname;
+	key.dsize = keylen;
+	dbuf.dptr = (unsigned char *)data;
+	dbuf.dsize = datalen;
+
+	if (tdb_store(tdb, key, dbuf, TDB_INSERT) == -1) {
+		terror("insert failed");
+	}
+}
+
+static void store_tdb(char *keyname, size_t keylen, char* data, size_t datalen)
+{
+	TDB_DATA key, dbuf;
+
+	if ((keyname == NULL) || (keylen == 0)) {
+		terror("need key");
+		return;
+	}
+
+	if ((data == NULL) || (datalen == 0)) {
+		terror("need data");
+		return;
+	}
+
+	key.dptr = (unsigned char *)keyname;
+	key.dsize = keylen;
+	dbuf.dptr = (unsigned char *)data;
+	dbuf.dsize = datalen;
+
+	printf("Storing key:\n");
+	print_rec(tdb, key, dbuf, NULL);
+
+	if (tdb_store(tdb, key, dbuf, TDB_REPLACE) == -1) {
+		terror("store failed");
+	}
+}
+
+static void show_tdb(char *keyname, size_t keylen)
+{
+	TDB_DATA key, dbuf;
+
+	if ((keyname == NULL) || (keylen == 0)) {
+		terror("need key");
+		return;
+	}
+
+	key.dptr = (unsigned char *)keyname;
+	key.dsize = keylen;
+
+	dbuf = tdb_fetch(tdb, key);
+	if (!dbuf.dptr) {
+	    terror("fetch failed");
+	    return;
+	}
+	
+	print_rec(tdb, key, dbuf, NULL);
+	
+	free( dbuf.dptr );
+	
+	return;
+}
+
+static void delete_tdb(char *keyname, size_t keylen)
+{
+	TDB_DATA key;
+
+	if ((keyname == NULL) || (keylen == 0)) {
+		terror("need key");
+		return;
+	}
+
+	key.dptr = (unsigned char *)keyname;
+	key.dsize = keylen;
+
+	if (tdb_delete(tdb, key) != 0) {
+		terror("delete failed");
+	}
+}
+
+static void move_rec(char *keyname, size_t keylen, char* tdbname)
+{
+	TDB_DATA key, dbuf;
+	struct tdb_context *dst_tdb;
+
+	if ((keyname == NULL) || (keylen == 0)) {
+		terror("need key");
+		return;
+	}
+
+	if ( !tdbname ) {
+		terror("need destination tdb name");
+		return;
+	}
+
+	key.dptr = (unsigned char *)keyname;
+	key.dsize = keylen;
+
+	dbuf = tdb_fetch(tdb, key);
+	if (!dbuf.dptr) {
+		terror("fetch failed");
+		return;
+	}
+	
+	print_rec(tdb, key, dbuf, NULL);
+	
+	dst_tdb = tdb_open(tdbname, 0, O_RDWR, 0600, NULL);
+	if ( !dst_tdb ) {
+		terror("unable to open destination tdb");
+		return;
+	}
+	
+	if ( tdb_store( dst_tdb, key, dbuf, TDB_REPLACE ) == -1 ) {
+		terror("failed to move record");
+	}
+	else
+		printf("record moved\n");
+	
+	tdb_close( dst_tdb );
+	
+	return;
+}
+
+static int print_rec(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+	printf("\nkey %d bytes\n", (int)key.dsize);
+	print_asc((const char *)key.dptr, key.dsize);
+	printf("\ndata %d bytes\n", (int)dbuf.dsize);
+	print_data((const char *)dbuf.dptr, dbuf.dsize);
+	return 0;
+}
+
+static int print_key(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+	printf("key %d bytes: ", (int)key.dsize);
+	print_asc((const char *)key.dptr, key.dsize);
+	printf("\n");
+	return 0;
+}
+
+static int print_hexkey(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+	printf("key %d bytes\n", (int)key.dsize);
+	print_data((const char *)key.dptr, key.dsize);
+	printf("\n");
+	return 0;
+}
+
+static int total_bytes;
+
+static int traverse_fn(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
+{
+	total_bytes += dbuf.dsize;
+	return 0;
+}
+
+static void info_tdb(void)
+{
+	char *summary = tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS);
+
+	if (!summary) {
+		printf("Error = %s\n", tdb_errorstr(tdb));
+	} else {
+		printf("%s", summary);
+		free(summary);
+	}
+}
+
+static void speed_tdb(const char *tlimit)
+{
+	unsigned timelimit = tlimit?atoi(tlimit):0;
+	double t;
+	int ops;
+	if (timelimit == 0) timelimit = 5;
+
+	ops = 0;
+	printf("Testing store speed for %u seconds\n", timelimit);
+	_start_timer();
+	do {
+		long int r = random();
+		TDB_DATA key, dbuf;
+		key.dptr = (unsigned char *)"store test";
+		key.dsize = strlen((char *)key.dptr);
+		dbuf.dptr = (unsigned char *)&r;
+		dbuf.dsize = sizeof(r);
+		tdb_store(tdb, key, dbuf, TDB_REPLACE);
+		t = _end_timer();
+		ops++;
+	} while (t < timelimit);
+	printf("%10.3f ops/sec\n", ops/t);
+
+	ops = 0;
+	printf("Testing fetch speed for %u seconds\n", timelimit);
+	_start_timer();
+	do {
+		long int r = random();
+		TDB_DATA key, dbuf;
+		key.dptr = (unsigned char *)"store test";
+		key.dsize = strlen((char *)key.dptr);
+		dbuf.dptr = (unsigned char *)&r;
+		dbuf.dsize = sizeof(r);
+		tdb_fetch(tdb, key);
+		t = _end_timer();
+		ops++;
+	} while (t < timelimit);
+	printf("%10.3f ops/sec\n", ops/t);
+
+#if 0 /* FIXME */
+	ops = 0;
+	printf("Testing transaction speed for %u seconds\n", timelimit);
+	_start_timer();
+	do {
+		long int r = random();
+		TDB_DATA key, dbuf;
+		key.dptr = (unsigned char *)"transaction test";
+		key.dsize = strlen((char *)key.dptr);
+		dbuf.dptr = (unsigned char *)&r;
+		dbuf.dsize = sizeof(r);
+		tdb_transaction_start(tdb);
+		tdb_store(tdb, key, dbuf, TDB_REPLACE);
+		tdb_transaction_commit(tdb);
+		t = _end_timer();
+		ops++;
+	} while (t < timelimit);
+	printf("%10.3f ops/sec\n", ops/t);
+#endif
+
+	ops = 0;
+	printf("Testing traverse speed for %u seconds\n", timelimit);
+	_start_timer();
+	do {
+		tdb_traverse(tdb, traverse_fn, NULL);
+		t = _end_timer();
+		ops++;
+	} while (t < timelimit);
+	printf("%10.3f ops/sec\n", ops/t);
+}
+
+static void toggle_mmap(void)
+{
+	disable_mmap = !disable_mmap;
+	if (disable_mmap) {
+		printf("mmap is disabled\n");
+	} else {
+		printf("mmap is enabled\n");
+	}
+}
+
+static char *tdb_getline(const char *prompt)
+{
+	static char thisline[1024];
+	char *p;
+	fputs(prompt, stdout);
+	thisline[0] = 0;
+	p = fgets(thisline, sizeof(thisline)-1, stdin);
+	if (p) p = strchr(p, '\n');
+	if (p) *p = 0;
+	return p?thisline:NULL;
+}
+
+static int do_delete_fn(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf,
+                     void *state)
+{
+    return tdb_delete(the_tdb, key);
+}
+
+static void first_record(struct tdb_context *the_tdb, TDB_DATA *pkey)
+{
+	TDB_DATA dbuf;
+	*pkey = tdb_firstkey(the_tdb);
+	
+	dbuf = tdb_fetch(the_tdb, *pkey);
+	if (!dbuf.dptr) terror("fetch failed");
+	else {
+		print_rec(the_tdb, *pkey, dbuf, NULL);
+	}
+}
+
+static void next_record(struct tdb_context *the_tdb, TDB_DATA *pkey)
+{
+	TDB_DATA dbuf;
+	*pkey = tdb_nextkey(the_tdb, *pkey);
+	
+	dbuf = tdb_fetch(the_tdb, *pkey);
+	if (!dbuf.dptr) 
+		terror("fetch failed");
+	else
+		print_rec(the_tdb, *pkey, dbuf, NULL);
+}
+
+static void check_db(struct tdb_context *the_tdb)
+{
+	if (!the_tdb) {
+		printf("Error: No database opened!\n");
+	} else {
+		if (tdb_check(the_tdb, NULL, NULL) == -1)
+			printf("Integrity check for the opened database failed.\n");
+		else
+			printf("Database integrity is OK.\n");
+	}
+}
+
+static int do_command(void)
+{
+	COMMAND_TABLE *ctp = cmd_table;
+	enum commands mycmd = CMD_HELP;
+	int cmd_len;
+
+	if (cmdname && strlen(cmdname) == 0) {
+		mycmd = CMD_NEXT;
+	} else {
+		while (ctp->name) {
+			cmd_len = strlen(ctp->name);
+			if (strncmp(ctp->name,cmdname,cmd_len) == 0) {
+				mycmd = ctp->cmd;
+				break;
+			}
+			ctp++;
+		}
+	}
+
+	switch (mycmd) {
+	case CMD_CREATE_TDB:
+		bIterate = 0;
+		create_tdb(arg1);
+		return 0;
+	case CMD_OPEN_TDB:
+		bIterate = 0;
+		open_tdb(arg1, NULL);
+		return 0;
+	case CMD_OPENJH_TDB:
+		bIterate = 0;
+		open_tdb(arg1, jenkins_hash);
+		return 0;
+	case CMD_SYSTEM:
+		/* Shell command */
+		if (system(arg1) == -1) {
+			terror("system() call failed\n");
+		}
+		return 0;
+	case CMD_QUIT:
+		return 1;
+	default:
+		/* all the rest require a open database */
+		if (!tdb) {
+			bIterate = 0;
+			terror("database not open");
+			help();
+			return 0;
+		}
+		switch (mycmd) {
+#if 0
+		case CMD_TRANSACTION_START:
+			bIterate = 0;
+			tdb_transaction_start(tdb);
+			return 0;
+		case CMD_TRANSACTION_COMMIT:
+			bIterate = 0;
+			tdb_transaction_commit(tdb);
+			return 0;
+		case CMD_TRANSACTION_CANCEL:
+			bIterate = 0;
+			tdb_transaction_cancel(tdb);
+			return 0;
+#endif
+		case CMD_ERASE:
+			bIterate = 0;
+			tdb_traverse(tdb, do_delete_fn, NULL);
+			return 0;
+		case CMD_DUMP:
+			bIterate = 0;
+			tdb_traverse(tdb, print_rec, NULL);
+			return 0;
+		case CMD_INSERT:
+			bIterate = 0;
+			insert_tdb(arg1, arg1len,arg2,arg2len);
+			return 0;
+		case CMD_MOVE:
+			bIterate = 0;
+			move_rec(arg1,arg1len,arg2);
+			return 0;
+		case CMD_STORE:
+			bIterate = 0;
+			store_tdb(arg1,arg1len,arg2,arg2len);
+			return 0;
+		case CMD_SHOW:
+			bIterate = 0;
+			show_tdb(arg1, arg1len);
+			return 0;
+		case CMD_KEYS:
+			tdb_traverse(tdb, print_key, NULL);
+			return 0;
+		case CMD_HEXKEYS:
+			tdb_traverse(tdb, print_hexkey, NULL);
+			return 0;
+		case CMD_DELETE:
+			bIterate = 0;
+			delete_tdb(arg1,arg1len);
+			return 0;
+#if 0
+		case CMD_LIST_HASH_FREE:
+			tdb_dump_all(tdb);
+			return 0;
+		case CMD_LIST_FREE:
+			tdb_printfreelist(tdb);
+			return 0;
+#endif
+		case CMD_INFO:
+			info_tdb();
+			return 0;
+		case CMD_SPEED:
+			speed_tdb(arg1);
+			return 0;
+		case CMD_MMAP:
+			toggle_mmap();
+			return 0;
+		case CMD_FIRST:
+			bIterate = 1;
+			first_record(tdb, &iterate_kbuf);
+			return 0;
+		case CMD_NEXT:
+			if (bIterate)
+				next_record(tdb, &iterate_kbuf);
+			return 0;
+		case CMD_CHECK:
+			check_db(tdb);
+			return 0;
+		case CMD_HELP:
+			help();
+			return 0;
+		case CMD_CREATE_TDB:
+		case CMD_OPEN_TDB:
+		case CMD_OPENJH_TDB:
+		case CMD_SYSTEM:
+		case CMD_QUIT:
+			/*
+			 * unhandled commands.  cases included here to avoid compiler
+			 * warnings.
+			 */
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+static char *convert_string(char *instring, size_t *sizep)
+{
+	size_t length = 0;
+	char *outp, *inp;
+	char temp[3];
+
+	outp = inp = instring;
+
+	while (*inp) {
+		if (*inp == '\\') {
+			inp++;
+			if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) {
+				temp[0] = *inp++;
+				temp[1] = '\0';
+				if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) {
+					temp[1] = *inp++;
+					temp[2] = '\0';
+				}
+				*outp++ = (char)strtol((const char *)temp,NULL,16);
+			} else {
+				*outp++ = *inp++;
+			}
+		} else {
+			*outp++ = *inp++;
+		}
+		length++;
+	}
+	*sizep = length;
+	return instring;
+}
+
+int main(int argc, char *argv[])
+{
+	cmdname = "";
+	arg1 = NULL;
+	arg1len = 0;
+	arg2 = NULL;
+	arg2len = 0;
+
+	if (argv[1]) {
+		cmdname = "open";
+		arg1 = argv[1];
+		do_command();
+		cmdname =  "";
+		arg1 = NULL;
+	}
+
+	switch (argc) {
+	case 1:
+	case 2:
+		/* Interactive mode */
+		while ((cmdname = tdb_getline("tdb> "))) {
+			arg2 = arg1 = NULL;
+			if ((arg1 = strchr((const char *)cmdname,' ')) != NULL) {
+				arg1++;
+				arg2 = arg1;
+				while (*arg2) {
+					if (*arg2 == ' ') {
+						*arg2++ = '\0';
+						break;
+					}
+					if ((*arg2++ == '\\') && (*arg2 == ' ')) {
+						arg2++;
+					}
+				}
+			}
+			if (arg1) arg1 = convert_string(arg1,&arg1len);
+			if (arg2) arg2 = convert_string(arg2,&arg2len);
+			if (do_command()) break;
+		}
+		break;
+	case 5:
+		arg2 = convert_string(argv[4],&arg2len);
+	case 4:
+		arg1 = convert_string(argv[3],&arg1len);
+	case 3:
+		cmdname = argv[2];
+	default:
+		do_command();
+		break;
+	}
+
+	if (tdb) tdb_close(tdb);
+
+	return 0;
+}

+ 496 - 0
ccan/tdb2/tools/tdbtorture.c

@@ -0,0 +1,496 @@
+/* this tests tdb by doing lots of ops from several simultaneous
+   writers - that stresses the locking code. 
+*/
+
+#include <ccan/tdb2/tdb2.h>
+#include <stdlib.h>
+#include <err.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/wait.h>
+
+//#define REOPEN_PROB 30
+#define DELETE_PROB 8
+#define STORE_PROB 4
+#define APPEND_PROB 6
+//#define TRANSACTION_PROB 10
+//#define TRANSACTION_PREPARE_PROB 2
+#define LOCKSTORE_PROB 5
+#define TRAVERSE_PROB 20
+#define TRAVERSE_READ_PROB 20
+#define TRAVERSE_MOD_PROB 100
+#define TRAVERSE_ABORT_PROB 500
+#define CULL_PROB 100
+#define KEYLEN 3
+#define DATALEN 100
+
+static struct tdb_context *db;
+static int in_transaction;
+static int in_traverse;
+static int error_count;
+#if TRANSACTION_PROB
+static int always_transaction = 0;
+#endif
+static int loopnum;
+static int count_pipe;
+static union tdb_attribute log_attr;
+
+#ifdef PRINTF_ATTRIBUTE
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, void *private, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
+#endif
+static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, void *private, const char *format, ...)
+{
+	va_list ap;
+
+	if (level != TDB_DEBUG_TRACE)
+		error_count++;
+
+	va_start(ap, format);
+	vfprintf(stdout, format, ap);
+	va_end(ap);
+	fflush(stdout);
+#if 0
+	if (level != TDB_DEBUG_TRACE) {
+		char *ptr;
+		signal(SIGUSR1, SIG_IGN);
+		asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
+		system(ptr);
+		free(ptr);
+	}
+#endif	
+}
+
+#include "../private.h"
+
+static void segv_handler(int signal, siginfo_t *info, void *p)
+{
+	char string[100];
+
+	sprintf(string, "%u: death at %p (map_ptr %p, map_size %llu)\n",
+		getpid(), info->si_addr, db->map_ptr, db->map_size);
+	write(2, string, strlen(string));
+	sleep(60);
+	_exit(11);
+}	
+
+static void fatal(const char *why)
+{
+	perror(why);
+	error_count++;
+}
+
+static char *randbuf(int len)
+{
+	char *buf;
+	int i;
+	buf = (char *)malloc(len+1);
+
+	for (i=0;i<len;i++) {
+		buf[i] = 'a' + (rand() % 26);
+	}
+	buf[i] = 0;
+	return buf;
+}
+
+static void addrec_db(void);
+static int modify_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
+			   void *state)
+{
+#if CULL_PROB
+	if (random() % CULL_PROB == 0) {
+		tdb_delete(tdb, key);
+	}
+#endif
+
+#if TRAVERSE_MOD_PROB
+	if (random() % TRAVERSE_MOD_PROB == 0) {
+		addrec_db();
+	}
+#endif
+
+#if TRAVERSE_ABORT_PROB
+	if (random() % TRAVERSE_ABORT_PROB == 0)
+		return 1;
+#endif
+
+	return 0;
+}
+
+static void addrec_db(void)
+{
+	int klen, dlen;
+	char *k, *d;
+	TDB_DATA key, data;
+
+	klen = 1 + (rand() % KEYLEN);
+	dlen = 1 + (rand() % DATALEN);
+
+	k = randbuf(klen);
+	d = randbuf(dlen);
+
+	key.dptr = (unsigned char *)k;
+	key.dsize = klen+1;
+
+	data.dptr = (unsigned char *)d;
+	data.dsize = dlen+1;
+
+#if REOPEN_PROB
+	if (in_traverse == 0 && in_transaction == 0 && random() % REOPEN_PROB == 0) {
+		tdb_reopen_all(0);
+		goto next;
+	} 
+#endif
+
+#if TRANSACTION_PROB
+	if (in_traverse == 0 && in_transaction == 0 && (always_transaction || random() % TRANSACTION_PROB == 0)) {
+		if (tdb_transaction_start(db) != 0) {
+			fatal("tdb_transaction_start failed");
+		}
+		in_transaction++;
+		goto next;
+	}
+	if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
+		if (random() % TRANSACTION_PREPARE_PROB == 0) {
+			if (tdb_transaction_prepare_commit(db) != 0) {
+				fatal("tdb_transaction_prepare_commit failed");
+			}
+		}
+		if (tdb_transaction_commit(db) != 0) {
+			fatal("tdb_transaction_commit failed");
+		}
+		in_transaction--;
+		goto next;
+	}
+
+	if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
+		if (tdb_transaction_cancel(db) != 0) {
+			fatal("tdb_transaction_cancel failed");
+		}
+		in_transaction--;
+		goto next;
+	}
+#endif
+
+#if DELETE_PROB
+	if (random() % DELETE_PROB == 0) {
+		tdb_delete(db, key);
+		goto next;
+	}
+#endif
+
+#if STORE_PROB
+	if (random() % STORE_PROB == 0) {
+		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+			fatal("tdb_store failed");
+		}
+		goto next;
+	}
+#endif
+
+#if APPEND_PROB
+	if (random() % APPEND_PROB == 0) {
+		if (tdb_append(db, key, data) != 0) {
+			fatal("tdb_append failed");
+		}
+		goto next;
+	}
+#endif
+
+#if LOCKSTORE_PROB
+	if (random() % LOCKSTORE_PROB == 0) {
+		tdb_chainlock(db, key);
+		data = tdb_fetch(db, key);
+		if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
+			fatal("tdb_store failed");
+		}
+		if (data.dptr) free(data.dptr);
+		tdb_chainunlock(db, key);
+		goto next;
+	} 
+#endif
+
+#if TRAVERSE_PROB
+	/* FIXME: recursive traverses break transactions? */
+	if (in_traverse == 0 && random() % TRAVERSE_PROB == 0) {
+		in_traverse++;
+		tdb_traverse(db, modify_traverse, NULL);
+		in_traverse--;
+		goto next;
+	}
+#endif
+
+#if TRAVERSE_READ_PROB
+	if (in_traverse == 0 && random() % TRAVERSE_READ_PROB == 0) {
+		in_traverse++;
+		tdb_traverse_read(db, NULL, NULL);
+		in_traverse--;
+		goto next;
+	}
+#endif
+
+	data = tdb_fetch(db, key);
+	if (data.dptr) free(data.dptr);
+
+next:
+	free(k);
+	free(d);
+}
+
+static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
+                       void *state)
+{
+	tdb_delete(tdb, key);
+	return 0;
+}
+
+static void usage(void)
+{
+	printf("Usage: tdbtorture"
+#if TRANSACTION_PROB
+	       " [-t]"
+#endif
+	       " [-k] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED]\n");
+	exit(0);
+}
+
+static void send_count_and_suicide(int sig)
+{
+	/* This ensures our successor can continue where we left off. */
+	write(count_pipe, &loopnum, sizeof(loopnum));
+	/* This gives a unique signature. */
+	kill(getpid(), SIGUSR2);
+}
+
+static int run_child(int i, int seed, unsigned num_loops, unsigned start)
+{
+	struct sigaction act = { .sa_sigaction = segv_handler,
+				 .sa_flags = SA_SIGINFO };
+	sigaction(11, &act, NULL);	
+
+	db = tdb_open("torture.tdb", TDB_DEFAULT, O_RDWR | O_CREAT, 0600,
+		      &log_attr);
+	if (!db) {
+		fatal("db open failed");
+	}
+
+#if 0
+	if (i == 0) {
+		printf("pid %i\n", getpid());
+		sleep(9);
+	} else
+		sleep(10);
+#endif
+
+	srand(seed + i);
+	srandom(seed + i);
+
+	/* Set global, then we're ready to handle being killed. */
+	loopnum = start;
+	signal(SIGUSR1, send_count_and_suicide);
+
+	for (;loopnum<num_loops && error_count == 0;loopnum++) {
+		addrec_db();
+	}
+
+	if (error_count == 0) {
+		tdb_traverse_read(db, NULL, NULL);
+#if TRANSACTION_PROB
+		if (always_transaction) {
+			while (in_transaction) {
+				tdb_transaction_cancel(db);
+				in_transaction--;
+			}
+			if (tdb_transaction_start(db) != 0)
+				fatal("tdb_transaction_start failed");
+		}
+#endif
+		tdb_traverse(db, traverse_fn, NULL);
+		tdb_traverse(db, traverse_fn, NULL);
+
+#if TRANSACTION_PROB
+		if (always_transaction) {
+			if (tdb_transaction_commit(db) != 0)
+				fatal("tdb_transaction_commit failed");
+		}
+#endif
+	}
+
+	tdb_close(db);
+
+	return (error_count < 100 ? error_count : 100);
+}
+
+int main(int argc, char * const *argv)
+{
+	int i, seed = -1;
+	int num_loops = 5000;
+	int num_procs = 3;
+	int c, pfds[2];
+	extern char *optarg;
+	pid_t *pids;
+	int kill_random = 0;
+	int *done;
+
+	log_attr.log.log_fn = tdb_log;
+
+	while ((c = getopt(argc, argv, "n:l:s:thk")) != -1) {
+		switch (c) {
+		case 'n':
+			num_procs = strtol(optarg, NULL, 0);
+			break;
+		case 'l':
+			num_loops = strtol(optarg, NULL, 0);
+			break;
+		case 's':
+			seed = strtol(optarg, NULL, 0);
+			break;
+		case 't':
+#if TRANSACTION_PROB
+			always_transaction = 1;
+#else
+			fprintf(stderr, "Transactions not supported\n");
+			usage();
+#endif
+			break;
+		case 'k':
+			kill_random = 1;
+			break;
+		default:
+			usage();
+		}
+	}
+
+	unlink("torture.tdb");
+
+	if (seed == -1) {
+		seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
+	}
+
+	if (num_procs == 1 && !kill_random) {
+		/* Don't fork for this case, makes debugging easier. */
+		error_count = run_child(0, seed, num_loops, 0);
+		goto done;
+	}
+
+	pids = (pid_t *)calloc(sizeof(pid_t), num_procs);
+	done = (int *)calloc(sizeof(int), num_procs);
+
+	if (pipe(pfds) != 0) {
+		perror("Creating pipe");
+		exit(1);
+	}
+	count_pipe = pfds[1];
+
+	for (i=0;i<num_procs;i++) {
+		if ((pids[i]=fork()) == 0) {
+			close(pfds[0]);
+			if (i == 0) {
+				printf("testing with %d processes, %d loops, seed=%d%s\n", 
+				       num_procs, num_loops, seed, 
+#if TRANSACTION_PROB
+				       always_transaction ? " (all within transactions)" : ""
+#else
+				       ""
+#endif
+					);
+			}
+			exit(run_child(i, seed, num_loops, 0));
+		}
+	}
+
+	while (num_procs) {
+		int status, j;
+		pid_t pid;
+
+		if (error_count != 0) {
+			/* try and stop the test on any failure */
+			for (j=0;j<num_procs;j++) {
+				if (pids[j] != 0) {
+					kill(pids[j], SIGTERM);
+				}
+			}
+		}
+
+		pid = waitpid(-1, &status, kill_random ? WNOHANG : 0);
+		if (pid == 0) {
+			struct timespec ts;
+
+			/* Sleep for 1/10 second. */
+			ts.tv_sec = 0;
+			ts.tv_nsec = 100000000;
+			nanosleep(&ts, NULL);
+
+			/* Kill someone. */
+			kill(pids[random() % num_procs], SIGUSR1);
+			continue;
+		}
+
+		if (pid == -1) {
+			perror("failed to wait for child\n");
+			exit(1);
+		}
+
+		for (j=0;j<num_procs;j++) {
+			if (pids[j] == pid) break;
+		}
+		if (j == num_procs) {
+			printf("unknown child %d exited!?\n", (int)pid);
+			exit(1);
+		}
+		if (WIFSIGNALED(status)) {
+			if (WTERMSIG(status) == SIGUSR2
+			    || WTERMSIG(status) == SIGUSR1) {
+				/* SIGUSR2 means they wrote to pipe. */
+				if (WTERMSIG(status) == SIGUSR2) {
+					read(pfds[0], &done[j],
+					     sizeof(done[j]));
+				}
+				pids[j] = fork();
+				if (pids[j] == 0)
+					exit(run_child(j, seed, num_loops,
+						       done[j]));
+				printf("Restarting child %i for %u-%u\n",
+				       j, done[j], num_loops);
+				continue;
+			}
+			printf("child %d exited with signal %d\n",
+			       (int)pid, WTERMSIG(status));
+			error_count++;
+		} else {
+			if (WEXITSTATUS(status) != 0) {
+				printf("child %d exited with status %d\n",
+				       (int)pid, WEXITSTATUS(status));
+				error_count++;
+			}
+		}
+		memmove(&pids[j], &pids[j+1],
+			(num_procs - j - 1)*sizeof(pids[0]));
+		num_procs--;
+	}
+
+	free(pids);
+
+done:
+	if (error_count == 0) {
+		db = tdb_open("torture.tdb", TDB_DEFAULT, O_RDWR | O_CREAT,
+			      0600, &log_attr);
+		if (!db) {
+			fatal("db open failed");
+		}
+		if (tdb_check(db, NULL, NULL) == -1) {
+			printf("db check failed");
+			exit(1);
+		}
+		tdb_close(db);
+		printf("OK\n");
+	}
+
+	return error_count;
+}