2008-03-19 19:40:10 +01:00
/*
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 ;
2008-04-13 00:23:58 +02:00
keys [ db_ctx - > changes . num_keys ] . data = ( uint8_t * ) talloc_memdup ( keys ,
key . dptr ,
key . dsize ) ;
2008-03-19 19:40:10 +01:00
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 ) ;
}