/* txn.c - TP support functions of the bdb2 backend */
+#include "portable.h"
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
#include "txn.h"
}
- sprintf( fileName, "%s%s", bdb2i_fixed_filenames[dbFile], LDBM_SUFFIX );
- (*fileNodeH)->dbc_name = strdup( fileName );
+ sprintf( fileName, "%s%s", bdb2i_fixed_filenames[dbFile], BDB2_SUFFIX );
+ (*fileNodeH)->dbc_name = ch_strdup( fileName );
fileNodeH = &(*fileNodeH)->next;
}
+ /* set defaults for checkpointing */
+ head->txn_log = BDB2_TXN_CHKP_MAX_LOG;
+ head->txn_time = BDB2_TXN_CHKP_MAX_TIME;
+
+ /* initialize the txn_dirty_mutex */
+ ldap_pvt_thread_mutex_init( &txn_dirty_mutex );
+
return 0;
}
static void
bdb2i_init_db_file_cache( struct ldbminfo *li, BDB2_TXN_FILES *fileinfo )
{
- time_t curtime;
struct stat st;
char buf[MAXPATHLEN];
- ldap_pvt_thread_mutex_lock( ¤ttime_mutex );
- curtime = currenttime;
- ldap_pvt_thread_mutex_unlock( ¤ttime_mutex );
-
fileinfo->dbc_refcnt = 1;
- fileinfo->dbc_lastref = curtime;
- sprintf( buf, "%s%s%s", li->li_directory, DEFAULT_DIRSEP,
- fileinfo->dbc_name );
+ sprintf( buf, "%s" LDAP_DIRSEP "%s",
+ li->li_directory, fileinfo->dbc_name );
+
if ( stat( buf, &st ) == 0 ) {
fileinfo->dbc_blksize = st.st_blksize;
} else {
}
+/* create a DB file cache entry for a specified index attribute
+ (if not already done); the function is called during config
+ file read for all index'ed attributes; if "default" index with
+ a non-none selection is given, this is remembered for run-time
+ extension of the list of index files; the function is also
+ called before add or modify operations to check for putative
+ new "default" index files; at that time, files are also opened
+*/
void
bdb2i_txn_attr_config(
struct ldbminfo *li,
BDB2_TXN_FILES **fileNodeH;
char fileName[MAXPATHLEN];
- sprintf( fileName, "%s%s", attr, LDBM_SUFFIX );
+ sprintf( fileName, "%s%s", attr, BDB2_SUFFIX );
/* search for the end of the list or a node describing
the current attribute */
}
}
- p->dbc_name = strdup( fileName );
+ p->dbc_name = ch_strdup( fileName );
/* if requested for, we have to open the DB file */
/* BUT NOT "objectclass", 'cause that's a default index ! */
if ( open && strcasecmp( fileName, "objectclass" )) {
/* re-use filename to get the complete path */
- sprintf( fileName, "%s%s%s",
- li->li_directory, DEFAULT_DIRSEP, p->dbc_name );
+ sprintf( fileName, "%s" LDAP_DIRSEP "%s",
+ li->li_directory, p->dbc_name );
/* since we have an mpool, we should not define a cache size */
- p->dbc_db = ldbm_open( fileName, LDBM_WRCREAT, li->li_mode, 0 );
+ p->dbc_db = bdb2i_db_open( fileName, DB_TYPE,
+ LDBM_WRCREAT, li->li_mode, 0 );
/* if the files could not be opened, something is wrong;
complain */
}
+/* open the NEXTID file for read/write; if it does not exist,
+ create it (access to the file must be preceeded by a rewind)
+*/
+static int
+bdb2i_open_nextid( BackendDB *be )
+{
+ struct ldbminfo *li = (struct ldbminfo *) be->be_private;
+ BDB2_TXN_HEAD *head = &li->li_txn_head;
+ LDBM db = NULL;
+ DB_INFO dbinfo;
+ char fileName[MAXPATHLEN];
+
+ sprintf( fileName, "%s" LDAP_DIRSEP "%s",
+ li->li_directory, NEXTID_NAME );
+
+ /* try to open the file for read and write */
+ memset( &dbinfo, 0, sizeof( dbinfo ));
+ dbinfo.db_pagesize = DEFAULT_DB_PAGE_SIZE;
+ dbinfo.db_malloc = ldbm_malloc;
+
+ (void) db_open( fileName, DB_RECNO, DB_CREATE | DB_THREAD,
+ li->li_mode, &bdb2i_dbEnv, &dbinfo, &db );
+
+ if ( db == NULL ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_open_nextid: could not open \"%s\"\n",
+ NEXTID_NAME, 0, 0 );
+ return( -1 );
+
+ }
+
+ /* the file is open for read/write */
+ head->nextidFile = db;
+
+ return( 0 );
+}
+
+
+/* open all DB during startup of the backend (necessary due to TP)
+ additional files may be opened during slapd life-time due to
+ default indexes (must be configured in slapd.conf;
+ see bdb2i_txn_attr_config)
+ also, set the counter and timer for TP checkpointing
+*/
int
-bdb2i_txn_open_files( struct ldbminfo *li )
+bdb2i_txn_open_files( BackendDB *be )
{
+ struct ldbminfo *li = (struct ldbminfo *) be->be_private;
BDB2_TXN_HEAD *head = &li->li_txn_head;
BDB2_TXN_FILES *dbFile;
+ int rc;
for ( dbFile = head->dbFiles; dbFile; dbFile = dbFile->next ) {
char fileName[MAXPATHLEN];
- sprintf( fileName, "%s%s%s",
- li->li_directory, DEFAULT_DIRSEP, dbFile->dbc_name );
+ sprintf( fileName, "%s" LDAP_DIRSEP "%s",
+ li->li_directory, dbFile->dbc_name );
/* since we have an mpool, we should not define a cache size */
- dbFile->dbc_db = ldbm_open( fileName, LDBM_WRCREAT, li->li_mode, 0 );
+ dbFile->dbc_db = bdb2i_db_open( fileName, DB_TYPE,
+ LDBM_WRCREAT, li->li_mode, 0 );
/* if the files could not be opened, something is wrong; complain */
if ( dbFile->dbc_db == NULL ) {
Debug( LDAP_DEBUG_ANY,
"bdb2i_txn_open_files(): couldn't open file \"%s\" -- FATAL.\n",
dbFile->dbc_name, 0, 0 );
- return( 1 );
+ return( -1 );
}
}
- return 0;
+ rc = bdb2i_open_nextid( be );
+
+ txn_max_pending_log = head->txn_log;
+ txn_max_pending_time = head->txn_time;
+
+ return rc;
}
+/* close all DB files during shutdown of the backend */
void
bdb2i_txn_close_files( BackendDB *be )
{
ldbm_close( dbFile->dbc_db );
}
+
+ if ( head->nextidFile )
+ ldbm_close( head->nextidFile );
+
}
+/* get the db_cache structure associated with a specified
+ DB file (replaces the on-the-fly opening of files in cache_open()
+*/
BDB2_TXN_FILES *
bdb2i_get_db_file_cache( struct ldbminfo *li, char *name )
{
struct dirent *file;
if ( ( datadir = opendir( li->li_directory ) ) == NULL ) {
+ int err = errno;
Debug( LDAP_DEBUG_ANY,
"bdb2i_check_additional_attr_index(): ERROR while opening datadir: %s\n",
- strerror( errno ), 0, 0 );
+ strerror( err ), 0, 0 );
return( 1 );
}
strcpy( filename, file->d_name );
namelen = strlen( filename );
- if ( namelen > strlen( LDBM_SUFFIX )) {
+ if ( namelen > strlen( BDB2_SUFFIX )) {
- if ( !strcasecmp( filename + namelen - strlen( LDBM_SUFFIX ),
- LDBM_SUFFIX )) {
+ if ( !strcasecmp( filename + namelen - strlen( BDB2_SUFFIX ),
+ BDB2_SUFFIX )) {
- *(filename + namelen - strlen( LDBM_SUFFIX )) = '\0';
+ *(filename + namelen - strlen( BDB2_SUFFIX )) = '\0';
bdb2i_txn_attr_config( li, filename, 0 );
Debug( LDAP_DEBUG_TRACE, "INDEX FILE: %s\n", filename, 0, 0 );
}
+/* get the next ID from the NEXTID file */
+ID
+bdb2i_get_nextid( BackendDB *be )
+{
+ struct ldbminfo *li = (struct ldbminfo *) be->be_private;
+ BDB2_TXN_HEAD *head = &li->li_txn_head;
+ ID id;
+ Datum key;
+ Datum data;
+ db_recno_t rec = NEXTID_RECNO;
+
+ ldbm_datum_init( key );
+ ldbm_datum_init( data );
+
+ key.data = &rec;
+ key.size = sizeof( rec );
+
+ data = bdb2i_db_fetch( head->nextidFile, key );
+ if ( data.data == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "next_id_read: could not get nextid from \"%s\"\n",
+ NEXTID_NAME, 0, 0 );
+ return NOID;
+ }
+
+ id = atol( data.data );
+ ldbm_datum_free( head->nextidFile, data );
+
+ if ( id < 1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "next_id_read %ld: return non-positive integer\n",
+ id, 0, 0 );
+ return NOID;
+ }
+
+ return( id );
+}
+
+
+int
+bdb2i_put_nextid( BackendDB *be, ID id )
+{
+ struct ldbminfo *li = (struct ldbminfo *) be->be_private;
+ BDB2_TXN_HEAD *head = &li->li_txn_head;
+ int rc, flags;
+ Datum key;
+ Datum data;
+ db_recno_t rec = NEXTID_RECNO;
+ char buf[20];
+
+ sprintf( buf, "%ld\n", id );
+
+ ldbm_datum_init( key );
+ ldbm_datum_init( data );
+
+ key.data = &rec;
+ key.size = sizeof( rec );
+
+ data.data = &buf;
+ data.size = sizeof( buf );
+
+ flags = LDBM_REPLACE;
+ if ( li->li_dbcachewsync ) flags |= LDBM_SYNC;
+
+ if (( rc = bdb2i_db_store( head->nextidFile, key, data, flags )) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "next_id_write(%ld): store failed (%d)\n",
+ id, rc, 0 );
+ return( -1 );
+ }
+
+ return( rc );
+}
+
+
+/* BDB2 backend-private functions of libldbm */
+LDBM
+bdb2i_db_open(
+ char *name,
+ int type,
+ int rw,
+ int mode,
+ int dbcachesize )
+{
+ LDBM ret = NULL;
+ DB_INFO dbinfo;
+
+ memset( &dbinfo, 0, sizeof( dbinfo ));
+ if ( bdb2i_dbEnv.mp_info == NULL )
+ dbinfo.db_cachesize = dbcachesize;
+ dbinfo.db_pagesize = DEFAULT_DB_PAGE_SIZE;
+ dbinfo.db_malloc = ldbm_malloc;
+
+ (void) db_open( name, type, rw, mode, &bdb2i_dbEnv, &dbinfo, &ret );
+
+ return( ret );
+}
+
+
+int
+bdb2i_db_store( LDBM ldbm, Datum key, Datum data, int flags )
+{
+ int rc;
+
+ rc = (*ldbm->put)( ldbm, txnid, &key, &data, flags & ~LDBM_SYNC );
+ rc = (-1 ) * rc;
+
+ if ( txnid != NULL ) {
+
+ /* if the store was OK, set the dirty flag,
+ otherwise set the abort flag */
+ if ( rc == 0 ) {
+
+ txn_dirty = 1;
+
+ } else {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_store: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ }
+
+ return( rc );
+}
+
+
+int
+bdb2i_db_delete( LDBM ldbm, Datum key )
+{
+ int rc;
+
+ rc = (*ldbm->del)( ldbm, txnid, &key, 0 );
+ rc = (-1 ) * rc;
+
+ if ( txnid != NULL ) {
+
+ /* if the delete was OK, set the dirty flag,
+ otherwise set the abort flag */
+ if ( rc == 0 ) {
+
+ txn_dirty = 1;
+
+ } else {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_delete: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ }
+
+ return( rc );
+}
+
+
+Datum
+bdb2i_db_fetch( LDBM ldbm, Datum key )
+{
+ Datum data;
+ int rc;
+
+ ldbm_datum_init( data );
+ data.flags = DB_DBT_MALLOC;
+
+ if ( (rc = (*ldbm->get)( ldbm, txnid, &key, &data, 0 )) != 0 ) {
+ if (( txnid != NULL ) && ( rc != DB_NOTFOUND )) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_fetch: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ if ( data.dptr ) free( data.dptr );
+ data.dptr = NULL;
+ data.dsize = 0;
+ }
+
+ return( data );
+}
+
+
+Datum
+bdb2i_db_firstkey( LDBM ldbm, DBC **dbch )
+{
+ Datum key, data;
+ int rc;
+ DBC *dbci;
+
+ ldbm_datum_init( key );
+ ldbm_datum_init( data );
+
+ key.flags = data.flags = DB_DBT_MALLOC;
+
+#if defined( DB_VERSION_MAJOR ) && defined( DB_VERSION_MINOR ) && \
+ DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6
+
+ if ( (*ldbm->cursor)( ldbm, txnid, &dbci ))
+
+#else
+
+ if ( (*ldbm->cursor)( ldbm, txnid, &dbci, 0 ))
+
+#endif
+ {
+ if ( txnid != NULL ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_firstkey: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ key.flags = 0;
+ return( key );
+ } else {
+ *dbch = dbci;
+ if ( (*dbci->c_get)( dbci, &key, &data, DB_NEXT ) == 0 ) {
+ ldbm_datum_free( ldbm, data );
+ } else {
+ if ( txnid != NULL ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_firstkey: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ ldbm_datum_free( ldbm, key );
+ key.flags = 0;
+ key.dptr = NULL;
+ key.dsize = 0;
+ }
+ }
+
+ return( key );
+}
+
+
+Datum
+bdb2i_db_nextkey( LDBM ldbm, Datum key, DBC *dbcp )
+{
+ Datum data;
+ int rc;
+
+ ldbm_datum_init( data );
+ ldbm_datum_free( ldbm, key );
+ key.flags = data.flags = DB_DBT_MALLOC;
+
+ if ( (*dbcp->c_get)( dbcp, &key, &data, DB_NEXT ) == 0 ) {
+ ldbm_datum_free( ldbm, data );
+ } else {
+ if ( txnid != NULL ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_db_nextkey: transaction failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+
+ }
+ key.flags = 0;
+ key.dptr = NULL;
+ key.dsize = 0;
+ }
+
+ return( key );
+}
+
+
+/* Transaction control of write access */
+/* Since these functions are only used by one writer at a time,
+ we do not have any concurrency (locking) problem */
+
+/* initialize a new transaction */
+int
+bdb2i_start_transction( DB_TXNMGR *txmgr )
+{
+ int rc;
+
+ txnid = NULL;
+ txn_do_abort = 0;
+
+ if (( rc = txn_begin( txmgr, NULL, &txnid )) != 0 ) {
+ int err = errno;
+ Debug( LDAP_DEBUG_ANY, "bdb2i_start_transction failed: %d: errno=%s\n",
+ rc, strerror( err ), 0 );
+
+ if ( txnid != NULL )
+ (void) txn_abort( txnid );
+ return( -1 );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "bdb2i_start_transaction: transaction started.\n",
+ 0, 0, 0 );
+
+ return( 0 );
+}
+
+
+/* finish the transaction */
+int
+bdb2i_finish_transaction()
+{
+ int rc = 0;
+
+ /* if transaction was NOT selected, just return */
+ if ( txnid == NULL ) return( 0 );
+
+ /* if nothing was wrong so far, we can try to commit the transaction */
+ /* complain, if the commit fails */
+ if (( txn_do_abort == 0 ) && ( txn_commit( txnid )) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_finish_transaction: transaction commit failed: aborted.\n",
+ 0, 0, 0 );
+ txn_do_abort = 1;
+ }
+
+ /* if anything went wrong, we have to abort the transaction */
+ if ( txn_do_abort ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_finish_transaction: transaction aborted.\n",
+ 0, 0, 0 );
+ (void) txn_abort( txnid );
+ rc = -1;
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "bdb2i_finish_transaction: transaction commited.\n",
+ 0, 0, 0 );
+ }
+
+ /* XXX do NOT free the txnid memory !!! */
+ txnid = NULL;
+ txn_do_abort = 0;
+
+ return( rc );
+}
+
+
+/* set a checkpoint
+ either forced (during shutdown) or when logsize or time are exceeded
+ (is called by reader and writer, so protect txn_dirty)
+*/
+int
+bdb2i_set_txn_checkpoint( DB_TXNMGR *txmgr, int forced )
+{
+ int rc = 0;
+
+ /* set dirty mutex */
+ ldap_pvt_thread_mutex_lock( &txn_dirty_mutex );
+
+ if ( txn_dirty ) {
+ int rc;
+ u_int32_t logsize;
+ u_int32_t mins;
+ time_t now;
+
+ logsize = forced ? (u_int32_t) 0 : txn_max_pending_log;
+ mins = forced ? (u_int32_t) 0 : txn_max_pending_time;
+
+ now = slap_get_time();
+
+ rc = txn_checkpoint( txmgr, logsize, mins );
+
+ /* if checkpointing was successful, reset txn_dirty */
+ if ( rc == 0 ) {
+ DB_TXN_STAT *statp = NULL;
+
+ /* check whether the checkpoint was actually written;
+ if so, unset the txn_dirty flag */
+ if (( rc = txn_stat( txmgr, &statp, ldbm_malloc )) == 0 ) {
+
+ if ( statp && ( statp->st_time_ckp >= now )) {
+
+ Debug( LDAP_DEBUG_TRACE,
+ "bdb2i_set_txn_checkpoint succeded.\n",
+ 0, 0, 0 );
+ txn_dirty = 0;
+
+ }
+
+ if ( statp ) free( statp );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "bdb2i_set_txn_checkpoint: txn_stat failed: %d\n",
+ rc, 0, 0 );
+ }
+ } else {
+ Debug( LDAP_DEBUG_ANY, "bdb2i_set_txn_checkpoint failed: %d\n",
+ rc, 0, 0 );
+ }
+ }
+
+ /* release dirty mutex */
+ ldap_pvt_thread_mutex_unlock( &txn_dirty_mutex );
+
+ return( rc );
+}
+
+