summaryrefslogtreecommitdiff
path: root/lib/tdb2/tdb1_tdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tdb2/tdb1_tdb.c')
-rw-r--r--lib/tdb2/tdb1_tdb.c993
1 files changed, 993 insertions, 0 deletions
diff --git a/lib/tdb2/tdb1_tdb.c b/lib/tdb2/tdb1_tdb.c
new file mode 100644
index 0000000000..1ada443d60
--- /dev/null
+++ b/lib/tdb2/tdb1_tdb.c
@@ -0,0 +1,993 @@
+ /*
+ Unix SMB/CIFS implementation.
+
+ trivial database library
+
+ Copyright (C) Andrew Tridgell 1999-2005
+ Copyright (C) Paul `Rusty' Russell 2000
+ Copyright (C) Jeremy Allison 2000-2003
+
+ ** 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "tdb1_private.h"
+
+_PUBLIC_ TDB1_DATA tdb1_null;
+
+/*
+ non-blocking increment of the tdb sequence number if the tdb has been opened using
+ the TDB1_SEQNUM flag
+*/
+_PUBLIC_ void tdb1_increment_seqnum_nonblock(struct tdb1_context *tdb)
+{
+ tdb1_off_t seqnum=0;
+
+ if (!(tdb->flags & TDB1_SEQNUM)) {
+ return;
+ }
+
+ /* we ignore errors from this, as we have no sane way of
+ dealing with them.
+ */
+ tdb1_ofs_read(tdb, TDB1_SEQNUM_OFS, &seqnum);
+ seqnum++;
+ tdb1_ofs_write(tdb, TDB1_SEQNUM_OFS, &seqnum);
+}
+
+/*
+ increment the tdb sequence number if the tdb has been opened using
+ the TDB1_SEQNUM flag
+*/
+static void tdb1_increment_seqnum(struct tdb1_context *tdb)
+{
+ if (!(tdb->flags & TDB1_SEQNUM)) {
+ return;
+ }
+
+ if (tdb1_nest_lock(tdb, TDB1_SEQNUM_OFS, F_WRLCK,
+ TDB1_LOCK_WAIT|TDB1_LOCK_PROBE) != 0) {
+ return;
+ }
+
+ tdb1_increment_seqnum_nonblock(tdb);
+
+ tdb1_nest_unlock(tdb, TDB1_SEQNUM_OFS, F_WRLCK, false);
+}
+
+static int tdb1_key_compare(TDB1_DATA key, TDB1_DATA data, void *private_data)
+{
+ return memcmp(data.dptr, key.dptr, data.dsize);
+}
+
+/* Returns 0 on fail. On success, return offset of record, and fills
+ in rec */
+static tdb1_off_t tdb1_find(struct tdb1_context *tdb, TDB1_DATA key, uint32_t hash,
+ struct tdb1_record *r)
+{
+ tdb1_off_t rec_ptr;
+
+ /* read in the hash top */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1)
+ return 0;
+
+ /* keep looking until we find the right record */
+ while (rec_ptr) {
+ if (tdb1_rec_read(tdb, rec_ptr, r) == -1)
+ return 0;
+
+ if (!TDB1_DEAD(r) && hash==r->full_hash
+ && key.dsize==r->key_len
+ && tdb1_parse_data(tdb, key, rec_ptr + sizeof(*r),
+ r->key_len, tdb1_key_compare,
+ NULL) == 0) {
+ return rec_ptr;
+ }
+ /* detect tight infinite loop */
+ if (rec_ptr == r->next) {
+ tdb->ecode = TDB1_ERR_CORRUPT;
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "tdb1_find: loop detected.\n"));
+ return 0;
+ }
+ rec_ptr = r->next;
+ }
+ tdb->ecode = TDB1_ERR_NOEXIST;
+ return 0;
+}
+
+/* As tdb1_find, but if you succeed, keep the lock */
+tdb1_off_t tdb1_find_lock_hash(struct tdb1_context *tdb, TDB1_DATA key, uint32_t hash, int locktype,
+ struct tdb1_record *rec)
+{
+ uint32_t rec_ptr;
+
+ if (tdb1_lock(tdb, TDB1_BUCKET(hash), locktype) == -1)
+ return 0;
+ if (!(rec_ptr = tdb1_find(tdb, key, hash, rec)))
+ tdb1_unlock(tdb, TDB1_BUCKET(hash), locktype);
+ return rec_ptr;
+}
+
+static TDB1_DATA _tdb1_fetch(struct tdb1_context *tdb, TDB1_DATA key);
+
+/* update an entry in place - this only works if the new data size
+ is <= the old data size and the key exists.
+ on failure return -1.
+*/
+static int tdb1_update_hash(struct tdb1_context *tdb, TDB1_DATA key, uint32_t hash, TDB1_DATA dbuf)
+{
+ struct tdb1_record rec;
+ tdb1_off_t rec_ptr;
+
+ /* find entry */
+ if (!(rec_ptr = tdb1_find(tdb, key, hash, &rec)))
+ return -1;
+
+ /* it could be an exact duplicate of what is there - this is
+ * surprisingly common (eg. with a ldb re-index). */
+ if (rec.key_len == key.dsize &&
+ rec.data_len == dbuf.dsize &&
+ rec.full_hash == hash) {
+ TDB1_DATA data = _tdb1_fetch(tdb, key);
+ if (data.dsize == dbuf.dsize &&
+ memcmp(data.dptr, dbuf.dptr, data.dsize) == 0) {
+ if (data.dptr) {
+ free(data.dptr);
+ }
+ return 0;
+ }
+ if (data.dptr) {
+ free(data.dptr);
+ }
+ }
+
+ /* must be long enough key, data and tailer */
+ if (rec.rec_len < key.dsize + dbuf.dsize + sizeof(tdb1_off_t)) {
+ tdb->ecode = TDB1_SUCCESS; /* Not really an error */
+ return -1;
+ }
+
+ if (tdb->methods->tdb1_write(tdb, rec_ptr + sizeof(rec) + rec.key_len,
+ dbuf.dptr, dbuf.dsize) == -1)
+ return -1;
+
+ if (dbuf.dsize != rec.data_len) {
+ /* update size */
+ rec.data_len = dbuf.dsize;
+ return tdb1_rec_write(tdb, rec_ptr, &rec);
+ }
+
+ return 0;
+}
+
+/* find an entry in the database given a key */
+/* If an entry doesn't exist tdb1_err will be set to
+ * TDB1_ERR_NOEXIST. If a key has no data attached
+ * then the TDB1_DATA will have zero length but
+ * a non-zero pointer
+ */
+static TDB1_DATA _tdb1_fetch(struct tdb1_context *tdb, TDB1_DATA key)
+{
+ tdb1_off_t rec_ptr;
+ struct tdb1_record rec;
+ TDB1_DATA ret;
+ uint32_t hash;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (!(rec_ptr = tdb1_find_lock_hash(tdb,key,hash,F_RDLCK,&rec)))
+ return tdb1_null;
+
+ ret.dptr = tdb1_alloc_read(tdb, rec_ptr + sizeof(rec) + rec.key_len,
+ rec.data_len);
+ ret.dsize = rec.data_len;
+ tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK);
+ return ret;
+}
+
+_PUBLIC_ TDB1_DATA tdb1_fetch(struct tdb1_context *tdb, TDB1_DATA key)
+{
+ TDB1_DATA ret = _tdb1_fetch(tdb, key);
+
+ return ret;
+}
+
+/*
+ * Find an entry in the database and hand the record's data to a parsing
+ * function. The parsing function is executed under the chain read lock, so it
+ * should be fast and should not block on other syscalls.
+ *
+ * DON'T CALL OTHER TDB CALLS FROM THE PARSER, THIS MIGHT LEAD TO SEGFAULTS.
+ *
+ * For mmapped tdb's that do not have a transaction open it points the parsing
+ * function directly at the mmap area, it avoids the malloc/memcpy in this
+ * case. If a transaction is open or no mmap is available, it has to do
+ * malloc/read/parse/free.
+ *
+ * This is interesting for all readers of potentially large data structures in
+ * the tdb records, ldb indexes being one example.
+ *
+ * Return -1 if the record was not found.
+ */
+
+_PUBLIC_ int tdb1_parse_record(struct tdb1_context *tdb, TDB1_DATA key,
+ int (*parser)(TDB1_DATA key, TDB1_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ tdb1_off_t rec_ptr;
+ struct tdb1_record rec;
+ int ret;
+ uint32_t hash;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+
+ if (!(rec_ptr = tdb1_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) {
+ /* record not found */
+ tdb->ecode = TDB1_ERR_NOEXIST;
+ return -1;
+ }
+
+ ret = tdb1_parse_data(tdb, key, rec_ptr + sizeof(rec) + rec.key_len,
+ rec.data_len, parser, private_data);
+
+ tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK);
+
+ return ret;
+}
+
+/* check if an entry in the database exists
+
+ note that 1 is returned if the key is found and 0 is returned if not found
+ this doesn't match the conventions in the rest of this module, but is
+ compatible with gdbm
+*/
+static int tdb1_exists_hash(struct tdb1_context *tdb, TDB1_DATA key, uint32_t hash)
+{
+ struct tdb1_record rec;
+
+ if (tdb1_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0)
+ return 0;
+ tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK);
+ return 1;
+}
+
+_PUBLIC_ int tdb1_exists(struct tdb1_context *tdb, TDB1_DATA key)
+{
+ uint32_t hash = tdb->hash_fn(&key);
+ int ret;
+
+ ret = tdb1_exists_hash(tdb, key, hash);
+ return ret;
+}
+
+/* actually delete an entry in the database given the offset */
+int tdb1_do_delete(struct tdb1_context *tdb, tdb1_off_t rec_ptr, struct tdb1_record *rec)
+{
+ tdb1_off_t last_ptr, i;
+ struct tdb1_record lastrec;
+
+ if (tdb->read_only || tdb->traverse_read) return -1;
+
+ if (((tdb->traverse_write != 0) && (!TDB1_DEAD(rec))) ||
+ tdb1_write_lock_record(tdb, rec_ptr) == -1) {
+ /* Someone traversing here: mark it as dead */
+ rec->magic = TDB1_DEAD_MAGIC;
+ return tdb1_rec_write(tdb, rec_ptr, rec);
+ }
+ if (tdb1_write_unlock_record(tdb, rec_ptr) != 0)
+ return -1;
+
+ /* find previous record in hash chain */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(rec->full_hash), &i) == -1)
+ return -1;
+ for (last_ptr = 0; i != rec_ptr; last_ptr = i, i = lastrec.next)
+ if (tdb1_rec_read(tdb, i, &lastrec) == -1)
+ return -1;
+
+ /* unlink it: next ptr is at start of record. */
+ if (last_ptr == 0)
+ last_ptr = TDB1_HASH_TOP(rec->full_hash);
+ if (tdb1_ofs_write(tdb, last_ptr, &rec->next) == -1)
+ return -1;
+
+ /* recover the space */
+ if (tdb1_free(tdb, rec_ptr, rec) == -1)
+ return -1;
+ return 0;
+}
+
+static int tdb1_count_dead(struct tdb1_context *tdb, uint32_t hash)
+{
+ int res = 0;
+ tdb1_off_t rec_ptr;
+ struct tdb1_record rec;
+
+ /* read in the hash top */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1)
+ return 0;
+
+ while (rec_ptr) {
+ if (tdb1_rec_read(tdb, rec_ptr, &rec) == -1)
+ return 0;
+
+ if (rec.magic == TDB1_DEAD_MAGIC) {
+ res += 1;
+ }
+ rec_ptr = rec.next;
+ }
+ return res;
+}
+
+/*
+ * Purge all DEAD records from a hash chain
+ */
+static int tdb1_purge_dead(struct tdb1_context *tdb, uint32_t hash)
+{
+ int res = -1;
+ struct tdb1_record rec;
+ tdb1_off_t rec_ptr;
+
+ if (tdb1_lock(tdb, -1, F_WRLCK) == -1) {
+ return -1;
+ }
+
+ /* read in the hash top */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1)
+ goto fail;
+
+ while (rec_ptr) {
+ tdb1_off_t next;
+
+ if (tdb1_rec_read(tdb, rec_ptr, &rec) == -1) {
+ goto fail;
+ }
+
+ next = rec.next;
+
+ if (rec.magic == TDB1_DEAD_MAGIC
+ && tdb1_do_delete(tdb, rec_ptr, &rec) == -1) {
+ goto fail;
+ }
+ rec_ptr = next;
+ }
+ res = 0;
+ fail:
+ tdb1_unlock(tdb, -1, F_WRLCK);
+ return res;
+}
+
+/* delete an entry in the database given a key */
+static int tdb1_delete_hash(struct tdb1_context *tdb, TDB1_DATA key, uint32_t hash)
+{
+ tdb1_off_t rec_ptr;
+ struct tdb1_record rec;
+ int ret;
+
+ if (tdb->max_dead_records != 0) {
+
+ /*
+ * Allow for some dead records per hash chain, mainly for
+ * tdb's with a very high create/delete rate like locking.tdb.
+ */
+
+ if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ if (tdb1_count_dead(tdb, hash) >= tdb->max_dead_records) {
+ /*
+ * Don't let the per-chain freelist grow too large,
+ * delete all existing dead records
+ */
+ tdb1_purge_dead(tdb, hash);
+ }
+
+ if (!(rec_ptr = tdb1_find(tdb, key, hash, &rec))) {
+ tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK);
+ return -1;
+ }
+
+ /*
+ * Just mark the record as dead.
+ */
+ rec.magic = TDB1_DEAD_MAGIC;
+ ret = tdb1_rec_write(tdb, rec_ptr, &rec);
+ }
+ else {
+ if (!(rec_ptr = tdb1_find_lock_hash(tdb, key, hash, F_WRLCK,
+ &rec)))
+ return -1;
+
+ ret = tdb1_do_delete(tdb, rec_ptr, &rec);
+ }
+
+ if (ret == 0) {
+ tdb1_increment_seqnum(tdb);
+ }
+
+ if (tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_WRLCK) != 0)
+ TDB1_LOG((tdb, TDB1_DEBUG_WARNING, "tdb1_delete: WARNING tdb1_unlock failed!\n"));
+ return ret;
+}
+
+_PUBLIC_ int tdb1_delete(struct tdb1_context *tdb, TDB1_DATA key)
+{
+ uint32_t hash = tdb->hash_fn(&key);
+ int ret;
+
+ ret = tdb1_delete_hash(tdb, key, hash);
+ return ret;
+}
+
+/*
+ * See if we have a dead record around with enough space
+ */
+static tdb1_off_t tdb1_find_dead(struct tdb1_context *tdb, uint32_t hash,
+ struct tdb1_record *r, tdb1_len_t length)
+{
+ tdb1_off_t rec_ptr;
+
+ /* read in the hash top */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1)
+ return 0;
+
+ /* keep looking until we find the right record */
+ while (rec_ptr) {
+ if (tdb1_rec_read(tdb, rec_ptr, r) == -1)
+ return 0;
+
+ if (TDB1_DEAD(r) && r->rec_len >= length) {
+ /*
+ * First fit for simple coding, TODO: change to best
+ * fit
+ */
+ return rec_ptr;
+ }
+ rec_ptr = r->next;
+ }
+ return 0;
+}
+
+static int _tdb1_store(struct tdb1_context *tdb, TDB1_DATA key,
+ TDB1_DATA dbuf, int flag, uint32_t hash)
+{
+ struct tdb1_record rec;
+ tdb1_off_t rec_ptr;
+ char *p = NULL;
+ int ret = -1;
+
+ /* check for it existing, on insert. */
+ if (flag == TDB1_INSERT) {
+ if (tdb1_exists_hash(tdb, key, hash)) {
+ tdb->ecode = TDB1_ERR_EXISTS;
+ goto fail;
+ }
+ } else {
+ /* first try in-place update, on modify or replace. */
+ if (tdb1_update_hash(tdb, key, hash, dbuf) == 0) {
+ goto done;
+ }
+ if (tdb->ecode == TDB1_ERR_NOEXIST &&
+ flag == TDB1_MODIFY) {
+ /* if the record doesn't exist and we are in TDB1_MODIFY mode then
+ we should fail the store */
+ goto fail;
+ }
+ }
+ /* reset the error code potentially set by the tdb1_update() */
+ tdb->ecode = TDB1_SUCCESS;
+
+ /* delete any existing record - if it doesn't exist we don't
+ care. Doing this first reduces fragmentation, and avoids
+ coalescing with `allocated' block before it's updated. */
+ if (flag != TDB1_INSERT)
+ tdb1_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 (!(p = (char *)malloc(key.dsize + dbuf.dsize))) {
+ tdb->ecode = TDB1_ERR_OOM;
+ goto fail;
+ }
+
+ memcpy(p, key.dptr, key.dsize);
+ if (dbuf.dsize)
+ memcpy(p+key.dsize, dbuf.dptr, dbuf.dsize);
+
+ if (tdb->max_dead_records != 0) {
+ /*
+ * Allow for some dead records per hash chain, look if we can
+ * find one that can hold the new record. We need enough space
+ * for key, data and tailer. If we find one, we don't have to
+ * consult the central freelist.
+ */
+ rec_ptr = tdb1_find_dead(
+ tdb, hash, &rec,
+ key.dsize + dbuf.dsize + sizeof(tdb1_off_t));
+
+ if (rec_ptr != 0) {
+ rec.key_len = key.dsize;
+ rec.data_len = dbuf.dsize;
+ rec.full_hash = hash;
+ rec.magic = TDB1_MAGIC;
+ if (tdb1_rec_write(tdb, rec_ptr, &rec) == -1
+ || tdb->methods->tdb1_write(
+ tdb, rec_ptr + sizeof(rec),
+ p, key.dsize + dbuf.dsize) == -1) {
+ goto fail;
+ }
+ goto done;
+ }
+ }
+
+ /*
+ * We have to allocate some space from the freelist, so this means we
+ * have to lock it. Use the chance to purge all the DEAD records from
+ * the hash chain under the freelist lock.
+ */
+
+ if (tdb1_lock(tdb, -1, F_WRLCK) == -1) {
+ goto fail;
+ }
+
+ if ((tdb->max_dead_records != 0)
+ && (tdb1_purge_dead(tdb, hash) == -1)) {
+ tdb1_unlock(tdb, -1, F_WRLCK);
+ goto fail;
+ }
+
+ /* we have to allocate some space */
+ rec_ptr = tdb1_allocate(tdb, key.dsize + dbuf.dsize, &rec);
+
+ tdb1_unlock(tdb, -1, F_WRLCK);
+
+ if (rec_ptr == 0) {
+ goto fail;
+ }
+
+ /* Read hash top into next ptr */
+ if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec.next) == -1)
+ goto fail;
+
+ rec.key_len = key.dsize;
+ rec.data_len = dbuf.dsize;
+ rec.full_hash = hash;
+ rec.magic = TDB1_MAGIC;
+
+ /* write out and point the top of the hash chain at it */
+ if (tdb1_rec_write(tdb, rec_ptr, &rec) == -1
+ || tdb->methods->tdb1_write(tdb, rec_ptr+sizeof(rec), p, key.dsize+dbuf.dsize)==-1
+ || tdb1_ofs_write(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) {
+ /* Need to tdb1_unallocate() here */
+ goto fail;
+ }
+
+ done:
+ ret = 0;
+ fail:
+ if (ret == 0) {
+ tdb1_increment_seqnum(tdb);
+ }
+
+ SAFE_FREE(p);
+ return ret;
+}
+
+/* store an element in the database, replacing any existing element
+ with the same key
+
+ return 0 on success, -1 on failure
+*/
+_PUBLIC_ int tdb1_store(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA dbuf, int flag)
+{
+ uint32_t hash;
+ int ret;
+
+ if (tdb->read_only || tdb->traverse_read) {
+ tdb->ecode = TDB1_ERR_RDONLY;
+ return -1;
+ }
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ ret = _tdb1_store(tdb, key, dbuf, flag, hash);
+ tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK);
+ return ret;
+}
+
+/* Append to an entry. Create if not exist. */
+_PUBLIC_ int tdb1_append(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA new_dbuf)
+{
+ uint32_t hash;
+ TDB1_DATA dbuf;
+ int ret = -1;
+
+ /* find which hash bucket it is in */
+ hash = tdb->hash_fn(&key);
+ if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1)
+ return -1;
+
+ dbuf = _tdb1_fetch(tdb, key);
+
+ if (dbuf.dptr == NULL) {
+ dbuf.dptr = (unsigned char *)malloc(new_dbuf.dsize);
+ } else {
+ unsigned int new_len = dbuf.dsize + new_dbuf.dsize;
+ unsigned char *new_dptr;
+
+ /* realloc '0' is special: don't do that. */
+ if (new_len == 0)
+ new_len = 1;
+ new_dptr = (unsigned char *)realloc(dbuf.dptr, new_len);
+ if (new_dptr == NULL) {
+ free(dbuf.dptr);
+ }
+ dbuf.dptr = new_dptr;
+ }
+
+ if (dbuf.dptr == NULL) {
+ tdb->ecode = TDB1_ERR_OOM;
+ goto failed;
+ }
+
+ memcpy(dbuf.dptr + dbuf.dsize, new_dbuf.dptr, new_dbuf.dsize);
+ dbuf.dsize += new_dbuf.dsize;
+
+ ret = _tdb1_store(tdb, key, dbuf, 0, hash);
+
+failed:
+ tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK);
+ SAFE_FREE(dbuf.dptr);
+ return ret;
+}
+
+
+/*
+ return the name of the current tdb file
+ useful for external logging functions
+*/
+_PUBLIC_ const char *tdb1_name(struct tdb1_context *tdb)
+{
+ return tdb->name;
+}
+
+/*
+ return the underlying file descriptor being used by tdb, or -1
+ useful for external routines that want to check the device/inode
+ of the fd
+*/
+_PUBLIC_ int tdb1_fd(struct tdb1_context *tdb)
+{
+ return tdb->fd;
+}
+
+/*
+ return the current logging function
+ useful for external tdb routines that wish to log tdb errors
+*/
+_PUBLIC_ tdb1_log_func tdb1_log_fn(struct tdb1_context *tdb)
+{
+ return tdb->log.log_fn;
+}
+
+
+/*
+ get the tdb sequence number. Only makes sense if the writers opened
+ with TDB1_SEQNUM set. Note that this sequence number will wrap quite
+ quickly, so it should only be used for a 'has something changed'
+ test, not for code that relies on the count of the number of changes
+ made. If you want a counter then use a tdb record.
+
+ The aim of this sequence number is to allow for a very lightweight
+ test of a possible tdb change.
+*/
+_PUBLIC_ int tdb1_get_seqnum(struct tdb1_context *tdb)
+{
+ tdb1_off_t seqnum=0;
+
+ tdb1_ofs_read(tdb, TDB1_SEQNUM_OFS, &seqnum);
+ return seqnum;
+}
+
+_PUBLIC_ int tdb1_hash_size(struct tdb1_context *tdb)
+{
+ return tdb->header.hash_size;
+}
+
+_PUBLIC_ size_t tdb1_map_size(struct tdb1_context *tdb)
+{
+ return tdb->map_size;
+}
+
+_PUBLIC_ int tdb1_get_flags(struct tdb1_context *tdb)
+{
+ return tdb->flags;
+}
+
+_PUBLIC_ void tdb1_add_flags(struct tdb1_context *tdb, unsigned flags)
+{
+ if ((flags & TDB1_ALLOW_NESTING) &&
+ (flags & TDB1_DISALLOW_NESTING)) {
+ tdb->ecode = TDB1_ERR_NESTING;
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "tdb1_add_flags: "
+ "allow_nesting and disallow_nesting are not allowed together!"));
+ return;
+ }
+
+ if (flags & TDB1_ALLOW_NESTING) {
+ tdb->flags &= ~TDB1_DISALLOW_NESTING;
+ }
+ if (flags & TDB1_DISALLOW_NESTING) {
+ tdb->flags &= ~TDB1_ALLOW_NESTING;
+ }
+
+ tdb->flags |= flags;
+}
+
+_PUBLIC_ void tdb1_remove_flags(struct tdb1_context *tdb, unsigned flags)
+{
+ if ((flags & TDB1_ALLOW_NESTING) &&
+ (flags & TDB1_DISALLOW_NESTING)) {
+ tdb->ecode = TDB1_ERR_NESTING;
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "tdb1_remove_flags: "
+ "allow_nesting and disallow_nesting are not allowed together!"));
+ return;
+ }
+
+ if (flags & TDB1_ALLOW_NESTING) {
+ tdb->flags |= TDB1_DISALLOW_NESTING;
+ }
+ if (flags & TDB1_DISALLOW_NESTING) {
+ tdb->flags |= TDB1_ALLOW_NESTING;
+ }
+
+ tdb->flags &= ~flags;
+}
+
+
+/*
+ enable sequence number handling on an open tdb
+*/
+_PUBLIC_ void tdb1_enable_seqnum(struct tdb1_context *tdb)
+{
+ tdb->flags |= TDB1_SEQNUM;
+}
+
+
+/*
+ add a region of the file to the freelist. Length is the size of the region in bytes,
+ which includes the free list header that needs to be added
+ */
+static int tdb1_free_region(struct tdb1_context *tdb, tdb1_off_t offset, ssize_t length)
+{
+ struct tdb1_record rec;
+ if (length <= sizeof(rec)) {
+ /* the region is not worth adding */
+ return 0;
+ }
+ if (length + offset > tdb->map_size) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL,"tdb1_free_region: adding region beyond end of file\n"));
+ return -1;
+ }
+ memset(&rec,'\0',sizeof(rec));
+ rec.rec_len = length - sizeof(rec);
+ if (tdb1_free(tdb, offset, &rec) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL,"tdb1_free_region: failed to add free record\n"));
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ wipe the entire database, deleting all records. This can be done
+ very fast by using a allrecord lock. The entire data portion of the
+ file becomes a single entry in the freelist.
+
+ This code carefully steps around the recovery area, leaving it alone
+ */
+_PUBLIC_ int tdb1_wipe_all(struct tdb1_context *tdb)
+{
+ int i;
+ tdb1_off_t offset = 0;
+ ssize_t data_len;
+ tdb1_off_t recovery_head;
+ tdb1_len_t recovery_size = 0;
+
+ if (tdb1_lockall(tdb) != 0) {
+ return -1;
+ }
+
+
+ /* see if the tdb has a recovery area, and remember its size
+ if so. We don't want to lose this as otherwise each
+ tdb1_wipe_all() in a transaction will increase the size of
+ the tdb by the size of the recovery area */
+ if (tdb1_ofs_read(tdb, TDB1_RECOVERY_HEAD, &recovery_head) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "tdb1_wipe_all: failed to read recovery head\n"));
+ goto failed;
+ }
+
+ if (recovery_head != 0) {
+ struct tdb1_record rec;
+ if (tdb->methods->tdb1_read(tdb, recovery_head, &rec, sizeof(rec), TDB1_DOCONV()) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, "tdb1_wipe_all: failed to read recovery record\n"));
+ return -1;
+ }
+ recovery_size = rec.rec_len + sizeof(rec);
+ }
+
+ /* wipe the hashes */
+ for (i=0;i<tdb->header.hash_size;i++) {
+ if (tdb1_ofs_write(tdb, TDB1_HASH_TOP(i), &offset) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL,"tdb1_wipe_all: failed to write hash %d\n", i));
+ goto failed;
+ }
+ }
+
+ /* wipe the freelist */
+ if (tdb1_ofs_write(tdb, TDB1_FREELIST_TOP, &offset) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL,"tdb1_wipe_all: failed to write freelist\n"));
+ goto failed;
+ }
+
+ /* add all the rest of the file to the freelist, possibly leaving a gap
+ for the recovery area */
+ if (recovery_size == 0) {
+ /* the simple case - the whole file can be used as a freelist */
+ data_len = (tdb->map_size - TDB1_DATA_START(tdb->header.hash_size));
+ if (tdb1_free_region(tdb, TDB1_DATA_START(tdb->header.hash_size), data_len) != 0) {
+ goto failed;
+ }
+ } else {
+ /* we need to add two freelist entries - one on either
+ side of the recovery area
+
+ Note that we cannot shift the recovery area during
+ this operation. Only the transaction.c code may
+ move the recovery area or we risk subtle data
+ corruption
+ */
+ data_len = (recovery_head - TDB1_DATA_START(tdb->header.hash_size));
+ if (tdb1_free_region(tdb, TDB1_DATA_START(tdb->header.hash_size), data_len) != 0) {
+ goto failed;
+ }
+ /* and the 2nd free list entry after the recovery area - if any */
+ data_len = tdb->map_size - (recovery_head+recovery_size);
+ if (tdb1_free_region(tdb, recovery_head+recovery_size, data_len) != 0) {
+ goto failed;
+ }
+ }
+
+ if (tdb1_unlockall(tdb) != 0) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL,"tdb1_wipe_all: failed to unlock\n"));
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ tdb1_unlockall(tdb);
+ return -1;
+}
+
+struct traverse_state {
+ bool error;
+ struct tdb1_context *dest_db;
+};
+
+/*
+ traverse function for repacking
+ */
+static int repack_traverse(struct tdb1_context *tdb, TDB1_DATA key, TDB1_DATA data, void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ if (tdb1_store(state->dest_db, key, data, TDB1_INSERT) != 0) {
+ state->error = true;
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ repack a tdb
+ */
+_PUBLIC_ int tdb1_repack(struct tdb1_context *tdb)
+{
+ struct tdb1_context *tmp_db;
+ struct traverse_state state;
+
+ if (tdb1_transaction_start(tdb) != 0) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to start transaction\n"));
+ return -1;
+ }
+
+ tmp_db = tdb1_open("tmpdb", tdb1_hash_size(tdb), TDB1_INTERNAL, O_RDWR|O_CREAT, 0);
+ if (tmp_db == NULL) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to create tmp_db\n"));
+ tdb1_transaction_cancel(tdb);
+ return -1;
+ }
+
+ state.error = false;
+ state.dest_db = tmp_db;
+
+ if (tdb1_traverse_read(tdb, repack_traverse, &state) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to traverse copying out\n"));
+ tdb1_transaction_cancel(tdb);
+ tdb1_close(tmp_db);
+ return -1;
+ }
+
+ if (state.error) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Error during traversal\n"));
+ tdb1_transaction_cancel(tdb);
+ tdb1_close(tmp_db);
+ return -1;
+ }
+
+ if (tdb1_wipe_all(tdb) != 0) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to wipe database\n"));
+ tdb1_transaction_cancel(tdb);
+ tdb1_close(tmp_db);
+ return -1;
+ }
+
+ state.error = false;
+ state.dest_db = tdb;
+
+ if (tdb1_traverse_read(tmp_db, repack_traverse, &state) == -1) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to traverse copying back\n"));
+ tdb1_transaction_cancel(tdb);
+ tdb1_close(tmp_db);
+ return -1;
+ }
+
+ if (state.error) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Error during second traversal\n"));
+ tdb1_transaction_cancel(tdb);
+ tdb1_close(tmp_db);
+ return -1;
+ }
+
+ tdb1_close(tmp_db);
+
+ if (tdb1_transaction_commit(tdb) != 0) {
+ TDB1_LOG((tdb, TDB1_DEBUG_FATAL, __location__ " Failed to commit\n"));
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Even on files, we can get partial writes due to signals. */
+bool tdb1_write_all(int fd, const void *buf, size_t count)
+{
+ while (count) {
+ ssize_t ret;
+ ret = write(fd, buf, count);
+ if (ret < 0)
+ return false;
+ buf = (const char *)buf + ret;
+ count -= ret;
+ }
+ return true;
+}