From 90f463b25f7bb0bc944732773c56e356834ea203 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 4 Oct 2012 09:04:19 +0930 Subject: [PATCH] tdb: add tdb_rescue() This allows for an emergency best-effort dump. It's a little better than strings(1). Signed-off-by: Rusty Russell --- lib/tdb/ABI/tdb-1.2.11.sigs | 67 +++++++ lib/tdb/common/rescue.c | 349 +++++++++++++++++++++++++++++++++++ lib/tdb/include/tdb.h | 22 +++ lib/tdb/libtdb.m4 | 2 +- lib/tdb/test/run-rescue-find_entry.c | 50 +++++ lib/tdb/test/run-rescue.c | 126 +++++++++++++ lib/tdb/wscript | 10 +- 7 files changed, 622 insertions(+), 4 deletions(-) create mode 100644 lib/tdb/ABI/tdb-1.2.11.sigs create mode 100644 lib/tdb/common/rescue.c create mode 100644 lib/tdb/test/run-rescue-find_entry.c create mode 100644 lib/tdb/test/run-rescue.c diff --git a/lib/tdb/ABI/tdb-1.2.11.sigs b/lib/tdb/ABI/tdb-1.2.11.sigs new file mode 100644 index 00000000000..d727f2163dd --- /dev/null +++ b/lib/tdb/ABI/tdb-1.2.11.sigs @@ -0,0 +1,67 @@ +tdb_add_flags: void (struct tdb_context *, unsigned int) +tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA) +tdb_chainlock: int (struct tdb_context *, TDB_DATA) +tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA) +tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA) +tdb_chainlock_read: int (struct tdb_context *, TDB_DATA) +tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA) +tdb_chainunlock: int (struct tdb_context *, TDB_DATA) +tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA) +tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *) +tdb_close: int (struct tdb_context *) +tdb_delete: int (struct tdb_context *, TDB_DATA) +tdb_dump_all: void (struct tdb_context *) +tdb_enable_seqnum: void (struct tdb_context *) +tdb_error: enum TDB_ERROR (struct tdb_context *) +tdb_errorstr: const char *(struct tdb_context *) +tdb_exists: int (struct tdb_context *, TDB_DATA) +tdb_fd: int (struct tdb_context *) +tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA) +tdb_firstkey: TDB_DATA (struct tdb_context *) +tdb_freelist_size: int (struct tdb_context *) +tdb_get_flags: int (struct tdb_context *) +tdb_get_logging_private: void *(struct tdb_context *) +tdb_get_seqnum: int (struct tdb_context *) +tdb_hash_size: int (struct tdb_context *) +tdb_increment_seqnum_nonblock: void (struct tdb_context *) +tdb_jenkins_hash: unsigned int (TDB_DATA *) +tdb_lock_nonblock: int (struct tdb_context *, int, int) +tdb_lockall: int (struct tdb_context *) +tdb_lockall_mark: int (struct tdb_context *) +tdb_lockall_nonblock: int (struct tdb_context *) +tdb_lockall_read: int (struct tdb_context *) +tdb_lockall_read_nonblock: int (struct tdb_context *) +tdb_lockall_unmark: int (struct tdb_context *) +tdb_log_fn: tdb_log_func (struct tdb_context *) +tdb_map_size: size_t (struct tdb_context *) +tdb_name: const char *(struct tdb_context *) +tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA) +tdb_null: dptr = 0xXXXX, dsize = 0 +tdb_open: struct tdb_context *(const char *, int, int, int, mode_t) +tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func) +tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *) +tdb_printfreelist: int (struct tdb_context *) +tdb_remove_flags: void (struct tdb_context *, unsigned int) +tdb_reopen: int (struct tdb_context *) +tdb_reopen_all: int (int) +tdb_repack: int (struct tdb_context *) +tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *) +tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *) +tdb_set_max_dead: void (struct tdb_context *, int) +tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *) +tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int) +tdb_summary: char *(struct tdb_context *) +tdb_transaction_cancel: int (struct tdb_context *) +tdb_transaction_commit: int (struct tdb_context *) +tdb_transaction_prepare_commit: int (struct tdb_context *) +tdb_transaction_start: int (struct tdb_context *) +tdb_transaction_start_nonblock: int (struct tdb_context *) +tdb_transaction_write_lock_mark: int (struct tdb_context *) +tdb_transaction_write_lock_unmark: int (struct tdb_context *) +tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *) +tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *) +tdb_unlock: int (struct tdb_context *, int, int) +tdb_unlockall: int (struct tdb_context *) +tdb_unlockall_read: int (struct tdb_context *) +tdb_validate_freelist: int (struct tdb_context *, int *) +tdb_wipe_all: int (struct tdb_context *) diff --git a/lib/tdb/common/rescue.c b/lib/tdb/common/rescue.c new file mode 100644 index 00000000000..03ae8d6ae51 --- /dev/null +++ b/lib/tdb/common/rescue.c @@ -0,0 +1,349 @@ + /* + Unix SMB/CIFS implementation. + + trivial database library, rescue attempt code. + + Copyright (C) Rusty Russell 2012 + + ** NOTE! The following LGPL license applies to the tdb + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#include "tdb_private.h" +#include + + +struct found { + tdb_off_t head; /* 0 -> invalid. */ + struct tdb_record rec; + TDB_DATA key; + bool in_hash; + bool in_free; +}; + +struct found_table { + /* As an ordered array (by head offset). */ + struct found *arr; + unsigned int num, max; +}; + +static bool looks_like_valid_record(struct tdb_context *tdb, + tdb_off_t off, + const struct tdb_record *rec, + TDB_DATA *key) +{ + unsigned int hval; + + if (rec->magic != TDB_MAGIC) + return false; + + if (rec->key_len + rec->data_len > rec->rec_len) + return false; + + if (rec->rec_len % TDB_ALIGNMENT) + return false; + + /* Next pointer must make some sense. */ + if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->header.hash_size)) + return false; + + if (tdb->methods->tdb_oob(tdb, rec->next, sizeof(*rec), 1)) + return false; + + key->dsize = rec->key_len; + key->dptr = tdb_alloc_read(tdb, off + sizeof(*rec), key->dsize); + if (!key->dptr) + return false; + + hval = tdb->hash_fn(key); + if (hval != rec->full_hash) { + free(key->dptr); + return false; + } + + /* Caller frees up key->dptr */ + return true; +} + +static bool add_to_table(struct found_table *found, + tdb_off_t off, + struct tdb_record *rec, + TDB_DATA key) +{ + if (found->num + 1 > found->max) { + struct found *new; + found->max = (found->max ? found->max * 2 : 128); + new = realloc(found->arr, found->max * sizeof(found->arr[0])); + if (!new) + return false; + found->arr = new; + } + + found->arr[found->num].head = off; + found->arr[found->num].rec = *rec; + found->arr[found->num].key = key; + found->arr[found->num].in_hash = false; + found->arr[found->num].in_free = false; + + found->num++; + return true; +} + +static bool walk_record(struct tdb_context *tdb, + const struct found *f, + void (*walk)(TDB_DATA, TDB_DATA, void *private_data), + void *private_data) +{ + TDB_DATA data; + + data.dsize = f->rec.data_len; + data.dptr = tdb_alloc_read(tdb, + f->head + sizeof(f->rec) + f->rec.key_len, + data.dsize); + if (!data.dptr) { + if (tdb->ecode == TDB_ERR_OOM) + return false; + /* I/O errors are expected. */ + return true; + } + + walk(f->key, data, private_data); + free(data.dptr); + return true; +} + +/* First entry which has offset >= this one. */ +static unsigned int find_entry(struct found_table *found, tdb_off_t off) +{ + unsigned int start = 0, end = found->num; + + while (start < end) { + /* We can't overflow here. */ + unsigned int mid = (start + end) / 2; + + if (off < found->arr[mid].head) { + end = mid; + } else if (off > found->arr[mid].head) { + start = mid + 1; + } else { + return mid; + } + } + + assert(start == end); + return end; +} + +static void found_in_hashchain(struct found_table *found, tdb_off_t head) +{ + unsigned int match; + + match = find_entry(found, head); + if (match < found->num && found->arr[match].head == head) { + found->arr[match].in_hash = true; + } +} + +static void mark_free_area(struct found_table *found, tdb_off_t head, + tdb_len_t len) +{ + unsigned int match; + + match = find_entry(found, head); + /* Mark everything within this free entry. */ + while (match < found->num) { + if (found->arr[match].head >= head + len) { + break; + } + found->arr[match].in_free = true; + match++; + } +} + +static int cmp_key(const void *a, const void *b) +{ + const struct found *fa = a, *fb = b; + + if (fa->key.dsize < fb->key.dsize) { + return -1; + } else if (fa->key.dsize > fb->key.dsize) { + return 1; + } + return memcmp(fa->key.dptr, fb->key.dptr, fa->key.dsize); +} + +static bool key_eq(TDB_DATA a, TDB_DATA b) +{ + return a.dsize == b.dsize + && memcmp(a.dptr, b.dptr, a.dsize) == 0; +} + +static void free_table(struct found_table *found) +{ + unsigned int i; + + for (i = 0; i < found->num; i++) { + free(found->arr[i].key.dptr); + } + free(found->arr); +} + +static void logging_suppressed(struct tdb_context *tdb, + enum tdb_debug_level level, const char *fmt, ...) +{ +} + +_PUBLIC_ int tdb_rescue(struct tdb_context *tdb, + void (*walk)(TDB_DATA, TDB_DATA, void *private_data), + void *private_data) +{ + struct found_table found = { NULL, 0, 0 }; + tdb_off_t h, off, i; + tdb_log_func oldlog = tdb->log.log_fn; + struct tdb_record rec; + TDB_DATA key; + bool locked; + + /* Read-only databases use no locking at all: it's best-effort. + * We may have a write lock already, so skip that case too. */ + if (tdb->read_only || tdb->allrecord_lock.count != 0) { + locked = false; + } else { + if (tdb_lockall_read(tdb) == -1) + return -1; + locked = true; + } + + /* Make sure we know true size of the underlying file. */ + tdb->methods->tdb_oob(tdb, tdb->map_size, 1, 1); + + /* Suppress logging, since we anticipate errors. */ + tdb->log.log_fn = logging_suppressed; + + /* Now walk entire db looking for records. */ + for (off = TDB_DATA_START(tdb->header.hash_size); + off < tdb->map_size; + off += TDB_ALIGNMENT) { + if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), + DOCONV()) == -1) + continue; + + if (looks_like_valid_record(tdb, off, &rec, &key)) { + if (!add_to_table(&found, off, &rec, key)) { + goto oom; + } + } + } + + /* Walk hash chains to positive vet. */ + for (h = 0; h < 1+tdb->header.hash_size; h++) { + bool slow_chase = false; + tdb_off_t slow_off = FREELIST_TOP + h*sizeof(tdb_off_t); + + if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t), + &off) == -1) + continue; + + while (off && off != slow_off) { + if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), + DOCONV()) != 0) { + break; + } + + /* 0 is the free list, rest are hash chains. */ + if (h == 0) { + /* Don't mark garbage as free. */ + if (rec.magic != TDB_FREE_MAGIC) { + break; + } + mark_free_area(&found, off, + sizeof(rec) + rec.rec_len); + } else { + found_in_hashchain(&found, off); + } + + off = rec.next; + + /* Loop detection using second pointer at half-speed */ + if (slow_chase) { + /* First entry happens to be next ptr */ + tdb_ofs_read(tdb, slow_off, &slow_off); + } + slow_chase = !slow_chase; + } + } + + /* Recovery area: must be marked as free, since it often has old + * records in there! */ + if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off) == 0 && off != 0) { + if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), + DOCONV()) == 0) { + mark_free_area(&found, off, sizeof(rec) + rec.rec_len); + } + } + + /* Now sort by key! */ + qsort(found.arr, found.num, sizeof(found.arr[0]), cmp_key); + + for (i = 0; i < found.num; ) { + unsigned int num, num_in_hash = 0; + + /* How many are identical? */ + for (num = 0; num < found.num - i; num++) { + if (!key_eq(found.arr[i].key, found.arr[i+num].key)) { + break; + } + if (found.arr[i+num].in_hash) { + if (!walk_record(tdb, &found.arr[i+num], + walk, private_data)) + goto oom; + num_in_hash++; + } + } + assert(num); + + /* If none were in the hash, we print any not in free list. */ + if (num_in_hash == 0) { + unsigned int j; + + for (j = i; j < i + num; j++) { + if (!found.arr[j].in_free) { + if (!walk_record(tdb, &found.arr[j], + walk, private_data)) + goto oom; + } + } + } + + i += num; + } + + tdb->log.log_fn = oldlog; + if (locked) { + tdb_unlockall_read(tdb); + } + return 0; + +oom: + tdb->log.log_fn = oldlog; + tdb->ecode = TDB_ERR_OOM; + TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_rescue: failed allocating\n")); + free_table(&found); + if (locked) { + tdb_unlockall_read(tdb); + } + return -1; +} diff --git a/lib/tdb/include/tdb.h b/lib/tdb/include/tdb.h index 5eb95625dae..d19439e7602 100644 --- a/lib/tdb/include/tdb.h +++ b/lib/tdb/include/tdb.h @@ -814,6 +814,28 @@ int tdb_check(struct tdb_context *tdb, int (*check) (TDB_DATA key, TDB_DATA data, void *private_data), void *private_data); +/** + * @brief Dump all possible records in a corrupt database. + * + * This is the only way to get data out of a database where tdb_check() fails. + * It will call walk() with anything which looks like a database record; this + * may well include invalid, incomplete or duplicate records. + * + * @param[in] tdb The database to check. + * + * @param[in] walk The walk function to use. + * + * @param[in] private_data the private data to pass to the walk function. + * + * @return 0 on success, -1 on error with error code set. + * + * @see tdb_error() + * @see tdb_errorstr() + */ +int tdb_rescue(struct tdb_context *tdb, + void (*walk) (TDB_DATA key, TDB_DATA data, void *private_data), + void *private_data); + /* @} ******************************************************************/ /* Low level locking functions: use with care */ diff --git a/lib/tdb/libtdb.m4 b/lib/tdb/libtdb.m4 index b5164fc5b20..47f098ed327 100644 --- a/lib/tdb/libtdb.m4 +++ b/lib/tdb/libtdb.m4 @@ -13,7 +13,7 @@ if test x"$tdbdir" = "x"; then AC_MSG_ERROR([cannot find tdb source in $tdbpaths]) fi TDB_OBJ="common/tdb.o common/dump.o common/transaction.o common/error.o common/traverse.o" -TDB_OBJ="$TDB_OBJ common/freelist.o common/freelistcheck.o common/io.o common/lock.o common/open.o common/check.o common/hash.o common/summary.o" +TDB_OBJ="$TDB_OBJ common/freelist.o common/freelistcheck.o common/io.o common/lock.o common/open.o common/check.o common/hash.o common/summary.o common/rescue.o" AC_SUBST(TDB_OBJ) AC_SUBST(LIBREPLACEOBJ) diff --git a/lib/tdb/test/run-rescue-find_entry.c b/lib/tdb/test/run-rescue-find_entry.c new file mode 100644 index 00000000000..25f4f1c05f4 --- /dev/null +++ b/lib/tdb/test/run-rescue-find_entry.c @@ -0,0 +1,50 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/rescue.c" +#include "tap-interface.h" +#include +#include "logging.h" + +#define NUM 20 + +/* Binary searches are deceptively simple: easy to screw up! */ +int main(int argc, char *argv[]) +{ + unsigned int i, j, n; + struct found f[NUM+1]; + struct found_table table; + + /* Set up array for searching. */ + for (i = 0; i < NUM+1; i++) { + f[i].head = i * 3; + } + table.arr = f; + + for (i = 0; i < NUM; i++) { + table.num = i; + for (j = 0; j < (i + 2) * 3; j++) { + n = find_entry(&table, j); + ok1(n <= i); + + /* If we were searching for something too large... */ + if (j > i*3) + ok1(n == i); + else { + /* It must give us something after j */ + ok1(f[n].head >= j); + ok1(n == 0 || f[n-1].head < j); + } + } + } + + return exit_status(); +} diff --git a/lib/tdb/test/run-rescue.c b/lib/tdb/test/run-rescue.c new file mode 100644 index 00000000000..a26c493972a --- /dev/null +++ b/lib/tdb/test/run-rescue.c @@ -0,0 +1,126 @@ +#include "../common/tdb_private.h" +#include "../common/io.c" +#include "../common/tdb.c" +#include "../common/lock.c" +#include "../common/freelist.c" +#include "../common/traverse.c" +#include "../common/transaction.c" +#include "../common/error.c" +#include "../common/open.c" +#include "../common/check.c" +#include "../common/hash.c" +#include "../common/rescue.c" +#include "tap-interface.h" +#include +#include "logging.h" + +struct walk_data { + TDB_DATA key; + TDB_DATA data; + bool fail; + unsigned count; +}; + +static inline bool tdb_deq(TDB_DATA a, TDB_DATA b) +{ + return a.dsize == b.dsize && memcmp(a.dptr, b.dptr, a.dsize) == 0; +} + +static inline TDB_DATA tdb_mkdata(const void *p, size_t len) +{ + TDB_DATA d; + d.dptr = (void *)p; + d.dsize = len; + return d; +} + +static void walk(TDB_DATA key, TDB_DATA data, void *_wd) +{ + struct walk_data *wd = _wd; + + if (!tdb_deq(key, wd->key)) { + wd->fail = true; + } + + if (!tdb_deq(data, wd->data)) { + wd->fail = true; + } + wd->count++; +} + +static void count_records(TDB_DATA key, TDB_DATA data, void *_wd) +{ + struct walk_data *wd = _wd; + + if (!tdb_deq(key, wd->key) || !tdb_deq(data, wd->data)) + diag("%.*s::%.*s\n", + (int)key.dsize, key.dptr, (int)data.dsize, data.dptr); + wd->count++; +} + +static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...) +{ + unsigned int *count = tdb_get_logging_private(tdb); + (*count)++; +} + +int main(int argc, char *argv[]) +{ + struct tdb_context *tdb; + struct walk_data wd; + unsigned int i, size, log_count = 0; + struct tdb_logging_context log_ctx = { log_fn, &log_count }; + + plan_tests(8); + tdb = tdb_open_ex("run-rescue.tdb", 1, TDB_CLEAR_IF_FIRST, + O_CREAT|O_TRUNC|O_RDWR, 0600, &log_ctx, NULL); + + wd.key.dsize = strlen("hi"); + wd.key.dptr = (void *)"hi"; + wd.data.dsize = strlen("world"); + wd.data.dptr = (void *)"world"; + wd.count = 0; + wd.fail = false; + + ok1(tdb_store(tdb, wd.key, wd.data, TDB_INSERT) == 0); + + ok1(tdb_rescue(tdb, walk, &wd) == 0); + ok1(!wd.fail); + ok1(wd.count == 1); + + /* Corrupt the database, walk should either get it or not. */ + size = tdb->map_size; + for (i = sizeof(struct tdb_header); i < size; i++) { + char c; + if (tdb->methods->tdb_read(tdb, i, &c, 1, false) != 0) + fail("Reading offset %i", i); + if (tdb->methods->tdb_write(tdb, i, "X", 1) != 0) + fail("Writing X at offset %i", i); + + wd.count = 0; + if (tdb_rescue(tdb, count_records, &wd) != 0) { + wd.fail = true; + break; + } + /* Could be 0 or 1. */ + if (wd.count > 1) { + wd.fail = true; + break; + } + if (tdb->methods->tdb_write(tdb, i, &c, 1) != 0) + fail("Restoring offset %i", i); + } + ok1(log_count == 0); + ok1(!wd.fail); + tdb_close(tdb); + + /* Now try our known-corrupt db. */ + tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0, + &taplogctx, NULL); + wd.count = 0; + ok1(tdb_rescue(tdb, count_records, &wd) == 0); + ok1(wd.count == 1627); + tdb_close(tdb); + + return exit_status(); +} diff --git a/lib/tdb/wscript b/lib/tdb/wscript index e28e43acac2..9d309a0325c 100644 --- a/lib/tdb/wscript +++ b/lib/tdb/wscript @@ -1,7 +1,7 @@ #!/usr/bin/env python APPNAME = 'tdb' -VERSION = '1.2.10' +VERSION = '1.2.11' blddir = 'bin' @@ -65,7 +65,7 @@ def build(bld): COMMON_SRC = bld.SUBDIR('common', '''check.c error.c tdb.c traverse.c freelistcheck.c lock.c dump.c freelist.c - io.c open.c transaction.c hash.c summary.c''') + io.c open.c transaction.c hash.c summary.c rescue.c''') if bld.env.standalone_tdb: bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig' @@ -143,6 +143,10 @@ def build(bld): 'replace tdb-test-helpers', includes='include', install=False) bld.SAMBA_BINARY('tdb1-run-readonly-check', 'test/run-readonly-check.c', 'replace tdb-test-helpers', includes='include', install=False) + bld.SAMBA_BINARY('tdb1-run-rescue', 'test/run-rescue.c', + 'replace tdb-test-helpers', includes='include', install=False) + bld.SAMBA_BINARY('tdb1-run-rescue-find_entry', 'test/run-rescue-find_entry.c', + 'replace tdb-test-helpers', includes='include', install=False) bld.SAMBA_BINARY('tdb1-run-rwlock-check', 'test/run-rwlock-check.c', 'replace tdb-test-helpers', includes='include', install=False) bld.SAMBA_BINARY('tdb1-run-summary', 'test/run-summary.c', @@ -185,7 +189,7 @@ def testonly(ctx): if not os.path.exists(link): os.symlink(os.path.abspath(os.path.join(env.cwd, 'test')), link) - for f in 'tdb1-run-3G-file', 'tdb1-run-bad-tdb-header', 'tdb1-run', 'tdb1-run-check', 'tdb1-run-corrupt', 'tdb1-run-die-during-transaction', 'tdb1-run-endian', 'tdb1-run-incompatible', 'tdb1-run-nested-transactions', 'tdb1-run-nested-traverse', 'tdb1-run-no-lock-during-traverse', 'tdb1-run-oldhash', 'tdb1-run-open-during-transaction', 'tdb1-run-readonly-check', 'tdb1-run-rwlock-check', 'tdb1-run-summary', 'tdb1-run-transaction-expand', 'tdb1-run-traverse-in-transaction', 'tdb1-run-wronghash-fail', 'tdb1-run-zero-append': + for f in 'tdb1-run-3G-file', 'tdb1-run-bad-tdb-header', 'tdb1-run', 'tdb1-run-check', 'tdb1-run-corrupt', 'tdb1-run-die-during-transaction', 'tdb1-run-endian', 'tdb1-run-incompatible', 'tdb1-run-nested-transactions', 'tdb1-run-nested-traverse', 'tdb1-run-no-lock-during-traverse', 'tdb1-run-oldhash', 'tdb1-run-open-during-transaction', 'tdb1-run-readonly-check', 'tdb1-run-rescue', 'tdb1-run-rescue-find_entry', 'tdb1-run-rwlock-check', 'tdb1-run-summary', 'tdb1-run-transaction-expand', 'tdb1-run-traverse-in-transaction', 'tdb1-run-wronghash-fail', 'tdb1-run-zero-append': cmd = "cd " + testdir + " && " + os.path.abspath(os.path.join(Utils.g_module.blddir, f)) + " > test-output 2>&1" print("..." + f) ret = samba_utils.RUN_COMMAND(cmd) -- 2.11.4.GIT