mirror of
https://github.com/samba-team/samba.git
synced 2025-01-13 13:18:06 +03:00
3aa565e790
Michael
(This used to be commit 18ced7e420
)
1266 lines
30 KiB
C
1266 lines
30 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
Database interface wrapper around tdb/ctdb
|
|
|
|
Copyright (C) Volker Lendecke 2005-2007
|
|
Copyright (C) Stefan Metzmacher 2008
|
|
|
|
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 "includes.h"
|
|
#include "librpc/gen_ndr/ndr_messaging.h"
|
|
|
|
struct db_tdb2_ctx {
|
|
struct db_context *db;
|
|
const char *name;
|
|
struct tdb_wrap *mtdb;
|
|
const char *mtdb_path;
|
|
bool master_transaction;
|
|
struct {
|
|
int hash_size;
|
|
int tdb_flags;
|
|
int open_flags;
|
|
mode_t mode;
|
|
} open;
|
|
struct tdb_wrap *ltdb;
|
|
const char *ltdb_path;
|
|
bool local_transaction;
|
|
int transaction;
|
|
bool out_of_sync;
|
|
uint32_t lseqnum;
|
|
uint32_t mseqnum;
|
|
#define DB_TDB2_MASTER_SEQNUM_KEYSTR "DB_TDB2_MASTER_SEQNUM_KEYSTR"
|
|
TDB_DATA mseqkey;
|
|
uint32_t max_buffer_size;
|
|
uint32_t current_buffer_size;
|
|
struct dbwrap_tdb2_changes changes;
|
|
};
|
|
|
|
|
|
static NTSTATUS db_tdb2_store(struct db_record *rec, TDB_DATA data, int flag);
|
|
static NTSTATUS db_tdb2_delete(struct db_record *rec);
|
|
|
|
static void db_tdb2_queue_change(struct db_tdb2_ctx *db_ctx, const TDB_DATA key);
|
|
static void db_tdb2_send_notify(struct db_tdb2_ctx *db_ctx);
|
|
|
|
static struct db_context *db_open_tdb2_ex(TALLOC_CTX *mem_ctx,
|
|
const char *name,
|
|
int hash_size, int tdb_flags,
|
|
int open_flags, mode_t mode,
|
|
const struct dbwrap_tdb2_changes *chgs);
|
|
|
|
static int db_tdb2_sync_from_master(struct db_tdb2_ctx *db_ctx,
|
|
const struct dbwrap_tdb2_changes *changes);
|
|
|
|
static int db_tdb2_open_master(struct db_tdb2_ctx *db_ctx, bool transaction,
|
|
const struct dbwrap_tdb2_changes *changes);
|
|
static int db_tdb2_commit_local(struct db_tdb2_ctx *db_ctx, uint32_t mseqnum);
|
|
static int db_tdb2_close_master(struct db_tdb2_ctx *db_ctx);
|
|
static int db_tdb2_transaction_cancel(struct db_context *db);
|
|
|
|
static void db_tdb2_receive_changes(struct messaging_context *msg,
|
|
void *private_data,
|
|
uint32_t msg_type,
|
|
struct server_id server_id,
|
|
DATA_BLOB *data);
|
|
|
|
static struct messaging_context *global_tdb2_msg_ctx;
|
|
static bool global_tdb2_msg_ctx_initialized;
|
|
|
|
void db_tdb2_setup_messaging(struct messaging_context *msg_ctx, bool server)
|
|
{
|
|
global_tdb2_msg_ctx = msg_ctx;
|
|
|
|
global_tdb2_msg_ctx_initialized = true;
|
|
|
|
if (!server) {
|
|
return;
|
|
}
|
|
|
|
if (!lp_parm_bool(-1, "dbwrap", "use_tdb2", false)) {
|
|
return;
|
|
}
|
|
|
|
messaging_register(msg_ctx, NULL, MSG_DBWRAP_TDB2_CHANGES,
|
|
db_tdb2_receive_changes);
|
|
}
|
|
|
|
static struct messaging_context *db_tdb2_get_global_messaging_context(void)
|
|
{
|
|
struct messaging_context *msg_ctx;
|
|
|
|
if (global_tdb2_msg_ctx_initialized) {
|
|
return global_tdb2_msg_ctx;
|
|
}
|
|
|
|
msg_ctx = messaging_init(NULL, procid_self(),
|
|
event_context_init(NULL));
|
|
|
|
db_tdb2_setup_messaging(msg_ctx, false);
|
|
|
|
return global_tdb2_msg_ctx;
|
|
}
|
|
|
|
struct tdb_fetch_locked_state {
|
|
TALLOC_CTX *mem_ctx;
|
|
struct db_record *result;
|
|
};
|
|
|
|
static int db_tdb2_fetchlock_parse(TDB_DATA key, TDB_DATA data,
|
|
void *private_data)
|
|
{
|
|
struct tdb_fetch_locked_state *state =
|
|
(struct tdb_fetch_locked_state *)private_data;
|
|
|
|
state->result = (struct db_record *)talloc_size(
|
|
state->mem_ctx,
|
|
sizeof(struct db_record) + key.dsize + data.dsize);
|
|
|
|
if (state->result == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
state->result->key.dsize = key.dsize;
|
|
state->result->key.dptr = ((uint8 *)state->result)
|
|
+ sizeof(struct db_record);
|
|
memcpy(state->result->key.dptr, key.dptr, key.dsize);
|
|
|
|
state->result->value.dsize = data.dsize;
|
|
|
|
if (data.dsize > 0) {
|
|
state->result->value.dptr = state->result->key.dptr+key.dsize;
|
|
memcpy(state->result->value.dptr, data.dptr, data.dsize);
|
|
}
|
|
else {
|
|
state->result->value.dptr = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct db_record *db_tdb2_fetch_locked(struct db_context *db,
|
|
TALLOC_CTX *mem_ctx, TDB_DATA key)
|
|
{
|
|
struct db_tdb2_ctx *ctx = talloc_get_type_abort(db->private_data,
|
|
struct db_tdb2_ctx);
|
|
struct tdb_fetch_locked_state state;
|
|
|
|
/* Do not accidently allocate/deallocate w/o need when debug level is lower than needed */
|
|
if(DEBUGLEVEL >= 10) {
|
|
char *keystr = hex_encode(NULL, (unsigned char*)key.dptr, key.dsize);
|
|
DEBUG(10, (DEBUGLEVEL > 10
|
|
? "Locking key %s\n" : "Locking key %.20s\n",
|
|
keystr));
|
|
TALLOC_FREE(keystr);
|
|
}
|
|
|
|
/*
|
|
* we only support modifications within a
|
|
* started transaction.
|
|
*/
|
|
if (ctx->transaction == 0) {
|
|
DEBUG(0, ("db_tdb2_fetch_locked[%s]: no transaction started\n",
|
|
ctx->name));
|
|
smb_panic("no transaction");
|
|
return NULL;
|
|
}
|
|
|
|
state.mem_ctx = mem_ctx;
|
|
state.result = NULL;
|
|
|
|
tdb_parse_record(ctx->mtdb->tdb, key, db_tdb2_fetchlock_parse, &state);
|
|
|
|
if (state.result == NULL) {
|
|
db_tdb2_fetchlock_parse(key, tdb_null, &state);
|
|
}
|
|
|
|
if (state.result == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
state.result->private_data = talloc_reference(state.result, ctx);
|
|
state.result->store = db_tdb2_store;
|
|
state.result->delete_rec = db_tdb2_delete;
|
|
|
|
DEBUG(10, ("Allocated locked data 0x%p\n", state.result));
|
|
|
|
return state.result;
|
|
}
|
|
|
|
struct tdb_fetch_state {
|
|
TALLOC_CTX *mem_ctx;
|
|
int result;
|
|
TDB_DATA data;
|
|
};
|
|
|
|
static int db_tdb2_fetch_parse(TDB_DATA key, TDB_DATA data,
|
|
void *private_data)
|
|
{
|
|
struct tdb_fetch_state *state =
|
|
(struct tdb_fetch_state *)private_data;
|
|
|
|
state->data.dptr = (uint8 *)talloc_memdup(state->mem_ctx, data.dptr,
|
|
data.dsize);
|
|
if (state->data.dptr == NULL) {
|
|
state->result = -1;
|
|
return 0;
|
|
}
|
|
|
|
state->data.dsize = data.dsize;
|
|
return 0;
|
|
}
|
|
|
|
static void db_tdb2_resync_before_read(struct db_tdb2_ctx *db_ctx, TDB_DATA *kbuf)
|
|
{
|
|
if (db_ctx->mtdb) {
|
|
return;
|
|
}
|
|
|
|
if (!db_ctx->out_of_sync) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* this function operates on the local copy,
|
|
* so hide the DB_TDB2_MASTER_SEQNUM_KEYSTR from the caller.
|
|
*/
|
|
if (kbuf && (db_ctx->mseqkey.dsize == kbuf->dsize) &&
|
|
(memcmp(db_ctx->mseqkey.dptr, kbuf->dptr, kbuf->dsize) == 0)) {
|
|
return;
|
|
}
|
|
|
|
DEBUG(0,("resync_before_read[%s/%s]\n",
|
|
db_ctx->mtdb_path, db_ctx->ltdb_path));
|
|
|
|
db_tdb2_open_master(db_ctx, false, NULL);
|
|
db_tdb2_close_master(db_ctx);
|
|
}
|
|
|
|
static int db_tdb2_fetch(struct db_context *db, TALLOC_CTX *mem_ctx,
|
|
TDB_DATA key, TDB_DATA *pdata)
|
|
{
|
|
struct db_tdb2_ctx *ctx = talloc_get_type_abort(
|
|
db->private_data, struct db_tdb2_ctx);
|
|
|
|
struct tdb_fetch_state state;
|
|
|
|
db_tdb2_resync_before_read(ctx, &key);
|
|
|
|
if (ctx->out_of_sync) {
|
|
DEBUG(0,("out of sync[%s] failing fetch\n",
|
|
ctx->ltdb_path));
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
state.mem_ctx = mem_ctx;
|
|
state.result = 0;
|
|
state.data = tdb_null;
|
|
|
|
tdb_parse_record(ctx->ltdb->tdb, key, db_tdb2_fetch_parse, &state);
|
|
|
|
if (state.result == -1) {
|
|
return -1;
|
|
}
|
|
|
|
*pdata = state.data;
|
|
return 0;
|
|
}
|
|
|
|
static NTSTATUS db_tdb2_store(struct db_record *rec, TDB_DATA data, int flag)
|
|
{
|
|
struct db_tdb2_ctx *ctx = talloc_get_type_abort(rec->private_data,
|
|
struct db_tdb2_ctx);
|
|
int ret;
|
|
|
|
/*
|
|
* This has a bug: We need to replace rec->value for correct
|
|
* operation, but right now brlock and locking don't use the value
|
|
* anymore after it was stored.
|
|
*/
|
|
|
|
/* first store it to the master copy */
|
|
ret = tdb_store(ctx->mtdb->tdb, rec->key, data, flag);
|
|
if (ret != 0) {
|
|
return NT_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
/* then store it to the local copy */
|
|
ret = tdb_store(ctx->ltdb->tdb, rec->key, data, flag);
|
|
if (ret != 0) {
|
|
/* try to restore the old value in the master copy */
|
|
if (rec->value.dptr) {
|
|
tdb_store(ctx->mtdb->tdb, rec->key,
|
|
rec->value, TDB_REPLACE);
|
|
} else {
|
|
tdb_delete(ctx->mtdb->tdb, rec->key);
|
|
}
|
|
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
|
}
|
|
|
|
db_tdb2_queue_change(ctx, rec->key);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static NTSTATUS db_tdb2_delete(struct db_record *rec)
|
|
{
|
|
struct db_tdb2_ctx *ctx = talloc_get_type_abort(rec->private_data,
|
|
struct db_tdb2_ctx);
|
|
int ret;
|
|
|
|
ret = tdb_delete(ctx->mtdb->tdb, rec->key);
|
|
if (ret != 0) {
|
|
if (tdb_error(ctx->mtdb->tdb) == TDB_ERR_NOEXIST) {
|
|
return NT_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
return NT_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
ret = tdb_delete(ctx->ltdb->tdb, rec->key);
|
|
if (ret != 0) {
|
|
/* try to restore the value in the master copy */
|
|
tdb_store(ctx->mtdb->tdb, rec->key,
|
|
rec->value, TDB_REPLACE);
|
|
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
|
}
|
|
|
|
db_tdb2_queue_change(ctx, rec->key);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct db_tdb2_traverse_ctx {
|
|
struct db_tdb2_ctx *db_ctx;
|
|
int (*f)(struct db_record *rec, void *private_data);
|
|
void *private_data;
|
|
};
|
|
|
|
static int db_tdb2_traverse_func(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf,
|
|
void *private_data)
|
|
{
|
|
struct db_tdb2_traverse_ctx *ctx =
|
|
(struct db_tdb2_traverse_ctx *)private_data;
|
|
struct db_record rec;
|
|
|
|
/* this function operates on the master copy */
|
|
|
|
rec.key = kbuf;
|
|
rec.value = dbuf;
|
|
rec.store = db_tdb2_store;
|
|
rec.delete_rec = db_tdb2_delete;
|
|
rec.private_data = ctx->db_ctx;
|
|
|
|
return ctx->f(&rec, ctx->private_data);
|
|
}
|
|
|
|
static int db_tdb2_traverse(struct db_context *db,
|
|
int (*f)(struct db_record *rec, void *private_data),
|
|
void *private_data)
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
struct db_tdb2_traverse_ctx ctx;
|
|
|
|
/*
|
|
* we only support modifications within a
|
|
* started transaction.
|
|
*/
|
|
if (db_ctx->transaction == 0) {
|
|
DEBUG(0, ("db_tdb2_traverse[%s]: no transaction started\n",
|
|
db_ctx->name));
|
|
smb_panic("no transaction");
|
|
return -1;
|
|
}
|
|
|
|
/* here we traverse the master copy */
|
|
ctx.db_ctx = db_ctx;
|
|
ctx.f = f;
|
|
ctx.private_data = private_data;
|
|
return tdb_traverse(db_ctx->mtdb->tdb, db_tdb2_traverse_func, &ctx);
|
|
}
|
|
|
|
static NTSTATUS db_tdb2_store_deny(struct db_record *rec, TDB_DATA data, int flag)
|
|
{
|
|
return NT_STATUS_MEDIA_WRITE_PROTECTED;
|
|
}
|
|
|
|
static NTSTATUS db_tdb2_delete_deny(struct db_record *rec)
|
|
{
|
|
return NT_STATUS_MEDIA_WRITE_PROTECTED;
|
|
}
|
|
|
|
static int db_tdb2_traverse_read_func(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf,
|
|
void *private_data)
|
|
{
|
|
struct db_tdb2_traverse_ctx *ctx =
|
|
(struct db_tdb2_traverse_ctx *)private_data;
|
|
struct db_record rec;
|
|
|
|
/*
|
|
* this function operates on the local copy,
|
|
* so hide the DB_TDB2_MASTER_SEQNUM_KEYSTR from the caller.
|
|
*/
|
|
if ((ctx->db_ctx->mseqkey.dsize == kbuf.dsize) &&
|
|
(memcmp(ctx->db_ctx->mseqkey.dptr, kbuf.dptr, kbuf.dsize) == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
rec.key = kbuf;
|
|
rec.value = dbuf;
|
|
rec.store = db_tdb2_store_deny;
|
|
rec.delete_rec = db_tdb2_delete_deny;
|
|
rec.private_data = ctx->db_ctx;
|
|
|
|
return ctx->f(&rec, ctx->private_data);
|
|
}
|
|
|
|
static int db_tdb2_traverse_read(struct db_context *db,
|
|
int (*f)(struct db_record *rec, void *private_data),
|
|
void *private_data)
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
struct db_tdb2_traverse_ctx ctx;
|
|
int ret;
|
|
|
|
db_tdb2_resync_before_read(db_ctx, NULL);
|
|
|
|
if (db_ctx->out_of_sync) {
|
|
DEBUG(0,("out of sync[%s] failing traverse_read\n",
|
|
db_ctx->ltdb_path));
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
/* here we traverse the local copy */
|
|
ctx.db_ctx = db_ctx;
|
|
ctx.f = f;
|
|
ctx.private_data = private_data;
|
|
ret = tdb_traverse_read(db_ctx->ltdb->tdb, db_tdb2_traverse_read_func, &ctx);
|
|
if (ret > 0) {
|
|
/* we have filtered one entry */
|
|
ret--;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int db_tdb2_get_seqnum(struct db_context *db)
|
|
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
uint32_t nlseq;
|
|
uint32_t nmseq;
|
|
bool ok;
|
|
|
|
nlseq = tdb_get_seqnum(db_ctx->ltdb->tdb);
|
|
|
|
if (nlseq == db_ctx->lseqnum) {
|
|
return db_ctx->mseqnum;
|
|
}
|
|
|
|
ok = tdb_fetch_uint32_byblob(db_ctx->ltdb->tdb,
|
|
db_ctx->mseqkey,
|
|
&nmseq);
|
|
if (!ok) {
|
|
/* TODO: what should we do here? */
|
|
return db_ctx->mseqnum;
|
|
}
|
|
|
|
db_ctx->lseqnum = nlseq;
|
|
db_ctx->mseqnum = nmseq;
|
|
|
|
return db_ctx->mseqnum;
|
|
}
|
|
|
|
static int db_tdb2_transaction_start(struct db_context *db)
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
int ret;
|
|
|
|
if (db_ctx->transaction) {
|
|
db_ctx->transaction++;
|
|
return 0;
|
|
}
|
|
|
|
/* we need to open the master tdb in order to */
|
|
ret = db_tdb2_open_master(db_ctx, true, NULL);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = tdb_transaction_start(db_ctx->ltdb->tdb);
|
|
if (ret != 0) {
|
|
db_tdb2_close_master(db_ctx);
|
|
return ret;
|
|
}
|
|
|
|
db_ctx->local_transaction = true;
|
|
db_ctx->transaction = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void db_tdb2_queue_change(struct db_tdb2_ctx *db_ctx, const TDB_DATA key)
|
|
{
|
|
size_t size_needed = 4 + key.dsize;
|
|
size_t size_new = db_ctx->current_buffer_size + size_needed;
|
|
uint32_t i;
|
|
DATA_BLOB *keys;
|
|
|
|
db_ctx->changes.num_changes++;
|
|
|
|
if (db_ctx->changes.num_changes > 1 &&
|
|
db_ctx->changes.keys == NULL) {
|
|
/*
|
|
* this means we already overflowed
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (db_ctx->changes.num_changes == 1) {
|
|
db_ctx->changes.old_seqnum = db_ctx->mseqnum;
|
|
}
|
|
|
|
for (i=0; i < db_ctx->changes.num_keys; i++) {
|
|
int ret;
|
|
|
|
if (key.dsize != db_ctx->changes.keys[i].length) {
|
|
continue;
|
|
}
|
|
ret = memcmp(key.dptr, db_ctx->changes.keys[i].data, key.dsize);
|
|
if (ret != 0) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* the key is already in the list
|
|
* so we're done
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (db_ctx->max_buffer_size < size_new) {
|
|
goto overflow;
|
|
}
|
|
|
|
keys = TALLOC_REALLOC_ARRAY(db_ctx, db_ctx->changes.keys,
|
|
DATA_BLOB,
|
|
db_ctx->changes.num_keys + 1);
|
|
if (!keys) {
|
|
goto overflow;
|
|
}
|
|
db_ctx->changes.keys = keys;
|
|
|
|
keys[db_ctx->changes.num_keys].data = (uint8_t *)talloc_memdup(keys,
|
|
key.dptr,
|
|
key.dsize);
|
|
if (!keys[db_ctx->changes.num_keys].data) {
|
|
goto overflow;
|
|
}
|
|
keys[db_ctx->changes.num_keys].length = key.dsize;
|
|
db_ctx->changes.num_keys++;
|
|
db_ctx->current_buffer_size = size_new;
|
|
|
|
return;
|
|
|
|
overflow:
|
|
/*
|
|
* on overflow discard the buffer and let
|
|
* the others reload the whole tdb
|
|
*/
|
|
db_ctx->current_buffer_size = 0;
|
|
db_ctx->changes.num_keys = 0;
|
|
TALLOC_FREE(db_ctx->changes.keys);
|
|
return;
|
|
}
|
|
|
|
static void db_tdb2_send_notify(struct db_tdb2_ctx *db_ctx)
|
|
{
|
|
enum ndr_err_code ndr_err;
|
|
bool ok;
|
|
DATA_BLOB blob;
|
|
struct messaging_context *msg_ctx;
|
|
int num_msgs = 0;
|
|
struct server_id self = procid_self();
|
|
|
|
msg_ctx = db_tdb2_get_global_messaging_context();
|
|
|
|
db_ctx->changes.name = db_ctx->name;
|
|
|
|
DEBUG(10,("%s[%s] size[%u/%u] changes[%u] keys[%u] seqnum[%u=>%u]\n",
|
|
__FUNCTION__,
|
|
db_ctx->changes.name,
|
|
db_ctx->current_buffer_size,
|
|
db_ctx->max_buffer_size,
|
|
db_ctx->changes.num_changes,
|
|
db_ctx->changes.num_keys,
|
|
db_ctx->changes.old_seqnum,
|
|
db_ctx->changes.new_seqnum));
|
|
|
|
if (db_ctx->changes.num_changes == 0) {
|
|
DEBUG(10,("db_tdb2_send_notify[%s]: no changes\n",
|
|
db_ctx->changes.name));
|
|
goto done;
|
|
}
|
|
|
|
if (!msg_ctx) {
|
|
DEBUG(1,("db_tdb2_send_notify[%s]: skipped (no msg ctx)\n",
|
|
db_ctx->changes.name));
|
|
goto done;
|
|
}
|
|
|
|
ndr_err = ndr_push_struct_blob(
|
|
&blob, talloc_tos(), &db_ctx->changes,
|
|
(ndr_push_flags_fn_t)ndr_push_dbwrap_tdb2_changes);
|
|
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
|
|
DEBUG(0,("db_tdb2_send_notify[%s]: failed to push changes: %s\n",
|
|
db_ctx->changes.name,
|
|
nt_errstr(ndr_map_error2ntstatus(ndr_err))));
|
|
goto done;
|
|
}
|
|
|
|
ok = message_send_all(msg_ctx, MSG_DBWRAP_TDB2_CHANGES,
|
|
blob.data, blob.length, &num_msgs);
|
|
if (!ok) {
|
|
DEBUG(0,("db_tdb2_send_notify[%s]: failed to send changes\n",
|
|
db_ctx->changes.name));
|
|
goto done;
|
|
}
|
|
|
|
DEBUG(10,("db_tdb2_send_notify[%s]: pid %s send %u messages\n",
|
|
db_ctx->name, procid_str_static(&self), num_msgs));
|
|
|
|
done:
|
|
TALLOC_FREE(db_ctx->changes.keys);
|
|
ZERO_STRUCT(db_ctx->changes);
|
|
|
|
return;
|
|
}
|
|
|
|
static void db_tdb2_receive_changes(struct messaging_context *msg,
|
|
void *private_data,
|
|
uint32_t msg_type,
|
|
struct server_id server_id,
|
|
DATA_BLOB *data)
|
|
{
|
|
enum ndr_err_code ndr_err;
|
|
struct dbwrap_tdb2_changes changes;
|
|
struct db_context *db;
|
|
struct server_id self;
|
|
|
|
if (procid_is_me(&server_id)) {
|
|
DEBUG(0,("db_tdb2_receive_changes: ignore selfpacket\n"));
|
|
return;
|
|
}
|
|
|
|
self = procid_self();
|
|
|
|
DEBUG(10,("db_tdb2_receive_changes: from %s to %s\n",
|
|
procid_str(debug_ctx(), &server_id),
|
|
procid_str(debug_ctx(), &self)));
|
|
|
|
ndr_err = ndr_pull_struct_blob_all(
|
|
data, talloc_tos(), &changes,
|
|
(ndr_pull_flags_fn_t)ndr_pull_dbwrap_tdb2_changes);
|
|
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
|
|
DEBUG(0,("db_tdb2_receive_changes: failed to pull changes: %s\n",
|
|
nt_errstr(ndr_map_error2ntstatus(ndr_err))));
|
|
goto done;
|
|
}
|
|
|
|
if(DEBUGLEVEL >= 10) {
|
|
NDR_PRINT_DEBUG(dbwrap_tdb2_changes, &changes);
|
|
}
|
|
|
|
/* open the db, this will sync it */
|
|
db = db_open_tdb2_ex(talloc_tos(), changes.name, 0,
|
|
0, O_RDWR, 0600, &changes);
|
|
TALLOC_FREE(db);
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static int db_tdb2_transaction_commit(struct db_context *db)
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
int ret;
|
|
uint32_t mseqnum;
|
|
|
|
if (db_ctx->transaction == 0) {
|
|
return -1;
|
|
} else if (db_ctx->transaction > 1) {
|
|
db_ctx->transaction--;
|
|
return 0;
|
|
}
|
|
|
|
mseqnum = tdb_get_seqnum(db_ctx->mtdb->tdb);
|
|
db_ctx->changes.new_seqnum = mseqnum;
|
|
|
|
/* first commit to the master copy */
|
|
ret = tdb_transaction_commit(db_ctx->mtdb->tdb);
|
|
db_ctx->master_transaction = false;
|
|
if (ret != 0) {
|
|
int saved_errno = errno;
|
|
db_tdb2_transaction_cancel(db);
|
|
errno = saved_errno;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Note: as we've already commited the changes to the master copy
|
|
* so we ignore errors in the following functions
|
|
*/
|
|
ret = db_tdb2_commit_local(db_ctx, mseqnum);
|
|
if (ret == 0) {
|
|
db_ctx->out_of_sync = false;
|
|
} else {
|
|
db_ctx->out_of_sync = true;
|
|
}
|
|
|
|
db_ctx->transaction = 0;
|
|
|
|
db_tdb2_close_master(db_ctx);
|
|
|
|
db_tdb2_send_notify(db_ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_transaction_cancel(struct db_context *db)
|
|
{
|
|
struct db_tdb2_ctx *db_ctx =
|
|
talloc_get_type_abort(db->private_data, struct db_tdb2_ctx);
|
|
int saved_errno;
|
|
int ret;
|
|
|
|
if (db_ctx->transaction == 0) {
|
|
return -1;
|
|
}
|
|
if (db_ctx->transaction > 1) {
|
|
db_ctx->transaction--;
|
|
return 0;
|
|
}
|
|
|
|
/* cancel the transaction and close the master copy */
|
|
ret = db_tdb2_close_master(db_ctx);
|
|
saved_errno = errno;
|
|
|
|
/* now cancel on the local copy and ignore any error */
|
|
tdb_transaction_cancel(db_ctx->ltdb->tdb);
|
|
db_ctx->local_transaction = false;
|
|
|
|
db_ctx->transaction = 0;
|
|
|
|
errno = saved_errno;
|
|
return ret;
|
|
}
|
|
|
|
static int db_tdb2_open_master(struct db_tdb2_ctx *db_ctx, bool transaction,
|
|
const struct dbwrap_tdb2_changes *changes)
|
|
{
|
|
int ret;
|
|
|
|
db_ctx->mtdb = tdb_wrap_open(db_ctx,
|
|
db_ctx->mtdb_path,
|
|
db_ctx->open.hash_size,
|
|
db_ctx->open.tdb_flags|TDB_NOMMAP|TDB_SEQNUM,
|
|
db_ctx->open.open_flags,
|
|
db_ctx->open.mode);
|
|
if (db_ctx->mtdb == NULL) {
|
|
DEBUG(0, ("Could not open master tdb[%s]: %s\n",
|
|
db_ctx->mtdb_path,
|
|
strerror(errno)));
|
|
return -1;
|
|
}
|
|
DEBUG(10,("open_master[%s]\n", db_ctx->mtdb_path));
|
|
|
|
if (!db_ctx->ltdb) {
|
|
struct stat st;
|
|
|
|
if (fstat(tdb_fd(db_ctx->mtdb->tdb), &st) == 0) {
|
|
db_ctx->open.mode = st.st_mode;
|
|
}
|
|
|
|
/* make sure the local one uses the same hash size as the master one */
|
|
db_ctx->open.hash_size = tdb_hash_size(db_ctx->mtdb->tdb);
|
|
|
|
db_ctx->ltdb = tdb_wrap_open(db_ctx,
|
|
db_ctx->ltdb_path,
|
|
db_ctx->open.hash_size,
|
|
db_ctx->open.tdb_flags|TDB_SEQNUM,
|
|
db_ctx->open.open_flags|O_CREAT,
|
|
db_ctx->open.mode);
|
|
if (db_ctx->ltdb == NULL) {
|
|
DEBUG(0, ("Could not open local tdb[%s]: %s\n",
|
|
db_ctx->ltdb_path,
|
|
strerror(errno)));
|
|
TALLOC_FREE(db_ctx->mtdb);
|
|
return -1;
|
|
}
|
|
DEBUG(10,("open_local[%s]\n", db_ctx->ltdb_path));
|
|
}
|
|
|
|
if (transaction) {
|
|
ret = tdb_transaction_start(db_ctx->mtdb->tdb);
|
|
if (ret != 0) {
|
|
DEBUG(0,("open failed to start transaction[%s]\n",
|
|
db_ctx->mtdb_path));
|
|
db_tdb2_close_master(db_ctx);
|
|
return ret;
|
|
}
|
|
db_ctx->master_transaction = true;
|
|
}
|
|
|
|
ret = db_tdb2_sync_from_master(db_ctx, changes);
|
|
if (ret != 0) {
|
|
DEBUG(0,("open failed to sync from master[%s]\n",
|
|
db_ctx->ltdb_path));
|
|
db_tdb2_close_master(db_ctx);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_commit_local(struct db_tdb2_ctx *db_ctx, uint32_t mseqnum)
|
|
{
|
|
bool ok;
|
|
int ret;
|
|
|
|
/* first fetch the master seqnum */
|
|
db_ctx->mseqnum = mseqnum;
|
|
|
|
/* now we try to store the master seqnum in the local tdb */
|
|
ok = tdb_store_uint32_byblob(db_ctx->ltdb->tdb,
|
|
db_ctx->mseqkey,
|
|
db_ctx->mseqnum);
|
|
if (!ok) {
|
|
tdb_transaction_cancel(db_ctx->ltdb->tdb);
|
|
db_ctx->local_transaction = false;
|
|
DEBUG(0,("local failed[%s] store mseq[%u]\n",
|
|
db_ctx->ltdb_path, db_ctx->mseqnum));
|
|
return -1;
|
|
}
|
|
|
|
/* now commit all changes to the local tdb */
|
|
ret = tdb_transaction_commit(db_ctx->ltdb->tdb);
|
|
db_ctx->local_transaction = false;
|
|
if (ret != 0) {
|
|
DEBUG(0,("local failed[%s] commit mseq[%u]\n",
|
|
db_ctx->ltdb_path, db_ctx->mseqnum));
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* and update the cached local seqnum this is needed to
|
|
* let us cache the master seqnum.
|
|
*/
|
|
db_ctx->lseqnum = tdb_get_seqnum(db_ctx->ltdb->tdb);
|
|
DEBUG(10,("local updated[%s] mseq[%u]\n",
|
|
db_ctx->ltdb_path, db_ctx->mseqnum));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_close_master(struct db_tdb2_ctx *db_ctx)
|
|
{
|
|
if (db_ctx->master_transaction) {
|
|
tdb_transaction_cancel(db_ctx->mtdb->tdb);
|
|
}
|
|
db_ctx->master_transaction = false;
|
|
/* now we can close the master handle */
|
|
TALLOC_FREE(db_ctx->mtdb);
|
|
|
|
DEBUG(10,("close_master[%s] ok\n", db_ctx->mtdb_path));
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_traverse_sync_all_func(TDB_CONTEXT *tdb,
|
|
TDB_DATA kbuf, TDB_DATA dbuf,
|
|
void *private_data)
|
|
{
|
|
struct db_tdb2_traverse_ctx *ctx =
|
|
(struct db_tdb2_traverse_ctx *)private_data;
|
|
uint32_t *seqnum = (uint32_t *)ctx->private_data;
|
|
int ret;
|
|
|
|
DEBUG(10,("sync_entry[%s]\n", ctx->db_ctx->mtdb_path));
|
|
|
|
/* Do not accidently allocate/deallocate w/o need when debug level is lower than needed */
|
|
if(DEBUGLEVEL >= 10) {
|
|
char *keystr = hex_encode(NULL, (unsigned char*)kbuf.dptr, kbuf.dsize);
|
|
DEBUG(10, (DEBUGLEVEL > 10
|
|
? "Locking key %s\n" : "Locking key %.20s\n",
|
|
keystr));
|
|
TALLOC_FREE(keystr);
|
|
}
|
|
|
|
ret = tdb_store(ctx->db_ctx->ltdb->tdb, kbuf, dbuf, TDB_INSERT);
|
|
if (ret != 0) {
|
|
DEBUG(0,("sync_entry[%s] %d: %s\n",
|
|
ctx->db_ctx->ltdb_path, ret,
|
|
tdb_errorstr(ctx->db_ctx->ltdb->tdb)));
|
|
return ret;
|
|
}
|
|
|
|
*seqnum = tdb_get_seqnum(ctx->db_ctx->mtdb->tdb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_sync_all(struct db_tdb2_ctx *db_ctx, uint32_t *seqnum)
|
|
{
|
|
struct db_tdb2_traverse_ctx ctx;
|
|
int ret;
|
|
|
|
ret = tdb_wipe_all(db_ctx->ltdb->tdb);
|
|
if (ret != 0) {
|
|
DEBUG(0,("tdb_wipe_all[%s] failed %d: %s\n",
|
|
db_ctx->ltdb_path, ret,
|
|
tdb_errorstr(db_ctx->ltdb->tdb)));
|
|
return ret;
|
|
}
|
|
|
|
ctx.db_ctx = db_ctx;
|
|
ctx.f = NULL;
|
|
ctx.private_data = seqnum;
|
|
ret = tdb_traverse_read(db_ctx->mtdb->tdb,
|
|
db_tdb2_traverse_sync_all_func,
|
|
&ctx);
|
|
DEBUG(10,("db_tdb2_sync_all[%s] count[%d]\n",
|
|
db_ctx->mtdb_path, ret));
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_sync_changes(struct db_tdb2_ctx *db_ctx,
|
|
const struct dbwrap_tdb2_changes *changes,
|
|
uint32_t *seqnum)
|
|
{
|
|
uint32_t cseqnum;
|
|
uint32_t mseqnum;
|
|
uint32_t i;
|
|
int ret;
|
|
bool need_full_sync = false;
|
|
|
|
DEBUG(10,("db_tdb2_sync_changes[%s] changes[%u]\n",
|
|
changes->name, changes->num_changes));
|
|
if(DEBUGLEVEL >= 10) {
|
|
NDR_PRINT_DEBUG(dbwrap_tdb2_changes, discard_const(changes));
|
|
}
|
|
|
|
/* for the master tdb for reading */
|
|
ret = tdb_lockall_read(db_ctx->mtdb->tdb);
|
|
if (ret != 0) {
|
|
DEBUG(0,("tdb_lockall_read[%s] %d\n", db_ctx->mtdb_path, ret));
|
|
return ret;
|
|
}
|
|
|
|
/* first fetch seqnum we know about */
|
|
cseqnum = db_tdb2_get_seqnum(db_ctx->db);
|
|
|
|
/* then fetch the master seqnum */
|
|
mseqnum = tdb_get_seqnum(db_ctx->mtdb->tdb);
|
|
|
|
if (cseqnum == mseqnum) {
|
|
DEBUG(10,("db_tdb2_sync_changes[%s] uptodate[%u]\n",
|
|
db_ctx->mtdb_path, mseqnum));
|
|
/* we hit a race before and now noticed we're uptodate */
|
|
goto done;
|
|
}
|
|
|
|
/* now see if the changes describe what we need */
|
|
if (changes->old_seqnum != cseqnum) {
|
|
need_full_sync = true;
|
|
}
|
|
|
|
if (changes->new_seqnum != mseqnum) {
|
|
need_full_sync = true;
|
|
}
|
|
|
|
/* this was the overflow case */
|
|
if (changes->num_keys == 0) {
|
|
need_full_sync = true;
|
|
}
|
|
|
|
if (need_full_sync) {
|
|
tdb_unlockall_read(db_ctx->mtdb->tdb);
|
|
DEBUG(0,("fallback to full sync[%s] seq[%u=>%u] keys[%u]\n",
|
|
db_ctx->ltdb_path, cseqnum, mseqnum,
|
|
changes->num_keys));
|
|
return db_tdb2_sync_all(db_ctx, &mseqnum);
|
|
}
|
|
|
|
for (i=0; i < changes->num_keys; i++) {
|
|
const char *op = NULL;
|
|
bool del = false;
|
|
TDB_DATA key;
|
|
TDB_DATA val;
|
|
|
|
key.dsize = changes->keys[i].length;
|
|
key.dptr = changes->keys[i].data;
|
|
|
|
val = tdb_fetch(db_ctx->mtdb->tdb, key);
|
|
ret = tdb_error(db_ctx->mtdb->tdb);
|
|
if (ret == TDB_ERR_NOEXIST) {
|
|
del = true;
|
|
} else if (ret != 0) {
|
|
DEBUG(0,("sync_changes[%s] failure %d\n",
|
|
db_ctx->mtdb_path, ret));
|
|
goto failed;
|
|
}
|
|
|
|
if (del) {
|
|
op = "delete";
|
|
ret = tdb_delete(db_ctx->ltdb->tdb, key);
|
|
DEBUG(10,("sync_changes[%s] delete key[%u] %d\n",
|
|
db_ctx->mtdb_path, i, ret));
|
|
} else {
|
|
op = "store";
|
|
ret = tdb_store(db_ctx->ltdb->tdb, key,
|
|
val, TDB_REPLACE);
|
|
DEBUG(10,("sync_changes[%s] store key[%u] %d\n",
|
|
db_ctx->mtdb_path, i, ret));
|
|
}
|
|
SAFE_FREE(val.dptr);
|
|
if (ret != 0) {
|
|
DEBUG(0,("sync_changes[%s] %s key[%u] failed %d\n",
|
|
db_ctx->mtdb_path, op, i, ret));
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
done:
|
|
tdb_unlockall_read(db_ctx->mtdb->tdb);
|
|
|
|
*seqnum = mseqnum;
|
|
return 0;
|
|
failed:
|
|
tdb_unlockall_read(db_ctx->mtdb->tdb);
|
|
return ret;
|
|
}
|
|
|
|
static int db_tdb2_sync_from_master(struct db_tdb2_ctx *db_ctx,
|
|
const struct dbwrap_tdb2_changes *changes)
|
|
{
|
|
int ret;
|
|
uint32_t cseqnum;
|
|
uint32_t mseqnum;
|
|
bool force = false;
|
|
|
|
/* first fetch seqnum we know about */
|
|
cseqnum = db_tdb2_get_seqnum(db_ctx->db);
|
|
|
|
/* then fetch the master seqnum */
|
|
mseqnum = tdb_get_seqnum(db_ctx->mtdb->tdb);
|
|
|
|
if (db_ctx->lseqnum == 0) {
|
|
force = true;
|
|
}
|
|
|
|
if (!force && cseqnum == mseqnum) {
|
|
DEBUG(10,("uptodate[%s] mseq[%u]\n",
|
|
db_ctx->ltdb_path, mseqnum));
|
|
/* the local copy is uptodate, close the master db */
|
|
return 0;
|
|
}
|
|
DEBUG(10,("not uptodate[%s] seq[%u=>%u]\n",
|
|
db_ctx->ltdb_path, cseqnum, mseqnum));
|
|
|
|
ret = tdb_transaction_start(db_ctx->ltdb->tdb);
|
|
if (ret != 0) {
|
|
DEBUG(0,("failed to start transaction[%s] %d: %s\n",
|
|
db_ctx->ltdb_path, ret,
|
|
tdb_errorstr(db_ctx->ltdb->tdb)));
|
|
db_ctx->out_of_sync = true;
|
|
return ret;
|
|
}
|
|
db_ctx->local_transaction = true;
|
|
|
|
if (changes && !force) {
|
|
ret = db_tdb2_sync_changes(db_ctx, changes, &mseqnum);
|
|
if (ret != 0) {
|
|
db_ctx->out_of_sync = true;
|
|
tdb_transaction_cancel(db_ctx->ltdb->tdb);
|
|
db_ctx->local_transaction = false;
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = db_tdb2_sync_all(db_ctx, &mseqnum);
|
|
if (ret != 0) {
|
|
db_ctx->out_of_sync = true;
|
|
tdb_transaction_cancel(db_ctx->ltdb->tdb);
|
|
db_ctx->local_transaction = false;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = db_tdb2_commit_local(db_ctx, mseqnum);
|
|
if (ret != 0) {
|
|
db_ctx->out_of_sync = true;
|
|
return ret;
|
|
}
|
|
|
|
db_ctx->out_of_sync = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int db_tdb2_ctx_destructor(struct db_tdb2_ctx *db_tdb2)
|
|
{
|
|
db_tdb2_close_master(db_tdb2);
|
|
if (db_tdb2->local_transaction) {
|
|
tdb_transaction_cancel(db_tdb2->ltdb->tdb);
|
|
}
|
|
db_tdb2->local_transaction = false;
|
|
TALLOC_FREE(db_tdb2->ltdb);
|
|
return 0;
|
|
}
|
|
|
|
static struct db_context *db_open_tdb2_ex(TALLOC_CTX *mem_ctx,
|
|
const char *name,
|
|
int hash_size, int tdb_flags,
|
|
int open_flags, mode_t mode,
|
|
const struct dbwrap_tdb2_changes *chgs)
|
|
{
|
|
struct db_context *result = NULL;
|
|
struct db_tdb2_ctx *db_tdb2;
|
|
int ret;
|
|
const char *md;
|
|
const char *ld;
|
|
const char *bn;
|
|
|
|
bn = strrchr_m(name, '/');
|
|
if (bn) {
|
|
bn++;
|
|
DEBUG(3,("db_open_tdb2: use basename[%s] of abspath[%s]:\n",
|
|
bn, name));
|
|
} else {
|
|
bn = name;
|
|
}
|
|
|
|
md = lp_parm_const_string(-1, "dbwrap_tdb2", "master directory", NULL);
|
|
if (!md) {
|
|
DEBUG(0,("'dbwrap_tdb2:master directory' empty\n"));
|
|
goto fail;
|
|
}
|
|
|
|
ld = lp_parm_const_string(-1, "dbwrap_tdb2", "local directory", NULL);
|
|
if (!ld) {
|
|
DEBUG(0,("'dbwrap_tdb2:local directory' empty\n"));
|
|
goto fail;
|
|
}
|
|
|
|
result = TALLOC_ZERO_P(mem_ctx, struct db_context);
|
|
if (result == NULL) {
|
|
DEBUG(0, ("talloc failed\n"));
|
|
goto fail;
|
|
}
|
|
|
|
result->private_data = db_tdb2 = TALLOC_ZERO_P(result, struct db_tdb2_ctx);
|
|
if (db_tdb2 == NULL) {
|
|
DEBUG(0, ("talloc failed\n"));
|
|
goto fail;
|
|
}
|
|
|
|
db_tdb2->db = result;
|
|
|
|
db_tdb2->open.hash_size = hash_size;
|
|
db_tdb2->open.tdb_flags = tdb_flags;
|
|
db_tdb2->open.open_flags= open_flags;
|
|
db_tdb2->open.mode = mode;
|
|
|
|
db_tdb2->max_buffer_size = lp_parm_ulong(-1, "dbwrap_tdb2",
|
|
"notify buffer size", 512);
|
|
|
|
db_tdb2->name = talloc_strdup(db_tdb2, bn);
|
|
if (db_tdb2->name == NULL) {
|
|
DEBUG(0, ("talloc_strdup failed\n"));
|
|
goto fail;
|
|
}
|
|
|
|
db_tdb2->mtdb_path = talloc_asprintf(db_tdb2, "%s/%s",
|
|
md, bn);
|
|
if (db_tdb2->mtdb_path == NULL) {
|
|
DEBUG(0, ("talloc_asprintf failed\n"));
|
|
goto fail;
|
|
}
|
|
|
|
db_tdb2->ltdb_path = talloc_asprintf(db_tdb2, "%s/%s.tdb2",
|
|
ld, bn);
|
|
if (db_tdb2->ltdb_path == NULL) {
|
|
DEBUG(0, ("talloc_asprintf failed\n"));
|
|
goto fail;
|
|
}
|
|
|
|
db_tdb2->mseqkey = string_term_tdb_data(DB_TDB2_MASTER_SEQNUM_KEYSTR);
|
|
|
|
/*
|
|
* this implicit opens the local one if as it's not yet open
|
|
* it syncs the local copy.
|
|
*/
|
|
ret = db_tdb2_open_master(db_tdb2, false, chgs);
|
|
if (ret != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
ret = db_tdb2_close_master(db_tdb2);
|
|
if (ret != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
DEBUG(10,("db_open_tdb2[%s] opened with mseq[%u]\n",
|
|
db_tdb2->name, db_tdb2->mseqnum));
|
|
|
|
result->fetch_locked = db_tdb2_fetch_locked;
|
|
result->fetch = db_tdb2_fetch;
|
|
result->traverse = db_tdb2_traverse;
|
|
result->traverse_read = db_tdb2_traverse_read;
|
|
result->get_seqnum = db_tdb2_get_seqnum;
|
|
result->persistent = ((tdb_flags & TDB_CLEAR_IF_FIRST) == 0);
|
|
result->transaction_start = db_tdb2_transaction_start;
|
|
result->transaction_commit = db_tdb2_transaction_commit;
|
|
result->transaction_cancel = db_tdb2_transaction_cancel;
|
|
|
|
talloc_set_destructor(db_tdb2, db_tdb2_ctx_destructor);
|
|
|
|
return result;
|
|
|
|
fail:
|
|
if (result != NULL) {
|
|
TALLOC_FREE(result);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct db_context *db_open_tdb2(TALLOC_CTX *mem_ctx,
|
|
const char *name,
|
|
int hash_size, int tdb_flags,
|
|
int open_flags, mode_t mode)
|
|
{
|
|
return db_open_tdb2_ex(mem_ctx, name, hash_size,
|
|
tdb_flags, open_flags, mode, NULL);
|
|
}
|