/*
Unix SMB/CIFS implementation.
- Samba database functions
- Copyright (C) Andrew Tridgell 1999-2000
- Copyright (C) Luke Kenneth Casson Leighton 2000
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2004
Copyright (C) Paul `Rusty' Russell 2000
Copyright (C) Jeremy Allison 2000-2003
- 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 2 of the License, or
- (at your option) any later version.
+ ** NOTE! The following LGPL license applies to the tdb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
- This program is distributed in the hope that it will be useful,
+ 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 2 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 General Public License for more details.
+ 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 General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+
+/* NOTE: If you use tdbs under valgrind, and in particular if you run
+ * tdbtorture, you may get spurious "uninitialized value" warnings. I
+ * think this is because valgrind doesn't understand that the mmap'd
+ * area may be written to by other processes. Memory can, from the
+ * point of view of the grinded process, spontaneously become
+ * initialized.
+ *
+ * I can think of a few solutions. [mbp 20030311]
+ *
+ * 1 - Write suppressions for Valgrind so that it doesn't complain
+ * about this. Probably the most reasonable but people need to
+ * remember to use them.
+ *
+ * 2 - Use IO not mmap when running under valgrind. Not so nice.
+ *
+ * 3 - Use the special valgrind macros to mark memory as valid at the
+ * right time. Probably too hard -- the process just doesn't know.
+ */
+
#ifdef STANDALONE
#if HAVE_CONFIG_H
#include <config.h>
#include "spinlock.h"
#else
#include "includes.h"
+
+#if defined(PARANOID_MALLOC_CHECKER)
+#ifdef malloc
+#undef malloc
+#endif
+
+#ifdef realloc
+#undef realloc
+#endif
+
+#ifdef calloc
+#undef calloc
+#endif
+
+#ifdef strdup
+#undef strdup
+#endif
+
+#ifdef strndup
+#undef strndup
+#endif
+
+#endif
+
#endif
#define TDB_MAGIC_FOOD "TDB file\n"
#define TDB_DEAD(r) ((r)->magic == TDB_DEAD_MAGIC)
#define TDB_BAD_MAGIC(r) ((r)->magic != TDB_MAGIC && !TDB_DEAD(r))
#define TDB_HASH_TOP(hash) (FREELIST_TOP + (BUCKET(hash)+1)*sizeof(tdb_off))
+#define TDB_DATA_START(hash_size) (TDB_HASH_TOP(hash_size-1) + TDB_SPINLOCK_SIZE(hash_size))
+
/* NB assumes there is a local variable called "tdb" that is the
* current context, also takes doubly-parenthesized print-style
tdb->fd, offset, rw_type, lck_type));
}
/* Was it an alarm timeout ? */
- if (errno == EINTR && palarm_fired && *palarm_fired)
+ if (errno == EINTR && palarm_fired && *palarm_fired) {
+ TDB_LOG((tdb, 5, "tdb_brlock timed out (fd=%d) at offset %d rw_type=%d lck_type=%d\n",
+ tdb->fd, offset, rw_type, lck_type));
return TDB_ERRCODE(TDB_ERR_LOCK_TIMEOUT, -1);
- /* Otherwise - generic lock error. */
- /* errno set by fcntl */
+ }
+ /* Otherwise - generic lock error. errno set by fcntl.
+ * EAGAIN is an expected return from non-blocking
+ * locks. */
+ if (errno != EAGAIN) {
+ TDB_LOG((tdb, 5, "tdb_brlock failed (fd=%d) at offset %d rw_type=%d lck_type=%d: %s\n",
+ tdb->fd, offset, rw_type, lck_type,
+ strerror(errno)));
+ }
return TDB_ERRCODE(TDB_ERR_LOCK, -1);
}
return 0;
if (tdb->locked[list+1].count == 0) {
if (!tdb->read_only && tdb->header.rwlocks) {
if (tdb_spinlock(tdb, list, ltype)) {
- TDB_LOG((tdb, 0, "tdb_lock spinlock failed on list ltype=%d\n",
+ TDB_LOG((tdb, 0, "tdb_lock spinlock failed on list %d ltype=%d\n",
list, ltype));
return -1;
}
return ret;
}
-/* This is based on the hash algorithm from gdbm */
-static u32 tdb_hash(TDB_DATA *key)
-{
- u32 value; /* Used to compute the hash value. */
- u32 i; /* Used to cycle through random values. */
-
- /* Set the initial value from the key size. */
- for (value = 0x238F13AF * key->dsize, i=0; i < key->dsize; i++)
- value = (value + (key->dptr[i] << (i*5 % 24)));
-
- return (1103515243 * value + 12345);
-}
-
/* check for an out of bounds access - if it is out of bounds then
see if the database has been expanded by someone else and expand
if necessary
if (rec->magic == TDB_MAGIC) {
/* this happens when a app is showdown while deleting a record - we should
not completely fail when this happens */
- TDB_LOG((tdb, 0,"rec_free_read non-free magic at offset=%d - fixing\n",
+ TDB_LOG((tdb, 0,"rec_free_read non-free magic 0x%x at offset=%d - fixing\n",
rec->magic, off));
rec->magic = TDB_FREE_MAGIC;
if (tdb_write(tdb, off, rec, sizeof(*rec)) == -1)
left:
/* Look left */
left = offset - sizeof(tdb_off);
- if (left > TDB_HASH_TOP(tdb->header.hash_size-1)) {
+ if (left > TDB_DATA_START(tdb->header.hash_size)) {
struct list_struct l;
tdb_off leftsize;
-
+
/* Read in tailer and jump back to header */
if (ofs_read(tdb, left, &leftsize) == -1) {
TDB_LOG((tdb, 0, "tdb_free: left offset read failed at %u\n", left));
tdb_off rec_ptr, last_ptr, newrec_ptr;
struct list_struct newrec;
+ memset(&newrec, '\0', sizeof(newrec));
+
if (tdb_lock(tdb, -1, F_WRLCK) == -1)
return 0;
return TDB_ERRCODE(TDB_ERR_NOEXIST, 0);
}
-/* If they do lockkeys, check that this hash is one they locked */
-static int tdb_keylocked(TDB_CONTEXT *tdb, u32 hash)
-{
- u32 i;
- if (!tdb->lockedkeys)
- return 1;
- for (i = 0; i < tdb->lockedkeys[0]; i++)
- if (tdb->lockedkeys[i+1] == hash)
- return 1;
- return TDB_ERRCODE(TDB_ERR_NOLOCK, 0);
-}
-
/* As tdb_find, but if you succeed, keep the lock */
-static tdb_off tdb_find_lock(TDB_CONTEXT *tdb, TDB_DATA key, int locktype,
+static tdb_off tdb_find_lock_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash, int locktype,
struct list_struct *rec)
{
- u32 hash, rec_ptr;
+ u32 rec_ptr;
- hash = tdb_hash(&key);
- if (!tdb_keylocked(tdb, hash))
- return 0;
if (tdb_lock(tdb, BUCKET(hash), locktype) == -1)
return 0;
if (!(rec_ptr = tdb_find(tdb, key, hash, rec)))
on failure return -1.
*/
-static int tdb_update(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf)
+static int tdb_update_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash, TDB_DATA dbuf)
{
struct list_struct rec;
tdb_off rec_ptr;
/* find entry */
- if (!(rec_ptr = tdb_find(tdb, key, tdb_hash(&key), &rec)))
+ if (!(rec_ptr = tdb_find(tdb, key, hash, &rec)))
return -1;
/* must be long enough key, data and tailer */
tdb_off rec_ptr;
struct list_struct rec;
TDB_DATA ret;
+ u32 hash;
/* find which hash bucket it is in */
- if (!(rec_ptr = tdb_find_lock(tdb,key,F_RDLCK,&rec)))
+ hash = tdb->hash_fn(&key);
+ if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec)))
return tdb_null;
if (rec.data_len)
this doesn't match the conventions in the rest of this module, but is
compatible with gdbm
*/
-int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key)
+static int tdb_exists_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash)
{
struct list_struct rec;
- if (tdb_find_lock(tdb, key, F_RDLCK, &rec) == 0)
+ if (tdb_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0)
return 0;
tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK);
return 1;
}
+int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key)
+{
+ u32 hash = tdb->hash_fn(&key);
+ return tdb_exists_hash(tdb, key, hash);
+}
+
/* record lock stops delete underneath */
static int lock_record(TDB_CONTEXT *tdb, tdb_off off)
{
{
int want_next = (tlock->off != 0);
- /* No traversal allows if you've called tdb_lockkeys() */
- if (tdb->lockedkeys)
- return TDB_ERRCODE(TDB_ERR_NOLOCK, -1);
-
/* Lock each chain from the start one. */
for (; tlock->hash < tdb->header.hash_size; tlock->hash++) {
if (tdb_lock(tdb, tlock->hash, F_WRLCK) == -1)
/* Try to clean dead ones from old traverses */
current = tlock->off;
tlock->off = rec->next;
- if (do_delete(tdb, current, rec) != 0)
+ if (!tdb->read_only &&
+ do_delete(tdb, current, rec) != 0)
goto fail;
}
tdb_unlock(tdb, tlock->hash, F_WRLCK);
if fn is NULL then it is not called
a non-zero return value from fn() indicates that the traversal should stop
*/
-int tdb_traverse(TDB_CONTEXT *tdb, tdb_traverse_func fn, void *state)
+int tdb_traverse(TDB_CONTEXT *tdb, tdb_traverse_func fn, void *private)
{
TDB_DATA key, dbuf;
struct list_struct rec;
ret = -1;
goto out;
}
- if (fn && fn(tdb, key, dbuf, state)) {
+ if (fn && fn(tdb, key, dbuf, private)) {
/* They want us to terminate traversal */
ret = count;
if (unlock_record(tdb, tl.off) != 0) {
if (!tdb->travlocks.off) {
/* No previous element: do normal find, and lock record */
- tdb->travlocks.off = tdb_find_lock(tdb, oldkey, F_WRLCK, &rec);
+ tdb->travlocks.off = tdb_find_lock_hash(tdb, oldkey, tdb->hash_fn(&oldkey), F_WRLCK, &rec);
if (!tdb->travlocks.off)
return tdb_null;
tdb->travlocks.hash = BUCKET(rec.full_hash);
}
/* delete an entry in the database given a key */
-int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key)
+static int tdb_delete_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash)
{
tdb_off rec_ptr;
struct list_struct rec;
int ret;
- if (!(rec_ptr = tdb_find_lock(tdb, key, F_WRLCK, &rec)))
+ if (!(rec_ptr = tdb_find_lock_hash(tdb, key, hash, F_WRLCK, &rec)))
return -1;
ret = do_delete(tdb, rec_ptr, &rec);
if (tdb_unlock(tdb, BUCKET(rec.full_hash), F_WRLCK) != 0)
return ret;
}
+int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key)
+{
+ u32 hash = tdb->hash_fn(&key);
+ return tdb_delete_hash(tdb, key, hash);
+}
+
/* store an element in the database, replacing any existing element
with the same key
int ret = 0;
/* find which hash bucket it is in */
- hash = tdb_hash(&key);
- if (!tdb_keylocked(tdb, hash))
- return -1;
+ hash = tdb->hash_fn(&key);
if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
return -1;
/* check for it existing, on insert. */
if (flag == TDB_INSERT) {
- if (tdb_exists(tdb, key)) {
+ if (tdb_exists_hash(tdb, key, hash)) {
tdb->ecode = TDB_ERR_EXISTS;
goto fail;
}
} else {
/* first try in-place update, on modify or replace. */
- if (tdb_update(tdb, key, dbuf) == 0)
+ if (tdb_update_hash(tdb, key, hash, dbuf) == 0)
goto out;
- if (flag == TDB_MODIFY && tdb->ecode == TDB_ERR_NOEXIST)
+ if (tdb->ecode == TDB_ERR_NOEXIST &&
+ flag == TDB_MODIFY) {
+ /* if the record doesn't exist and we are in TDB_MODIFY mode then
+ we should fail the store */
goto fail;
}
+ }
/* reset the error code potentially set by the tdb_update() */
tdb->ecode = TDB_SUCCESS;
care. Doing this first reduces fragmentation, and avoids
coalescing with `allocated' block before it's updated. */
if (flag != TDB_INSERT)
- tdb_delete(tdb, key);
+ tdb_delete_hash(tdb, key, hash);
/* Copy key+value *before* allocating free space in case malloc
fails and we are left with a dead spot in the tdb. */
if (dbuf.dsize)
memcpy(p+key.dsize, dbuf.dptr, dbuf.dsize);
- /* now we're into insert / modify / replace of a record which
- * we know could not be optimised by an in-place store (for
- * various reasons). */
+ /* we have to allocate some space */
if (!(rec_ptr = tdb_allocate(tdb, key.dsize + dbuf.dsize, &rec)))
goto fail;
is <= the old data size and the key exists.
on failure return -1. Record must be locked before calling.
*/
-static int tdb_append_inplace(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA new_dbuf)
+static int tdb_append_inplace(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash, TDB_DATA new_dbuf)
{
struct list_struct rec;
tdb_off rec_ptr;
/* find entry */
- if (!(rec_ptr = tdb_find(tdb, key, tdb_hash(&key), &rec)))
+ if (!(rec_ptr = tdb_find(tdb, key, hash, &rec)))
return -1;
/* Append of 0 is always ok. */
size_t new_data_size = 0;
/* find which hash bucket it is in */
- hash = tdb_hash(&key);
- if (!tdb_keylocked(tdb, hash))
- return -1;
+ hash = tdb->hash_fn(&key);
if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
return -1;
/* first try in-place. */
- if (tdb_append_inplace(tdb, key, new_dbuf) == 0)
+ if (tdb_append_inplace(tdb, key, hash, new_dbuf) == 0)
goto out;
/* reset the error code potentially set by the tdb_append_inplace() */
care. Doing this first reduces fragmentation, and avoids
coalescing with `allocated' block before it's updated. */
- tdb_delete(tdb, key);
+ tdb_delete_hash(tdb, key, hash);
if (!(rec_ptr = tdb_allocate(tdb, key.dsize + new_data_size, &rec)))
goto fail;
return 0;
}
+/* This is based on the hash algorithm from gdbm */
+static u32 default_tdb_hash(TDB_DATA *key)
+{
+ u32 value; /* Used to compute the hash value. */
+ u32 i; /* Used to cycle through random values. */
+
+ /* Set the initial value from the key size. */
+ for (value = 0x238F13AF * key->dsize, i=0; i < key->dsize; i++)
+ value = (value + (key->dptr[i] << (i*5 % 24)));
+
+ return (1103515243 * value + 12345);
+}
+
/* open the database, creating it if necessary
The open_flags and mode are passed straight to the open call on the
TDB_CONTEXT *tdb_open(const char *name, int hash_size, int tdb_flags,
int open_flags, mode_t mode)
{
- return tdb_open_ex(name, hash_size, tdb_flags, open_flags, mode, NULL);
+ return tdb_open_ex(name, hash_size, tdb_flags, open_flags, mode, NULL, NULL);
}
TDB_CONTEXT *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
int open_flags, mode_t mode,
- tdb_log_func log_fn)
+ tdb_log_func log_fn,
+ tdb_hash_func hash_fn)
{
TDB_CONTEXT *tdb;
struct stat st;
- int rev = 0, locked;
+ int rev = 0, locked = 0;
unsigned char *vp;
u32 vertest;
tdb->fd = -1;
tdb->name = NULL;
tdb->map_ptr = NULL;
- tdb->lockedkeys = NULL;
tdb->flags = tdb_flags;
tdb->open_flags = open_flags;
tdb->log_fn = log_fn;
-
+ tdb->hash_fn = hash_fn ? hash_fn : default_tdb_hash;
+
if ((open_flags & O_ACCMODE) == O_WRONLY) {
TDB_LOG((tdb, 0, "tdb_open_ex: can't open tdb %s write-only\n",
name));
}
/* we need to zero database if we are the only one with it open */
- if ((locked = (tdb_brlock(tdb, ACTIVE_LOCK, F_WRLCK, F_SETLK, 0) == 0))
- && (tdb_flags & TDB_CLEAR_IF_FIRST)) {
+ if ((tdb_flags & TDB_CLEAR_IF_FIRST) &&
+ (locked = (tdb_brlock(tdb, ACTIVE_LOCK, F_WRLCK, F_SETLK, 0) == 0))) {
open_flags |= O_CREAT;
if (ftruncate(tdb->fd, 0) == -1) {
TDB_LOG((tdb, 0, "tdb_open_ex: "
if (tdb_already_open(st.st_dev, st.st_ino)) {
TDB_LOG((tdb, 2, "tdb_open_ex: "
"%s (%d,%d) is already open in this process\n",
- name, st.st_dev, st.st_ino));
+ name, (int)st.st_dev, (int)st.st_ino));
errno = EBUSY;
goto fail;
}
name, strerror(errno)));
goto fail;
}
+
}
- /* leave this lock in place to indicate it's in use */
- if (tdb_brlock(tdb, ACTIVE_LOCK, F_RDLCK, F_SETLKW, 0) == -1)
- goto fail;
+
+ /* We always need to do this if the CLEAR_IF_FIRST flag is set, even if
+ we didn't get the initial exclusive lock as we need to let all other
+ users know we're using it. */
+
+ if (tdb_flags & TDB_CLEAR_IF_FIRST) {
+ /* leave this lock in place to indicate it's in use */
+ if (tdb_brlock(tdb, ACTIVE_LOCK, F_RDLCK, F_SETLKW, 0) == -1)
+ goto fail;
+ }
+
internal:
/* Internal (memory-only) databases skip all the code above to
if (tdb->fd != -1)
ret = close(tdb->fd);
SAFE_FREE(tdb->locked);
- SAFE_FREE(tdb->lockedkeys);
/* Remove from contexts list */
for (i = &tdbs; *i; i = &(*i)->next) {
/* There are no locks on read-only dbs */
if (tdb->read_only)
return TDB_ERRCODE(TDB_ERR_LOCK, -1);
- if (tdb->lockedkeys)
- return TDB_ERRCODE(TDB_ERR_NOLOCK, -1);
for (i = 0; i < tdb->header.hash_size; i++)
if (tdb_lock(tdb, i, F_WRLCK))
break;
tdb_unlock(tdb, i, F_WRLCK);
}
-int tdb_lockkeys(TDB_CONTEXT *tdb, u32 number, TDB_DATA keys[])
-{
- u32 i, j, hash;
-
- /* Can't lock more keys if already locked */
- if (tdb->lockedkeys)
- return TDB_ERRCODE(TDB_ERR_NOLOCK, -1);
- if (!(tdb->lockedkeys = malloc(sizeof(u32) * (number+1))))
- return TDB_ERRCODE(TDB_ERR_OOM, -1);
- /* First number in array is # keys */
- tdb->lockedkeys[0] = number;
-
- /* Insertion sort by bucket */
- for (i = 0; i < number; i++) {
- hash = tdb_hash(&keys[i]);
- for (j = 0; j < i && BUCKET(tdb->lockedkeys[j+1]) < BUCKET(hash); j++);
- memmove(&tdb->lockedkeys[j+2], &tdb->lockedkeys[j+1], sizeof(u32) * (i-j));
- tdb->lockedkeys[j+1] = hash;
- }
- /* Finally, lock in order */
- for (i = 0; i < number; i++)
- if (tdb_lock(tdb, i, F_WRLCK))
- break;
-
- /* If error, release locks we have... */
- if (i < number) {
- for ( j = 0; j < i; j++)
- tdb_unlock(tdb, j, F_WRLCK);
- SAFE_FREE(tdb->lockedkeys);
- return TDB_ERRCODE(TDB_ERR_NOLOCK, -1);
- }
- return 0;
-}
-
-/* Unlock the keys previously locked by tdb_lockkeys() */
-void tdb_unlockkeys(TDB_CONTEXT *tdb)
-{
- u32 i;
- if (!tdb->lockedkeys)
- return;
- for (i = 0; i < tdb->lockedkeys[0]; i++)
- tdb_unlock(tdb, tdb->lockedkeys[i+1], F_WRLCK);
- SAFE_FREE(tdb->lockedkeys);
-}
-
/* lock/unlock one hash chain. This is meant to be used to reduce
contention - it cannot guarantee how many records will be locked */
int tdb_chainlock(TDB_CONTEXT *tdb, TDB_DATA key)
{
- return tdb_lock(tdb, BUCKET(tdb_hash(&key)), F_WRLCK);
+ return tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
}
int tdb_chainunlock(TDB_CONTEXT *tdb, TDB_DATA key)
{
- return tdb_unlock(tdb, BUCKET(tdb_hash(&key)), F_WRLCK);
+ return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
}
int tdb_chainlock_read(TDB_CONTEXT *tdb, TDB_DATA key)
{
- return tdb_lock(tdb, BUCKET(tdb_hash(&key)), F_RDLCK);
+ return tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
}
int tdb_chainunlock_read(TDB_CONTEXT *tdb, TDB_DATA key)
{
- return tdb_unlock(tdb, BUCKET(tdb_hash(&key)), F_RDLCK);
+ return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
}
tdb->log_fn = fn;
}
-
-/* reopen a tdb - this is used after a fork to ensure that we have an independent
+/* reopen a tdb - this can be used after a fork to ensure that we have an independent
seek pointer from our parent and to re-establish locks */
int tdb_reopen(TDB_CONTEXT *tdb)
{
struct stat st;
+ if (tdb->flags & TDB_INTERNAL)
+ return 0; /* Nothing to do. */
if (tdb_munmap(tdb) != 0) {
TDB_LOG((tdb, 0, "tdb_reopen: munmap failed (%s)\n", strerror(errno)));
goto fail;
goto fail;
}
tdb_mmap(tdb);
- if (tdb_brlock(tdb, ACTIVE_LOCK, F_RDLCK, F_SETLKW, 0) == -1) {
+ if ((tdb->flags & TDB_CLEAR_IF_FIRST) && (tdb_brlock(tdb, ACTIVE_LOCK, F_RDLCK, F_SETLKW, 0) == -1)) {
TDB_LOG((tdb, 0, "tdb_reopen: failed to obtain active lock\n"));
goto fail;
}
TDB_CONTEXT *tdb;
for (tdb=tdbs; tdb; tdb = tdb->next) {
- if (tdb_reopen(tdb) != 0) return -1;
+ /* Ensure no clear-if-first. */
+ tdb->flags &= ~TDB_CLEAR_IF_FIRST;
+ if (tdb_reopen(tdb) != 0)
+ return -1;
}
return 0;