diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:46:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:46:09 +0000 |
commit | 48ae1a0fb55b89de931512ffe4afd2c629ae828a (patch) | |
tree | a464b4d09295e0d1cdc290e42dc8cc97c4e044fe /tools | |
parent | Initial commit. (diff) | |
download | tdb-48ae1a0fb55b89de931512ffe4afd2c629ae828a.tar.xz tdb-48ae1a0fb55b89de931512ffe4afd2c629ae828a.zip |
Adding upstream version 1.4.8.upstream/1.4.8upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/tdbbackup.c | 370 | ||||
-rw-r--r-- | tools/tdbdump.c | 181 | ||||
-rw-r--r-- | tools/tdbrestore.c | 202 | ||||
-rw-r--r-- | tools/tdbtest.c | 290 | ||||
-rw-r--r-- | tools/tdbtool.c | 924 | ||||
-rw-r--r-- | tools/tdbtortseq.c | 123 | ||||
-rw-r--r-- | tools/tdbtorture.c | 500 |
7 files changed, 2590 insertions, 0 deletions
diff --git a/tools/tdbbackup.c b/tools/tdbbackup.c new file mode 100644 index 0000000..1125987 --- /dev/null +++ b/tools/tdbbackup.c @@ -0,0 +1,370 @@ +/* + Unix SMB/CIFS implementation. + low level tdb backup and restore utility + Copyright (C) Andrew Tridgell 2002 + + 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/>. +*/ + +/* + + This program is meant for backup/restore of tdb databases. Typical usage would be: + tdbbackup *.tdb + when Samba shuts down cleanly, which will make a backup of all the local databases + to *.bak files. Then on Samba startup you would use: + tdbbackup -v *.tdb + and this will check the databases for corruption and if corruption is detected then + the backup will be restored. + + You may also like to do a backup on a regular basis while Samba is + running, perhaps using cron. + + The reason this program is needed is to cope with power failures + while Samba is running. A power failure could lead to database + corruption and Samba will then not start correctly. + + Note that many of the databases in Samba are transient and thus + don't need to be backed up, so you can optimise the above a little + by only running the backup on the critical databases. + + */ + +#include "replace.h" +#include "system/locale.h" +#include "system/time.h" +#include "system/filesys.h" +#include "system/wait.h" +#include "tdb.h" + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +static int failed; + +static struct tdb_logging_context log_ctx; + +#ifdef PRINTF_ATTRIBUTE +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4); +#endif +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vfprintf(stdout, format, ap); + va_end(ap); + fflush(stdout); +} + +static char *add_suffix(const char *name, const char *suffix) +{ + char *ret; + int len = strlen(name) + strlen(suffix) + 1; + ret = (char *)malloc(len); + if (!ret) { + fprintf(stderr,"Out of memory!\n"); + exit(1); + } + snprintf(ret, len, "%s%s", name, suffix); + return ret; +} + +static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state) +{ + TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state; + + if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) { + fprintf(stderr,"Failed to insert into %s\n", tdb_name(tdb_new)); + failed = 1; + return 1; + } + return 0; +} + + +static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state) +{ + return 0; +} + +/* + carefully backup a tdb, validating the contents and + only doing the backup if its OK + this function is also used for restore +*/ +static int backup_tdb(const char *old_name, const char *new_name, + int hash_size, int nolock, bool readonly) +{ + TDB_CONTEXT *tdb; + TDB_CONTEXT *tdb_new; + char *tmp_name; + struct stat st; + int count1, count2; + + tmp_name = add_suffix(new_name, ".tmp"); + + /* stat the old tdb to find its permissions */ + if (stat(old_name, &st) != 0) { + perror(old_name); + free(tmp_name); + return 1; + } + + /* open the old tdb */ + tdb = tdb_open_ex(old_name, 0, + TDB_DEFAULT | (nolock ? TDB_NOLOCK : 0), + O_RDWR, 0, &log_ctx, NULL); + if (!tdb) { + printf("Failed to open %s\n", old_name); + free(tmp_name); + return 1; + } + + /* create the new tdb */ + unlink(tmp_name); + tdb_new = tdb_open_ex(tmp_name, + hash_size ? hash_size : tdb_hash_size(tdb), + TDB_DEFAULT, + O_RDWR|O_CREAT|O_EXCL, st.st_mode & 0777, + &log_ctx, NULL); + if (!tdb_new) { + perror(tmp_name); + free(tmp_name); + return 1; + } + + if (readonly) { + if (tdb_lockall_read(tdb) != 0) { + printf("Failed to obtain read only lock on old tdb\n"); + tdb_close(tdb); + tdb_close(tdb_new); + unlink(tmp_name); + free(tmp_name); + return 1; + } + } else if (tdb_transaction_start(tdb) != 0) { + printf("Failed to start transaction on db\n"); + tdb_close(tdb); + tdb_close(tdb_new); + unlink(tmp_name); + free(tmp_name); + return 1; + } + + /* lock the backup tdb so that nobody else can change it */ + if (tdb_lockall(tdb_new) != 0) { + printf("Failed to lock backup tdb\n"); + tdb_close(tdb); + tdb_close(tdb_new); + unlink(tmp_name); + free(tmp_name); + return 1; + } + + failed = 0; + + /* traverse and copy */ + if (readonly) { + count1 = tdb_traverse_read(tdb, + copy_fn, + (void *)tdb_new); + } else { + count1 = tdb_traverse(tdb, + copy_fn, + (void *)tdb_new); + } + if (count1 < 0 || failed) { + fprintf(stderr,"failed to copy %s\n", old_name); + tdb_close(tdb); + tdb_close(tdb_new); + unlink(tmp_name); + free(tmp_name); + return 1; + } + + /* close the old tdb */ + tdb_close(tdb); + + /* copy done, unlock the backup tdb */ + tdb_unlockall(tdb_new); + +#ifdef HAVE_FDATASYNC + if (fdatasync(tdb_fd(tdb_new)) != 0) { +#else + if (fsync(tdb_fd(tdb_new)) != 0) { +#endif + /* not fatal */ + fprintf(stderr, "failed to fsync backup file\n"); + } + + /* close the new tdb and re-open read-only */ + tdb_close(tdb_new); + tdb_new = tdb_open_ex(tmp_name, + 0, + TDB_DEFAULT, + O_RDONLY, 0, + &log_ctx, NULL); + if (!tdb_new) { + fprintf(stderr,"failed to reopen %s\n", tmp_name); + unlink(tmp_name); + perror(tmp_name); + free(tmp_name); + return 1; + } + + /* traverse the new tdb to confirm */ + count2 = tdb_traverse(tdb_new, test_fn, NULL); + if (count2 != count1) { + fprintf(stderr,"failed to copy %s\n", old_name); + tdb_close(tdb_new); + unlink(tmp_name); + free(tmp_name); + return 1; + } + + /* close the new tdb and rename it to .bak */ + tdb_close(tdb_new); + if (rename(tmp_name, new_name) != 0) { + perror(new_name); + free(tmp_name); + return 1; + } + + free(tmp_name); + + return 0; +} + +/* + verify a tdb and if it is corrupt then restore from *.bak +*/ +static int verify_tdb(const char *fname, const char *bak_name) +{ + TDB_CONTEXT *tdb; + int count = -1; + + /* open the tdb */ + tdb = tdb_open_ex(fname, 0, 0, + O_RDONLY, 0, &log_ctx, NULL); + + /* traverse the tdb, then close it */ + if (tdb) { + count = tdb_traverse(tdb, test_fn, NULL); + tdb_close(tdb); + } + + /* count is < 0 means an error */ + if (count < 0) { + printf("restoring %s\n", fname); + return backup_tdb(bak_name, fname, 0, 0, 0); + } + + printf("%s : %d records\n", fname, count); + + return 0; +} + +/* + see if one file is newer than another +*/ +static int file_newer(const char *fname1, const char *fname2) +{ + struct stat st1, st2; + if (stat(fname1, &st1) != 0) { + return 0; + } + if (stat(fname2, &st2) != 0) { + return 1; + } + return (st1.st_mtime > st2.st_mtime); +} + +static void usage(void) +{ + printf("Usage: tdbbackup [options] <fname...>\n\n"); + printf(" -h this help message\n"); + printf(" -s suffix set the backup suffix\n"); + printf(" -v verify mode (restore if corrupt)\n"); + printf(" -n hashsize set the new hash size for the backup\n"); + printf(" -l open without locking to back up mutex dbs\n"); + printf(" -r open with read only locking\n"); +} + + int main(int argc, char *argv[]) +{ + int i; + int ret = 0; + int c; + int verify = 0; + int hashsize = 0; + int nolock = 0; + bool readonly = false; + const char *suffix = ".bak"; + + log_ctx.log_fn = tdb_log; + + while ((c = getopt(argc, argv, "vhs:n:lr")) != -1) { + switch (c) { + case 'h': + usage(); + exit(0); + case 'v': + verify = 1; + break; + case 's': + suffix = optarg; + break; + case 'n': + hashsize = atoi(optarg); + break; + case 'l': + nolock = 1; + break; + case 'r': + readonly = true; + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); + exit(1); + } + + for (i=0; i<argc; i++) { + const char *fname = argv[i]; + char *bak_name; + + bak_name = add_suffix(fname, suffix); + + if (verify) { + if (verify_tdb(fname, bak_name) != 0) { + ret = 1; + } + } else { + if (file_newer(fname, bak_name) && + backup_tdb(fname, bak_name, hashsize, + nolock, readonly) != 0) { + ret = 1; + } + } + + free(bak_name); + } + + return ret; +} diff --git a/tools/tdbdump.c b/tools/tdbdump.c new file mode 100644 index 0000000..9f1ab91 --- /dev/null +++ b/tools/tdbdump.c @@ -0,0 +1,181 @@ +/* + Unix SMB/CIFS implementation. + simple tdb dump util + Copyright (C) Andrew Tridgell 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 "replace.h" +#include "system/locale.h" +#include "system/time.h" +#include "system/filesys.h" +#include "system/wait.h" +#include "tdb.h" + +static void print_data(TDB_DATA d) +{ + unsigned char *p = (unsigned char *)d.dptr; + int len = d.dsize; + while (len--) { + if (isprint(*p) && !strchr("\"\\", *p)) { + fputc(*p, stdout); + } else { + printf("\\%02X", *p); + } + p++; + } +} + +static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state) +{ + printf("{\n"); + printf("key(%zu) = \"", key.dsize); + print_data(key); + printf("\"\n"); + printf("data(%zu) = \"", dbuf.dsize); + print_data(dbuf); + printf("\"\n"); + printf("}\n"); + return 0; +} + +static void log_stderr(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) PRINTF_ATTRIBUTE(3,4); + +static void log_stderr(struct tdb_context *tdb, enum tdb_debug_level level, + const char *fmt, ...) +{ + va_list ap; + const char *name = tdb_name(tdb); + const char *prefix = ""; + + if (!name) + name = "unnamed"; + + switch (level) { + case TDB_DEBUG_ERROR: + prefix = "ERROR: "; + break; + case TDB_DEBUG_WARNING: + prefix = "WARNING: "; + break; + case TDB_DEBUG_TRACE: + return; + + default: + case TDB_DEBUG_FATAL: + prefix = "FATAL: "; + break; + } + + va_start(ap, fmt); + fprintf(stderr, "tdb(%s): %s", name, prefix); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static void emergency_walk(TDB_DATA key, TDB_DATA dbuf, void *keyname) +{ + if (keyname) { + if (key.dsize != strlen(keyname)) + return; + if (memcmp(key.dptr, keyname, key.dsize) != 0) + return; + } + traverse_fn(NULL, key, dbuf, NULL); +} + +static int dump_tdb(const char *fname, const char *keyname, bool emergency) +{ + TDB_CONTEXT *tdb; + TDB_DATA key, value; + struct tdb_logging_context logfn = { + .log_fn = log_stderr, + }; + int tdb_flags = TDB_DEFAULT; + + /* + * Note: that O_RDONLY implies TDB_NOLOCK, but we want to make it + * explicit as it's important when working on databases which were + * created with mutex locking. + */ + tdb_flags |= TDB_NOLOCK; + + tdb = tdb_open_ex(fname, 0, tdb_flags, O_RDONLY, 0, &logfn, NULL); + if (!tdb) { + printf("Failed to open %s\n", fname); + return 1; + } + + if (emergency) { + return tdb_rescue(tdb, emergency_walk, discard_const(keyname)) == 0; + } + if (!keyname) { + return tdb_traverse(tdb, traverse_fn, NULL) == -1 ? 1 : 0; + } else { + key.dptr = discard_const_p(uint8_t, keyname); + key.dsize = strlen(keyname); + value = tdb_fetch(tdb, key); + if (!value.dptr) { + return 1; + } else { + print_data(value); + free(value.dptr); + } + } + + return 0; +} + +static void usage( void) +{ + printf( "Usage: tdbdump [options] <filename>\n\n"); + printf( " -h this help message\n"); + printf( " -k keyname dumps value of keyname\n"); + printf( " -e emergency dump, for corrupt databases\n"); +} + + int main(int argc, char *argv[]) +{ + char *fname, *keyname=NULL; + bool emergency = false; + int c; + + if (argc < 2) { + printf("Usage: tdbdump <fname>\n"); + exit(1); + } + + while ((c = getopt( argc, argv, "hk:e")) != -1) { + switch (c) { + case 'h': + usage(); + exit( 0); + case 'k': + keyname = optarg; + break; + case 'e': + emergency = true; + break; + default: + usage(); + exit( 1); + } + } + + fname = argv[optind]; + + return dump_tdb(fname, keyname, emergency); +} diff --git a/tools/tdbrestore.c b/tools/tdbrestore.c new file mode 100644 index 0000000..1435758 --- /dev/null +++ b/tools/tdbrestore.c @@ -0,0 +1,202 @@ +/* + tdbrestore -- construct a tdb from tdbdump output. + Copyright (C) Volker Lendecke 2010 + Copyright (C) Simon McVittie 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include <assert.h> +#include "system/locale.h" +#include "system/time.h" +#include "system/filesys.h" +#include "system/wait.h" +#include "tdb.h" + +static int read_linehead(FILE *f) +{ + size_t i; + int c; + int num_bytes; + char prefix[128]; + + while (1) { + c = getc(f); + if (c == EOF) { + return -1; + } + if (c == '(') { + break; + } + } + for (i=0; i<sizeof(prefix); i++) { + c = getc(f); + if (c == EOF) { + return -1; + } + prefix[i] = c; + if (c == '"') { + break; + } + } + if (i == sizeof(prefix)) { + return -1; + } + prefix[i] = '\0'; + + if (sscanf(prefix, "%d) = ", &num_bytes) != 1) { + return -1; + } + return num_bytes; +} + +static int read_data(FILE *f, TDB_DATA *d, size_t size) { + size_t i; + + d->dptr = (unsigned char *)malloc(size); + if (d->dptr == NULL) { + return -1; + } + d->dsize = size; + + for (i=0; i<size; i++) { + int c = getc(f); + if (c == EOF) { + fprintf(stderr, "Unexpected EOF in data\n"); + return 1; + } else if (c == '"') { + return 0; + } else if (c == '\\') { + char in[3] = {0}; + size_t n; + bool ok; + + n = fread(in, 1, 2, stdin); + if (n != 2) { + return -1; + } + ok = hex_byte(in, &d->dptr[i]); + if (!ok) { + fprintf(stderr, "Invalid hex: .2%s\n", in); + return -1; + } + } else { + d->dptr[i] = c; + } + } + return 0; +} + +static int swallow(FILE *f, const char *s, int *eof) +{ + char line[128]; + + if (fgets(line, sizeof(line), f) == NULL) { + if (eof != NULL) { + *eof = 1; + } + return -1; + } + if (strcmp(line, s) != 0) { + return -1; + } + return 0; +} + +static int read_rec(FILE *f, TDB_CONTEXT *tdb, int *eof) +{ + int length; + TDB_DATA key, data; + int ret = -1; + + key.dptr = NULL; + data.dptr = NULL; + + if (swallow(f, "{\n", eof) == -1) { + goto fail; + } + length = read_linehead(f); + if (length == -1) { + goto fail; + } + if (read_data(f, &key, length) == -1) { + goto fail; + } + if (swallow(f, "\"\n", NULL) == -1) { + goto fail; + } + length = read_linehead(f); + if (length == -1) { + goto fail; + } + if (read_data(f, &data, length) == -1) { + goto fail; + } + if ((swallow(f, "\"\n", NULL) == -1) + || (swallow(f, "}\n", NULL) == -1)) { + goto fail; + } + if (tdb_store(tdb, key, data, TDB_INSERT) != 0) { + fprintf(stderr, "TDB error: %s\n", tdb_errorstr(tdb)); + goto fail; + } + + ret = 0; +fail: + free(key.dptr); + free(data.dptr); + return ret; +} + +static int restore_tdb(const char *fname) +{ + TDB_CONTEXT *tdb; + + tdb = tdb_open(fname, 0, 0, O_RDWR|O_CREAT|O_EXCL, 0666); + if (!tdb) { + perror("tdb_open"); + fprintf(stderr, "Failed to open %s\n", fname); + return 1; + } + + while (1) { + int eof = 0; + if (read_rec(stdin, tdb, &eof) == -1) { + if (eof) { + break; + } + return 1; + } + } + if (tdb_close(tdb)) { + fprintf(stderr, "Error closing tdb\n"); + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + char *fname; + + if (argc < 2) { + printf("Usage: %s dbname < tdbdump_output\n", argv[0]); + exit(1); + } + + fname = argv[1]; + + return restore_tdb(fname); +} diff --git a/tools/tdbtest.c b/tools/tdbtest.c new file mode 100644 index 0000000..0be35dc --- /dev/null +++ b/tools/tdbtest.c @@ -0,0 +1,290 @@ +/* a test program for tdb - the trivial database */ + +#include "replace.h" +#include "tdb.h" +#include "system/filesys.h" +#include "system/time.h" + +#include <gdbm.h> + + +#define DELETE_PROB 7 +#define STORE_PROB 5 + +static struct tdb_context *db; +static GDBM_FILE gdbm; + +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 fatal(const char *why) +{ + perror(why); + exit(1); +} + +#ifdef PRINTF_ATTRIBUTE +static void tdb_log(struct tdb_context *tdb, int level, const char *format, ...) PRINTF_ATTRIBUTE(3,4); +#endif +static void tdb_log(struct tdb_context *tdb, int level, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vfprintf(stdout, format, ap); + va_end(ap); + fflush(stdout); +} + +static void compare_db(void) +{ + TDB_DATA d, key, nextkey; + datum gd, gkey, gnextkey; + + key = tdb_firstkey(db); + while (key.dptr) { + d = tdb_fetch(db, key); + gkey.dptr = key.dptr; + gkey.dsize = key.dsize; + + gd = gdbm_fetch(gdbm, gkey); + + if (!gd.dptr) fatal("key not in gdbm"); + if (gd.dsize != d.dsize) fatal("data sizes differ"); + if (memcmp(gd.dptr, d.dptr, d.dsize)) { + fatal("data differs"); + } + + nextkey = tdb_nextkey(db, key); + free(key.dptr); + free(d.dptr); + free(gd.dptr); + key = nextkey; + } + + gkey = gdbm_firstkey(gdbm); + while (gkey.dptr) { + gd = gdbm_fetch(gdbm, gkey); + key.dptr = gkey.dptr; + key.dsize = gkey.dsize; + + d = tdb_fetch(db, key); + + if (!d.dptr) fatal("key not in db"); + if (d.dsize != gd.dsize) fatal("data sizes differ"); + if (memcmp(d.dptr, gd.dptr, gd.dsize)) { + fatal("data differs"); + } + + gnextkey = gdbm_nextkey(gdbm, gkey); + free(gkey.dptr); + free(gd.dptr); + free(d.dptr); + gkey = gnextkey; + } +} + +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) +{ + int klen, dlen; + char *k, *d; + TDB_DATA key, data; + + klen = 1 + (rand() % 4); + dlen = 1 + (rand() % 100); + + k = randbuf(klen); + d = randbuf(dlen); + + key.dptr = k; + key.dsize = klen+1; + + data.dptr = d; + data.dsize = dlen+1; + + if (rand() % DELETE_PROB == 0) { + tdb_delete(db, key); + } else if (rand() % STORE_PROB == 0) { + if (tdb_store(db, key, data, TDB_REPLACE) != 0) { + fatal("tdb_store failed"); + } + } else { + data = tdb_fetch(db, key); + if (data.dptr) free(data.dptr); + } + + free(k); + free(d); +} + +static void addrec_gdbm(void) +{ + int klen, dlen; + char *k, *d; + datum key, data; + + klen = 1 + (rand() % 4); + dlen = 1 + (rand() % 100); + + k = randbuf(klen); + d = randbuf(dlen); + + key.dptr = k; + key.dsize = klen+1; + + data.dptr = d; + data.dsize = dlen+1; + + if (rand() % DELETE_PROB == 0) { + gdbm_delete(gdbm, key); + } else if (rand() % STORE_PROB == 0) { + if (gdbm_store(gdbm, key, data, GDBM_REPLACE) != 0) { + fatal("gdbm_store failed"); + } + } else { + data = gdbm_fetch(gdbm, key); + if (data.dptr) free(data.dptr); + } + + free(k); + free(d); +} + +static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, void *state) +{ +#if 0 + printf("[%s] [%s]\n", key.dptr, dbuf.dptr); +#endif + tdb_delete(tdb, key); + return 0; +} + +static void merge_test(void) +{ + int i; + char keys[5][2]; + char tdata[] = "test"; + TDB_DATA key, data; + + for (i = 0; i < 5; i++) { + snprintf(keys[i],2, "%d", i); + key.dptr = keys[i]; + key.dsize = 2; + + data.dptr = tdata; + data.dsize = 4; + + if (tdb_store(db, key, data, TDB_REPLACE) != 0) { + fatal("tdb_store failed"); + } + } + + key.dptr = keys[0]; + tdb_delete(db, key); + key.dptr = keys[4]; + tdb_delete(db, key); + key.dptr = keys[2]; + tdb_delete(db, key); + key.dptr = keys[1]; + tdb_delete(db, key); + key.dptr = keys[3]; + tdb_delete(db, key); +} + +static char *test_path(const char *filename) +{ + const char *prefix = getenv("TEST_DATA_PREFIX"); + + if (prefix) { + char *path = NULL; + int ret; + + ret = asprintf(&path, "%s/%s", prefix, filename); + if (ret == -1) { + return NULL; + } + return path; + } + + return strdup(filename); +} + + int main(int argc, const char *argv[]) +{ + int i, seed=0; + int loops = 10000; + int num_entries; + char test_gdbm[1] = "test.gdbm"; + char *test_tdb; + + test_gdbm[0] = test_path("test.gdbm"); + test_tdb = test_path("test.tdb"); + + unlink(test_gdbm[0]); + + db = tdb_open(test_tdb, 0, TDB_CLEAR_IF_FIRST, + O_RDWR | O_CREAT | O_TRUNC, 0600); + gdbm = gdbm_open(test_gdbm, 512, GDBM_WRITER|GDBM_NEWDB|GDBM_FAST, + 0600, NULL); + + if (!db || !gdbm) { + fatal("db open failed"); + } + +#if 1 + srand(seed); + _start_timer(); + for (i=0;i<loops;i++) addrec_gdbm(); + printf("gdbm got %.2f ops/sec\n", i/_end_timer()); +#endif + + merge_test(); + + srand(seed); + _start_timer(); + for (i=0;i<loops;i++) addrec_db(); + printf("tdb got %.2f ops/sec\n", i/_end_timer()); + + if (tdb_validate_freelist(db, &num_entries) == -1) { + printf("tdb freelist is corrupt\n"); + } else { + printf("tdb freelist is good (%d entries)\n", num_entries); + } + + compare_db(); + + printf("traversed %d records\n", tdb_traverse(db, traverse_fn, NULL)); + printf("traversed %d records\n", tdb_traverse(db, traverse_fn, NULL)); + + tdb_close(db); + gdbm_close(gdbm); + + free(test_gdbm[0]); + free(test_tdb); + + return 0; +} diff --git a/tools/tdbtool.c b/tools/tdbtool.c new file mode 100644 index 0000000..fca28a1 --- /dev/null +++ b/tools/tdbtool.c @@ -0,0 +1,924 @@ +/* + 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 "replace.h" +#include "system/locale.h" +#include "system/time.h" +#include "system/filesys.h" +#include "system/wait.h" +#include "tdb.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; +static int _disable_lock; + +enum commands { + CMD_CREATE_TDB, + CMD_OPEN_TDB, + CMD_TRANSACTION_START, + CMD_TRANSACTION_COMMIT, + CMD_TRANSACTION_CANCEL, + CMD_ERASE, + CMD_DUMP, + CMD_INSERT, + CMD_MOVE, + CMD_STOREHEX, + CMD_STORE, + CMD_SHOW, + CMD_KEYS, + CMD_HEXKEYS, + CMD_DELETE, + CMD_LIST_HASH_FREE, + CMD_LIST_FREE, + CMD_FREELIST_SIZE, + CMD_INFO, + CMD_MMAP, + CMD_SPEED, + CMD_FIRST, + CMD_NEXT, + CMD_SYSTEM, + CMD_CHECK, + CMD_REPACK, + 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}, + {"transaction_start", CMD_TRANSACTION_START}, + {"transaction_commit", CMD_TRANSACTION_COMMIT}, + {"transaction_cancel", CMD_TRANSACTION_CANCEL}, + {"erase", CMD_ERASE}, + {"dump", CMD_DUMP}, + {"insert", CMD_INSERT}, + {"move", CMD_MOVE}, + {"storehex", CMD_STOREHEX}, + {"store", CMD_STORE}, + {"show", CMD_SHOW}, + {"keys", CMD_KEYS}, + {"hexkeys", CMD_HEXKEYS}, + {"delete", CMD_DELETE}, + {"list", CMD_LIST_HASH_FREE}, + {"free", CMD_LIST_FREE}, + {"freelist_size", CMD_FREELIST_SIZE}, + {"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}, + {"repack", CMD_REPACK}, + {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); +} + +#ifdef PRINTF_ATTRIBUTE +static void tdb_log_open(struct tdb_context *tdb, enum tdb_debug_level level, + const char *format, ...) PRINTF_ATTRIBUTE(3,4); +#endif +static void tdb_log_open(struct tdb_context *tdb, enum tdb_debug_level level, + const char *format, ...) +{ + const char *mutex_msg = + "Can use mutexes only with MUTEX_LOCKING or NOLOCK\n"; + char *p; + va_list ap; + + p = strstr(format, mutex_msg); + if (p != NULL) { + /* + * Yes, this is a hack, but we don't want to see this + * message on first open, but we want to see + * everything else. + */ + return; + } + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +#ifdef PRINTF_ATTRIBUTE +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4); +#endif +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, 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 TDB_CONTEXT *tdb; + +static int print_rec(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state); +static int print_key(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state); +static int print_hexkey(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" +" 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" +" storehex key data : store a record (replace), key/value in hex format\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" +" freelist_size : print the number of records in the freelist\n" +" check : check the integrity of an opened database\n" +" repack : repack the 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) +{ + struct tdb_logging_context log_ctx = { NULL, NULL}; + log_ctx.log_fn = tdb_log; + + if (tdb) tdb_close(tdb); + tdb = tdb_open_ex(tdbname, 0, + TDB_CLEAR_IF_FIRST | + (disable_mmap?TDB_NOMMAP:0) | + (_disable_lock?TDB_NOLOCK:0), + O_RDWR | O_CREAT | O_TRUNC, 0600, &log_ctx, NULL); + if (!tdb) { + printf("Could not create %s: %s\n", tdbname, strerror(errno)); + } +} + +static void open_tdb(const char *tdbname) +{ + struct tdb_logging_context log_ctx = { NULL, NULL }; + log_ctx.log_fn = tdb_log_open; + + if (tdb) tdb_close(tdb); + tdb = tdb_open_ex(tdbname, 0, + (disable_mmap?TDB_NOMMAP:0) | + (_disable_lock?TDB_NOLOCK:0), + O_RDWR, 0600, + &log_ctx, NULL); + + log_ctx.log_fn = tdb_log; + if (tdb != NULL) { + tdb_set_logging_function(tdb, &log_ctx); + } + + if ((tdb == NULL) && (errno == EINVAL)) { + /* + * Retry NOLOCK and readonly. There we want to see all + * error messages. + */ + tdb = tdb_open_ex(tdbname, 0, + (disable_mmap?TDB_NOMMAP:0) |TDB_NOLOCK, + O_RDONLY, 0600, + &log_ctx, NULL); + } + + 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) != 0) { + 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) != 0) { + terror("store failed"); + } +} + +static bool parse_hex(const char *src, size_t srclen, uint8_t *dst) +{ + size_t i=0; + + if ((srclen % 2) != 0) { + return false; + } + + while (i<srclen) { + bool ok = hex_byte(src, dst); + if (!ok) { + return false; + } + src += 2; + dst += 1; + } + + return true; +} + +static void store_hex_tdb(char *keystr, size_t keylen, + char *datastr, size_t datalen) +{ + if ((keystr == NULL) || (keylen == 0)) { + terror("need key"); + return; + } + if ((datastr == NULL) || (datalen == 0)) { + terror("need data"); + return; + } + + { + uint8_t keybuf[keylen/2]; + TDB_DATA key = { .dptr = keybuf, .dsize = sizeof(keybuf) }; + uint8_t databuf[datalen/2]; + TDB_DATA data = { .dptr = databuf, .dsize = sizeof(databuf) }; + bool ok; + + ok = parse_hex(keystr, keylen, keybuf); + if (!ok) { + terror("need hex key"); + return; + } + ok = parse_hex(datastr, datalen, databuf); + if (!ok) { + terror("need hex data"); + return; + } + + printf("storing key/data:\n"); + print_data((char *)key.dptr, key.dsize); + print_data((char *)data.dptr, data.dsize); + + if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) { + 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; + 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, 0, O_RDWR, 0600); + if ( !dst_tdb ) { + terror("unable to open destination tdb"); + return; + } + + if (tdb_store( dst_tdb, key, dbuf, TDB_REPLACE ) != 0) { + terror("failed to move record"); + } + else + printf("record moved\n"); + + tdb_close( dst_tdb ); + + return; +} + +static int print_rec(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(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(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(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); + + if (!summary) { + printf("Error = %s\n", tdb_errorstr(tdb)); + } else { + printf("%s", summary); + free(summary); + } +} + +static void speed_tdb(const char *tlimit) +{ + const char *str = "store test", *str2 = "transaction test"; + 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 = discard_const_p(uint8_t, str); + key.dsize = strlen((char *)key.dptr); + dbuf.dptr = (uint8_t *) &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 { + TDB_DATA key; + key.dptr = discard_const_p(uint8_t, str); + key.dsize = strlen((char *)key.dptr); + tdb_fetch(tdb, key); + t = _end_timer(); + ops++; + } while (t < timelimit); + printf("%10.3f ops/sec\n", ops/t); + + ops = 0; + printf("Testing transaction speed for %u seconds\n", timelimit); + _start_timer(); + do { + long int r = random(); + TDB_DATA key, dbuf; + key.dptr = discard_const_p(uint8_t, str2); + key.dsize = strlen((char *)key.dptr); + dbuf.dptr = (uint8_t *) &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); + + 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 void first_record(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(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 int count(TDB_DATA key, TDB_DATA data, void *private_data) +{ + (*(unsigned int *)private_data)++; + return 0; +} + +static void check_db(TDB_CONTEXT *the_tdb) +{ + int tdbcount = 0; + if (!the_tdb) + printf("Error: No database opened!\n"); + else if (tdb_check(the_tdb, count, &tdbcount) == -1) + printf("Integrity check for the opened database failed.\n"); + else + printf("Database integrity is OK and has %d records.\n", + tdbcount); +} + +static int do_command(void) +{ + COMMAND_TABLE *ctp = cmd_table; + enum commands mycmd = CMD_HELP; + int cmd_len; + + if (cmdname != NULL) { + if (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); + 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) { + 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_REPACK: + bIterate = 0; + tdb_repack(tdb); + return 0; + case CMD_TRANSACTION_CANCEL: + bIterate = 0; + tdb_transaction_cancel(tdb); + return 0; + case CMD_ERASE: + bIterate = 0; + tdb_wipe_all(tdb); + 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_STOREHEX: + bIterate = 0; + store_hex_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; + case CMD_LIST_HASH_FREE: + tdb_dump_all(tdb); + return 0; + case CMD_LIST_FREE: + tdb_printfreelist(tdb); + return 0; + case CMD_FREELIST_SIZE: { + int size; + + size = tdb_freelist_size(tdb); + if (size < 0) { + printf("Error getting freelist size.\n"); + } else { + printf("freelist size: %d\n", size); + } + + return 0; + } + 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_SYSTEM: + case CMD_QUIT: + /* + * unhandled commands. cases included here to avoid compiler + * warnings. + */ + return 0; + } + } + + return 0; +} + +static char *tdb_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] && (strcmp(argv[1], "-l") == 0)) { + _disable_lock = 1; + argv[1] = argv[0]; + argv += 1; + argc -= 1; + } + + 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 = tdb_convert_string(arg1,&arg1len); + if (arg2) arg2 = tdb_convert_string(arg2,&arg2len); + if (do_command()) break; + } + break; + case 5: + arg2 = tdb_convert_string(argv[4],&arg2len); + FALL_THROUGH; + case 4: + arg1 = tdb_convert_string(argv[3],&arg1len); + FALL_THROUGH; + case 3: + cmdname = argv[2]; + FALL_THROUGH; + default: + do_command(); + break; + } + + if (tdb) tdb_close(tdb); + + return 0; +} diff --git a/tools/tdbtortseq.c b/tools/tdbtortseq.c new file mode 100644 index 0000000..9423b46 --- /dev/null +++ b/tools/tdbtortseq.c @@ -0,0 +1,123 @@ +/* + * Unix SMB/CIFS implementation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "replace.h" +#include "system/filesys.h" +#include <tdb.h> +#include <stdio.h> +#include <errno.h> +#include "system/wait.h" + +#define NPROCS 500 +#define NUMOPS 1000000 + +int main(void) +{ + pid_t pids[NPROCS]; + struct tdb_context *tdb = NULL; + int i; + uint32_t seqnum_before, seqnum_after; + + tdb = tdb_open("seqnum_test.tdb", + 10000, + TDB_CLEAR_IF_FIRST| + TDB_SEQNUM| + TDB_INCOMPATIBLE_HASH| + TDB_MUTEX_LOCKING, + O_CREAT|O_RDWR, + 0644); + if (tdb == NULL) { + perror("tdb_open failed"); + return 1; + } + seqnum_before = tdb_get_seqnum(tdb); + + for (i=0; i<NPROCS; i++) { + pids[i] = fork(); + if (pids[i] == -1) { + perror("fork failed"); + return 1; + } + if (pids[i] == 0) { + pid_t mypid = getpid(); + int ret; + int j; + + ret = tdb_reopen(tdb); + if (ret != 0) { + perror("tdb_reopen failed"); + return 1; + } + + for (j=0; j<NUMOPS; j++) { + TDB_DATA key = { + .dptr = (uint8_t *)&mypid, + .dsize = sizeof(mypid), + }; + TDB_DATA value = { + .dptr = (uint8_t *)&j, + .dsize = sizeof(j), + }; + ret = tdb_store(tdb, key, value, 0); + if (ret == -1) { + perror("tdb_store failed"); + return 1; + } + } + + return 0; + } + } + + for (i=0; i<NPROCS; i++) { + int wstatus; + pid_t ret = waitpid(pids[i], &wstatus, 0); + + if (ret == -1) { + perror("waitpid failed"); + return 1; + } + + if (!WIFEXITED(wstatus)) { + fprintf(stderr, + "pid %d did not exit properly\n", + (int)pids[i]); + return 1; + } + + if (WEXITSTATUS(wstatus) != 0) { + fprintf(stderr, + "pid %d returned %d\n", + (int)pids[i], + WEXITSTATUS(wstatus)); + return 1; + } + } + + seqnum_after = tdb_get_seqnum(tdb); + + printf("seqnum_before=%"PRIu32", seqnum_after=%"PRIu32"\n", + seqnum_before, + seqnum_after); + + if ((seqnum_after - seqnum_before) != (NPROCS*NUMOPS)) { + perror("incrementing seqnum failed"); + return 1; + } + + return 0; +} diff --git a/tools/tdbtorture.c b/tools/tdbtorture.c new file mode 100644 index 0000000..1063f14 --- /dev/null +++ b/tools/tdbtorture.c @@ -0,0 +1,500 @@ +/* this tests tdb by doing lots of ops from several simultaneous + writers - that stresses the locking code. +*/ + +#include "replace.h" +#include "system/time.h" +#include "system/wait.h" +#include "system/filesys.h" +#include "tdb.h" + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + + +#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 CULL_PROB 100 +#define KEYLEN 3 +#define DATALEN 100 + +static struct tdb_context *db; +static int in_transaction; +static int error_count; +static int always_transaction = 0; +static int hash_size = 2; +static unsigned loopnum; +static int count_pipe; +static bool mutex = false; +static struct tdb_logging_context log_ctx; + +#ifdef PRINTF_ATTRIBUTE +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4); +#endif +static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) +{ + va_list ap; + + /* trace level messages do not indicate an error */ + 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 +} + +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 int cull_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 + return 0; +} + +static bool do_transaction(void) +{ +#if TRANSACTION_PROB + if (mutex) { + return false; + } + if (random() % TRANSACTION_PROB == 0) { + return true; + } +#endif + return false; +} + +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_transaction == 0 && random() % REOPEN_PROB == 0) { + tdb_reopen_all(0); + goto next; + } +#endif + + if (in_transaction == 0 && + (always_transaction || do_transaction())) { + if (tdb_transaction_start(db) != 0) { + fatal("tdb_transaction_start failed"); + } + in_transaction++; + goto next; + } + if (in_transaction && do_transaction()) { + 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_transaction && do_transaction()) { + if (tdb_transaction_cancel(db) != 0) { + fatal("tdb_transaction_cancel failed"); + } + in_transaction--; + goto next; + } + +#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 + if (random() % TRAVERSE_PROB == 0) { + tdb_traverse(db, cull_traverse, NULL); + goto next; + } +#endif + +#if TRAVERSE_READ_PROB + if (random() % TRAVERSE_READ_PROB == 0) { + tdb_traverse_read(db, NULL, NULL); + 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 [-t] [-k] [-m] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED] [-H HASH_SIZE]\n"); + exit(0); +} + +static void send_count_and_suicide(int sig) +{ + ssize_t ret; + + /* This ensures our successor can continue where we left off. */ + do { + ret = write(count_pipe, &loopnum, sizeof(loopnum)); + } while (ret == -1 && errno == EINTR); + /* This gives a unique signature. */ + kill(getpid(), SIGUSR2); +} + +static int run_child(const char *filename, int i, int seed, unsigned num_loops, unsigned start) +{ + int tdb_flags = TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH; + + if (mutex) { + tdb_flags |= TDB_MUTEX_LOCKING; + } + + db = tdb_open_ex(filename, hash_size, tdb_flags, + O_RDWR | O_CREAT, 0600, &log_ctx, NULL); + if (!db) { + fatal("db open failed"); + } + + 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 (always_transaction) { + while (in_transaction) { + tdb_transaction_cancel(db); + in_transaction--; + } + if (tdb_transaction_start(db) != 0) + fatal("tdb_transaction_start failed"); + } + tdb_traverse(db, traverse_fn, NULL); + tdb_traverse(db, traverse_fn, NULL); + if (always_transaction) { + if (tdb_transaction_commit(db) != 0) + fatal("tdb_transaction_commit failed"); + } + } + + tdb_close(db); + + return (error_count < 100 ? error_count : 100); +} + +static char *test_path(const char *filename) +{ + const char *prefix = getenv("TEST_DATA_PREFIX"); + + if (prefix) { + char *path = NULL; + int ret; + + ret = asprintf(&path, "%s/%s", prefix, filename); + if (ret == -1) { + return NULL; + } + return path; + } + + return strdup(filename); +} + +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; + char *test_tdb; + + log_ctx.log_fn = tdb_log; + + while ((c = getopt(argc, argv, "n:l:s:H:thkm")) != -1) { + switch (c) { + case 'n': + num_procs = strtol(optarg, NULL, 0); + break; + case 'l': + num_loops = strtol(optarg, NULL, 0); + break; + case 'H': + hash_size = strtol(optarg, NULL, 0); + break; + case 's': + seed = strtol(optarg, NULL, 0); + break; + case 't': + always_transaction = 1; + break; + case 'k': + kill_random = 1; + break; + case 'm': + mutex = tdb_runtime_check_for_robust_mutexes(); + if (!mutex) { + printf("tdb_runtime_check_for_robust_mutexes() returned false\n"); + exit(1); + } + break; + default: + usage(); + } + } + + test_tdb = test_path("torture.tdb"); + + unlink(test_tdb); + + if (seed == -1) { + seed = (getpid() + time(NULL)) & 0x7FFFFFFF; + } + + printf("Testing with %d processes, %d loops, %d hash_size, seed=%d%s\n", + num_procs, num_loops, hash_size, seed, + (always_transaction ? " (all within transactions)" : "")); + + if (num_procs == 1 && !kill_random) { + /* Don't fork for this case, makes debugging easier. */ + error_count = run_child(test_tdb, 0, seed, num_loops, 0); + goto done; + } + + pids = (pid_t *)calloc(sizeof(pid_t), num_procs); + if (pids == NULL) { + perror("Unable to allocate memory for pids"); + exit(1); + } + done = (int *)calloc(sizeof(int), num_procs); + if (done == NULL) { + perror("Unable to allocate memory for done"); + exit(1); + } + + 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]); + exit(run_child(test_tdb, 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 timeval tv; + + /* Sleep for 1/10 second. */ + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + + /* 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) { + ssize_t ret; + + do { + ret = read(pfds[0], &done[j], + sizeof(done[j])); + } while (ret == -1 && errno == EINTR); + } + pids[j] = fork(); + if (pids[j] == 0) + exit(run_child(test_tdb, 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++; + } + } + ARRAY_DEL_ELEMENT(pids, j, num_procs); + num_procs--; + } + + free(pids); + +done: + if (error_count == 0) { + int tdb_flags = TDB_DEFAULT; + + if (mutex) { + tdb_flags |= TDB_NOLOCK; + } + + db = tdb_open_ex(test_tdb, hash_size, tdb_flags, + O_RDWR, 0, &log_ctx, NULL); + if (!db) { + fatal("db open failed\n"); + exit(1); + } + if (tdb_check(db, NULL, NULL) == -1) { + printf("db check failed\n"); + exit(1); + } + tdb_close(db); + printf("OK\n"); + } + + free(test_tdb); + return error_count; +} |