/* * Initialize ENTRYID and USN sequence. */ static DWORD MDBInitSequence( PVDIR_MDB_DB pDB, VDIR_DB mdbDbi ) { DWORD dwError = 0; PVDIR_DB_TXN pTxn = NULL; VDIR_DB_DBT key = {0}; VDIR_DB_DBT value = {0}; unsigned char EidBytes[sizeof( ENTRYID )] = {0}; ENTRYID initEIDValue = ENTRY_ID_SEQ_INITIAL_VALUE; ENTRYID initUNSValue = USN_SEQ_INITIAL_VALUE; assert(pDB); dwError = mdb_txn_begin( pDB->mdbEnv, NULL, BE_DB_FLAGS_ZERO, &pTxn ); BAIL_ON_VMDIR_ERROR(dwError); key.mv_data = &EidBytes[0]; MDBEntryIdToDBT(BE_MDB_ENTRYID_SEQ_KEY, &key); dwError = mdb_get(pTxn, mdbDbi, &key, &value); if (dwError == MDB_NOTFOUND) { // first time, initialize two sequence records value.mv_data = &initEIDValue; value.mv_size = sizeof(initEIDValue); // set entryid sequence record dwError = mdb_put(pTxn, mdbDbi, &key, &value, MDB_NOOVERWRITE); BAIL_ON_VMDIR_ERROR(dwError); MDBEntryIdToDBT(BE_MDB_USN_SEQ_KEY, &key); value.mv_data = &initUNSValue; value.mv_size = sizeof(initUNSValue); // set usn sequence record dwError = mdb_put(pTxn, mdbDbi, &key, &value, MDB_NOOVERWRITE); BAIL_ON_VMDIR_ERROR(dwError); } dwError = mdb_txn_commit(pTxn); pTxn = NULL; BAIL_ON_VMDIR_ERROR(dwError); cleanup: return dwError; error: if (pTxn) { mdb_txn_abort(pTxn); } goto cleanup; }
// To get the current max ENTRYID DWORD VmDirMDBMaxEntryId( PVDIR_BACKEND_CTX pBECtx, ENTRYID* pEId) { DWORD dwError = 0; PVDIR_DB_TXN pTxn = NULL; MDB_val key = {0}; MDB_val value = {0}; unsigned char EIDBytes[sizeof( ENTRYID )] = {0}; assert(pBECtx && pEId); dwError = mdb_txn_begin( gVdirMdbGlobals.mdbEnv, NULL, MDB_RDONLY, &pTxn ); BAIL_ON_VMDIR_ERROR(dwError); key.mv_data = &EIDBytes[0]; MDBEntryIdToDBT(BE_MDB_ENTRYID_SEQ_KEY, &key); dwError = mdb_get(pTxn, gVdirMdbGlobals.mdbSeqDBi, &key, &value); BAIL_ON_VMDIR_ERROR(dwError); dwError = mdb_txn_commit(pTxn); pTxn = NULL; BAIL_ON_VMDIR_ERROR(dwError); assert(value.mv_size == sizeof(ENTRYID)); *pEId = *((ENTRYID*)value.mv_data); cleanup: return dwError; error: if (pTxn) { mdb_txn_abort(pTxn); } VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "VmDirMDBMaxEntryId: failed with error (%d),(%s)", dwError, mdb_strerror(dwError) ); dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, "MaxEntryId"); goto cleanup; }
/* MdbEIdToEntry: For a given entry ID, reads an entry from the entry DB. * * Returns: BE error - BACKEND_ERROR, BACKEND OPERATIONS, BACKEND_ENTRY_NOTFOUND * */ DWORD VmDirMDBEIdToEntry( PVDIR_BACKEND_CTX pBECtx, PVDIR_SCHEMA_CTX pSchemaCtx, ENTRYID eId, PVDIR_ENTRY pEntry, VDIR_BACKEND_ENTRY_LOCKTYPE entryLockType) { DWORD dwError = 0; VDIR_DB mdbDBi = 0; PVDIR_DB_TXN pTxn = NULL; VDIR_DB_DBT key = {0}; VDIR_DB_DBT value = {0}; unsigned char eIdBytes[sizeof( ENTRYID )] = {0}; unsigned char* pszBlob = NULL; assert(pBECtx && pBECtx->pBEPrivate && pSchemaCtx && pEntry); pTxn = (PVDIR_DB_TXN)pBECtx->pBEPrivate; mdbDBi = gVdirMdbGlobals.mdbEntryDB.pMdbDataFiles[0].mdbDBi; // Set key key.mv_data = &eIdBytes[0]; MDBEntryIdToDBT(eId, &key); if ((dwError = mdb_get(pTxn, mdbDBi, &key, &value) ) != 0) { dwError = MDBToBackendError(dwError, MDB_NOTFOUND, ERROR_BACKEND_ENTRY_NOTFOUND, pBECtx, "EIDToEntry"); BAIL_ON_VMDIR_ERROR( dwError ); } if ((dwError = VmDirAllocateMemory( value.mv_size, (PVOID *)&pszBlob)) != 0) { dwError = ERROR_BACKEND_OPERATIONS; BAIL_ON_VMDIR_ERROR( dwError ); } if ((dwError = VmDirCopyMemory(pszBlob, value.mv_size, value.mv_data, value.mv_size)) != 0) { dwError = ERROR_BACKEND_OPERATIONS; BAIL_ON_VMDIR_ERROR( dwError ); } // encodedEntry takes over pszBlob pEntry->encodedEntry = pszBlob; pszBlob = NULL; pEntry->eId = eId; dwError = VmDirDecodeEntry(pSchemaCtx, pEntry ); BAIL_ON_VMDIR_ERROR(dwError); cleanup: return dwError; error: VMDIR_LOG_ERROR( LDAP_DEBUG_BACKEND, "VmDirMDBEIdToEntry, eid(%u) failed (%u)", eId, dwError); VMDIR_SAFE_FREE_MEMORY(pszBlob); VmDirFreeEntryContent( pEntry ); VMDIR_SET_BACKEND_ERROR(dwError); // if dwError no in BE space, set to ERROR_BACKEND_ERROR goto cleanup; }
/* MdbAddEntry: Creates an entry in the MDB DBs. * * Returns: BE error codes. * */ DWORD VmDirMDBAddEntry( PVDIR_BACKEND_CTX pBECtx, PVDIR_ENTRY pEntry) { DWORD dwError = 0; ENTRYID entryId = 0; VDIR_DB_TXN* pTxn = NULL; VDIR_BERVALUE encodedEntry = VDIR_BERVALUE_INIT; VDIR_ATTRIBUTE * nextAttr = NULL; assert( pEntry && pBECtx && pBECtx->pBEPrivate ); pTxn = (PVDIR_DB_TXN)pBECtx->pBEPrivate; dwError = VmDirEncodeEntry( pEntry, &encodedEntry ); BAIL_ON_VMDIR_ERROR(dwError); if (pEntry->eId != 0) // Reserved entries have eId already { entryId = pEntry->eId; } else { VDIR_DB_DBT EIDkey = {0}; VDIR_DB_DBT EIDvalue = {0}; unsigned char EIDKeyBytes[sizeof( ENTRYID )] = {0}; unsigned char EIDValueBytes[sizeof( ENTRYID )] = {0}; EIDkey.mv_data = &EIDKeyBytes[0]; MDBEntryIdToDBT(BE_MDB_ENTRYID_SEQ_KEY, &EIDkey); dwError = mdb_get(pTxn, gVdirMdbGlobals.mdbSeqDBi, &EIDkey, &EIDvalue); BAIL_ON_VMDIR_ERROR(dwError); assert( EIDvalue.mv_size == sizeof(ENTRYID) ); entryId = *((ENTRYID*)EIDvalue.mv_data); *((ENTRYID*)&EIDValueBytes[0]) = entryId + 1; EIDvalue.mv_data = &EIDValueBytes[0]; EIDvalue.mv_size = sizeof(ENTRYID); dwError = mdb_put(pTxn, gVdirMdbGlobals.mdbSeqDBi, &EIDkey, &EIDvalue, BE_DB_FLAGS_ZERO); BAIL_ON_VMDIR_ERROR(dwError); } assert( entryId > 0 ); if ((dwError = MDBCreateParentIdIndex(pBECtx, &(pEntry->pdn), entryId)) != 0) { dwError = MDBToBackendError(dwError, ERROR_BACKEND_ENTRY_NOTFOUND, ERROR_BACKEND_PARENT_NOTFOUND, pBECtx, "CreateParentIdIndex"); BAIL_ON_VMDIR_ERROR( dwError ); } // Update DN index first. this make sure we always return ERROR_BACKEND_ENTRY_EXISTS in such case. for (nextAttr = pEntry->attrs; nextAttr != NULL; nextAttr = nextAttr->next) { if (VmDirStringCompareA(nextAttr->type.lberbv.bv_val, ATTR_DN, FALSE) == 0) { // make sure we store normalized DN value. dwError = VmDirNormalizeDN( &(nextAttr->vals[0]), pEntry->pSchemaCtx ); BAIL_ON_VMDIR_ERROR(dwError); if ((dwError = MdbUpdateIndicesForAttr( pTxn, &(nextAttr->type), nextAttr->vals, nextAttr->numVals, entryId, BE_INDEX_OP_TYPE_CREATE)) != 0) { dwError = MDBToBackendError( dwError, MDB_KEYEXIST, ERROR_BACKEND_ENTRY_EXISTS, pBECtx, VDIR_SAFE_STRING(nextAttr->vals[0].bvnorm_val)); BAIL_ON_VMDIR_ERROR( dwError ); } if ((dwError = MdbUpdateAttrMetaData( pTxn, nextAttr, entryId, BE_INDEX_OP_TYPE_CREATE )) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, "UpdateDNAttrMetaData"); BAIL_ON_VMDIR_ERROR( dwError ); } } } // Update remaining indices for (nextAttr = pEntry->attrs; nextAttr != NULL; nextAttr = nextAttr->next) { if (VmDirStringCompareA(nextAttr->type.lberbv.bv_val, ATTR_DN, FALSE) != 0) { if ((dwError = MdbUpdateIndicesForAttr( pTxn, &(nextAttr->type), nextAttr->vals, nextAttr->numVals, entryId, BE_INDEX_OP_TYPE_CREATE)) != 0) { dwError = MDBToBackendError( dwError, MDB_KEYEXIST, ERROR_BACKEND_CONSTRAINT, pBECtx, VDIR_SAFE_STRING(nextAttr->type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } if ((dwError = MdbUpdateAttrMetaData( pTxn, nextAttr, entryId, BE_INDEX_OP_TYPE_CREATE )) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, VDIR_SAFE_STRING(nextAttr->type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } } } // Update entry/blob database if ((dwError = MdbCreateEIDIndex(pTxn, entryId, &encodedEntry, TRUE /* 1st time new entry creation */)) != 0) { dwError = MDBToBackendError(dwError, MDB_KEYEXIST, ERROR_BACKEND_CONSTRAINT, pBECtx, "CreateEIDIndex"); BAIL_ON_VMDIR_ERROR( dwError ); } cleanup: VMDIR_SAFE_FREE_MEMORY( encodedEntry.lberbv.bv_val ); return dwError; error: // TODO set pBECtx->pszBEErrorMsg? VMDIR_LOG_ERROR( LDAP_DEBUG_BACKEND, "BEAddEntry DN (%s), (%u)(%s)", VDIR_SAFE_STRING(pEntry->dn.lberbv.bv_val), dwError, VDIR_SAFE_STRING(pBECtx->pszBEErrorMsg)); VMDIR_SET_BACKEND_ERROR(dwError); // if dwError no in BE space, set to ERROR_BACKEND_ERROR goto cleanup; }
/* * Get the next available USN number. */ DWORD VmDirMDBGetNextUSN( PVDIR_BACKEND_CTX pBECtx, USN * pUsn) { DWORD dwError = 0; PVDIR_DB_TXN pTxn = NULL; PVDIR_DB_TXN pLocalTxn = NULL; VDIR_DB_DBT key = {0}; VDIR_DB_DBT value = {0}; unsigned char USNKeyBytes[sizeof( USN )] = {0}; unsigned char USNValueBytes[sizeof( USN )] = {0}; USN localUSN = 0; BOOLEAN bRevertUSN = FALSE; BOOLEAN bUnsetMaxUSN = TRUE; assert( pBECtx && pUsn ); pTxn = (PVDIR_DB_TXN)pBECtx->pBEPrivate; if (pTxn) { pLocalTxn = pTxn; } else { dwError = mdb_txn_begin( gVdirMdbGlobals.mdbEnv, BE_DB_PARENT_TXN_NULL, BE_DB_FLAGS_ZERO, &pLocalTxn ); BAIL_ON_VMDIR_ERROR(dwError); } key.mv_data = &USNKeyBytes[0]; MDBEntryIdToDBT(BE_MDB_USN_SEQ_KEY, &key); dwError = mdb_get(pLocalTxn, gVdirMdbGlobals.mdbSeqDBi, &key, &value); BAIL_ON_VMDIR_ERROR(dwError); assert( value.mv_size == sizeof(USN) ); localUSN = *((USN*)value.mv_data); *((USN*)&USNValueBytes[0]) = localUSN + 1; value.mv_size = sizeof(USN); value.mv_data = &USNValueBytes[0]; dwError = mdb_put(pLocalTxn, gVdirMdbGlobals.mdbSeqDBi, &key, &value, BE_DB_FLAGS_ZERO); BAIL_ON_VMDIR_ERROR(dwError); if (pBECtx->wTxnUSN == 0) { // capture and set outstanding USN within txn scope pBECtx->wTxnUSN = localUSN; dwError = VmDirBackendAddOutstandingUSN( pBECtx ); BAIL_ON_VMDIR_ERROR(dwError); bRevertUSN = TRUE; } else { // if BECtx has wTxnUSN already (nested txn), it should be smaller than new USN here. if (pBECtx->wTxnUSN >= localUSN) { VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "nested OP/TXN USN ordering logic error: (%ld)(%ld)", pBECtx->wTxnUSN, localUSN ); dwError = LDAP_OPERATIONS_ERROR; BAIL_ON_VMDIR_ERROR(dwError); } VmDirBackendSetMaxOutstandingUSN(pBECtx, localUSN); bUnsetMaxUSN = TRUE; } if (pLocalTxn != pTxn) { dwError = mdb_txn_commit(pLocalTxn); pLocalTxn = NULL; BAIL_ON_VMDIR_ERROR(dwError); } *pUsn = localUSN; cleanup: return dwError; error: if (bUnsetMaxUSN) { VmDirBackendSetMaxOutstandingUSN(pBECtx, localUSN - 1); } if (bRevertUSN) { VmDirBackendRemoveOutstandingUSN( pBECtx ); pBECtx->wTxnUSN = 0; } if (pLocalTxn && pLocalTxn != pTxn) { mdb_txn_abort(pLocalTxn); } VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "VmDirMDBGetNextUSN: failed with error (%d),(%s)", dwError, mdb_strerror(dwError) ); dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, "GetNextUSN"); goto cleanup; }
// To get the current max ENTRYID // Entry database is separated from log databases. DWORD VmDirMDBMaxEntryId( PVDIR_BACKEND_INTERFACE pBE, ENTRYID* pEId ) { DWORD dwError = 0; VDIR_DB mdbDBi = 0; PVDIR_DB_DBC pCursor = NULL; MDB_val key = {0}; MDB_val value = {0}; ENTRYID eId = {0}; unsigned char EIDBytes[sizeof(ENTRYID)] = {0}; VDIR_BACKEND_CTX mdbBECtx = {0}; BOOLEAN bHasTxn = FALSE; PVDIR_MDB_DB pDB = VmDirSafeDBFromBE(pBE); assert(pEId && pDB); mdbBECtx.pBE = pBE; dwError = VmDirMDBTxnBegin(&mdbBECtx, VDIR_BACKEND_TXN_READ, &bHasTxn); BAIL_ON_VMDIR_ERROR(dwError); mdbDBi = pDB->mdbEntryDB.pMdbDataFiles[0].mdbDBi; dwError = mdb_cursor_open((PVDIR_DB_TXN)mdbBECtx.pBEPrivate, mdbDBi, &pCursor); BAIL_ON_VMDIR_ERROR(dwError); key.mv_data = &EIDBytes[0]; if (pBE == VmDirBackendSelect(ALIAS_MAIN)) { //Set cursor position equal or above LOG_ENTRY_EID_PREFIX // it would find the first entry id of raft log entry on legacy data // i.e. database creaded before we implemented a split log. eId = LOG_ENTRY_EID_PREFIX; MDBEntryIdToDBT(eId, &key); dwError = mdb_cursor_get(pCursor, &key, &value, MDB_SET_RANGE); if (dwError == MDB_NOTFOUND) { //mainDb was created after we implemented split log (no log entries in mainDb) // - simply find the last id which will be the last entry-id in normal entries. eId = 0; MDBEntryIdToDBT(eId, &key); dwError = mdb_cursor_get(pCursor, &key, &value, MDB_LAST); BAIL_ON_VMDIR_ERROR(dwError); MDBDBTToEntryId(&key, &eId); } else { BAIL_ON_VMDIR_ERROR(dwError); //Found an entry that is LOG_ENTRY_EID_PREFIX or above // - search backward, and the first one is the last normal entry do { dwError = mdb_cursor_get(pCursor, &key, &value, MDB_PREV); BAIL_ON_VMDIR_ERROR(dwError); MDBDBTToEntryId(&key, &eId); } while (eId >= LOG_ENTRY_EID_PREFIX); } } else { //For logDb, the last one is the log entry. eId = 0; MDBEntryIdToDBT(eId, &key); dwError = mdb_cursor_get(pCursor, &key, &value, MDB_LAST); BAIL_ON_VMDIR_ERROR(dwError); MDBDBTToEntryId(&key, &eId); } mdb_cursor_close(pCursor); pCursor = NULL; if (bHasTxn) { dwError = VmDirMDBTxnCommit(&mdbBECtx); bHasTxn = FALSE; BAIL_ON_VMDIR_ERROR(dwError); } *pEId = eId; cleanup: return dwError; error: VMDIR_LOG_ERROR( VMDIR_LOG_MASK_ALL, "VmDirMDBMaxEntryId: failed with error (%d),(%s)", dwError, mdb_strerror(dwError) ); mdb_cursor_close(pCursor); if (bHasTxn) { VmDirMDBTxnAbort(&mdbBECtx); } VMDIR_SET_BACKEND_ERROR(dwError); goto cleanup; }