/* VmDirMDBDNToEntry: For a given entry DN, reads an entry from the entry DB. * * Returns: BE error - BACKEND_ERROR, BACKEND OPERATIONS, BACKEND_ENTRY_NOTFOUND * */ DWORD VmDirMDBDNToEntry( PVDIR_BACKEND_CTX pBECtx, PVDIR_SCHEMA_CTX pSchemaCtx, VDIR_BERVALUE* pDn, PVDIR_ENTRY pEntry, VDIR_BACKEND_ENTRY_LOCKTYPE entryLockType) { DWORD dwError = LDAP_SUCCESS; ENTRYID eId = {0}; // make sure we look up normalized dn value dwError = VmDirNormalizeDN( pDn, pSchemaCtx ); BAIL_ON_VMDIR_ERROR(dwError); dwError = VmDirMDBDNToEntryId( pBECtx, pDn, &eId ); BAIL_ON_VMDIR_ERROR( dwError ); dwError = VmDirMDBEIdToEntry( pBECtx, pSchemaCtx, eId, pEntry, entryLockType ); BAIL_ON_VMDIR_ERROR( dwError ); cleanup: return dwError; error: VMDIR_LOG_ERROR( LDAP_DEBUG_BACKEND, "BEDNToEntry DN (%s) failed, (%u)(%s)", VDIR_SAFE_STRING( pDn->bvnorm_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; }
/* MdbDeleteEntry: Deletes an entry in the MDB DBs. * * Returns: BE error codes. * */ DWORD VmDirMDBDeleteEntry( PVDIR_BACKEND_CTX pBECtx, PVDIR_MODIFICATION pMods, PVDIR_ENTRY pEntry ) { DWORD dwError = 0; VDIR_MODIFICATION * mod = NULL; PVDIR_DB_TXN pTxn = NULL; assert( pBECtx && pBECtx->pBEPrivate && pMods && pEntry ); pTxn = (PVDIR_DB_TXN)pBECtx->pBEPrivate; // Delete child from the parentId index dwError = MDBDeleteParentIdIndex( pBECtx, &(pEntry->pdn), pEntry->eId ); BAIL_ON_VMDIR_ERROR(dwError); for (mod = pMods; mod != NULL; mod = mod->next) { switch (mod->operation) { case MOD_OP_DELETE: if ((dwError = MdbUpdateIndicesForAttr( pBECtx->pBE, pTxn, &(pEntry->dn), &(mod->attr.type), mod->attr.vals, mod->attr.numVals, pEntry->eId, BE_INDEX_OP_TYPE_DELETE )) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, VDIR_SAFE_STRING(mod->attr.type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } break; default: assert( FALSE ); } } // Delete Entry Blob if ((dwError = MDBDeleteEntryBlob(pBECtx, pEntry->eId)) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, "MDBDeleteEntryBlob"); BAIL_ON_VMDIR_ERROR( dwError ); } cleanup: return dwError; error: VMDIR_SET_BACKEND_ERROR(dwError); // if dwError no in BE space, set to ERROR_BACKEND_ERROR VMDIR_LOG_ERROR(LDAP_DEBUG_BACKEND, "VmDirMDBDeleteEntry: failed: error=%d,DN=%s", dwError, VDIR_SAFE_STRING(pEntry->dn.lberbv.bv_val)); goto cleanup; }
/* MdbCheckRefIntegrity: Checks for the attributes that have referential integrity constraint set, that the DN attribute * values refer to existing objects. * * Returns: BE error codes. * */ DWORD VmDirMDBCheckRefIntegrity( PVDIR_BACKEND_CTX pBECtx, PVDIR_ENTRY pEntry) { DWORD dwError = 0; VDIR_ATTRIBUTE * attr = NULL; assert( pBECtx && pBECtx->pBEPrivate && pEntry ); for (attr = pEntry->attrs; attr; attr = attr->next) { // SJ-TBD: Instead of checking referential integrity for hard coded attributes, we should have a // proprietary flag e.g. X-constraint in the attribute schema definition if (VmDirStringCompareA(attr->type.lberbv.bv_val, ATTR_MEMBER, FALSE) == 0) { unsigned int i = 0; ENTRYID eId = 0; for (; i < attr->numVals; i++) { // Lookup in the DN index. if ((dwError = VmDirNormalizeDN( &(attr->vals[i]), pEntry->pSchemaCtx)) != 0) { dwError = ERROR_BACKEND_OPERATIONS; BAIL_ON_VMDIR_ERROR( dwError ); } if ((dwError = VmDirMDBDNToEntryId( pBECtx, &(attr->vals[i]), &eId )) != 0) { dwError = MDBToBackendError(dwError, ERROR_BACKEND_ENTRY_NOTFOUND, ERROR_BACKEND_CONSTRAINT, pBECtx, VDIR_SAFE_STRING(attr->vals[i].lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } } } } cleanup: return dwError; error: // TODO set pBECtx->pszBEErrorMsg VMDIR_LOG_ERROR( LDAP_DEBUG_BACKEND, "BE DN (%s) reference check, error (%u)(%s)", pEntry->dn.lberbv_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; }
/* MdbDeleteEntry: Deletes an entry in the MDB DBs. * * Returns: BE error codes. * */ DWORD VmDirMDBDeleteEntry( PVDIR_BACKEND_CTX pBECtx, PVDIR_MODIFICATION pMods, PVDIR_ENTRY pEntry ) { DWORD dwError = 0; assert( pBECtx && pBECtx->pBEPrivate && pEntry ); // Delete child from the parentId index dwError = MDBDeleteParentIdIndex( pBECtx, &(pEntry->pdn), pEntry->eId ); BAIL_ON_VMDIR_ERROR(dwError); dwError = VmDirMDBModifyEntry( pBECtx, pMods, pEntry); BAIL_ON_VMDIR_ERROR( dwError ); if ((dwError = MDBCreateParentIdIndex( pBECtx, &(gVmdirServerGlobals.delObjsContainerDN), pEntry->eId )) != 0) { dwError = MDBToBackendError(dwError, ERROR_BACKEND_ENTRY_NOTFOUND, ERROR_BACKEND_PARENT_NOTFOUND, pBECtx, "CreateParentIdIndex"); BAIL_ON_VMDIR_ERROR( dwError ); } cleanup: return dwError; error: VMDIR_LOG_ERROR( LDAP_DEBUG_BACKEND, "BEDeleteEntry DN (%s) failed, (%u)(%s)", VDIR_SAFE_STRING( pEntry->dn.bvnorm_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; }
/* MdbModifyEntry: Updates an entry in the MDB DBs. * * Returns: BE error codes. * */ DWORD VmDirMDBModifyEntry( PVDIR_BACKEND_CTX pBECtx, VDIR_MODIFICATION* pMods, PVDIR_ENTRY pEntry ) { DWORD dwError = 0; VDIR_BERVALUE newEncodedEntry = VDIR_BERVALUE_INIT; VDIR_MODIFICATION * mod = NULL; PVDIR_DB_TXN pTxn = NULL; assert( pBECtx && pBECtx->pBEPrivate && pMods && pEntry ); pTxn = (PVDIR_DB_TXN)pBECtx->pBEPrivate; dwError = VmDirEncodeEntry( pEntry, &newEncodedEntry ); BAIL_ON_VMDIR_ERROR(dwError); VMDIR_SAFE_FREE_MEMORY( pEntry->encodedEntry ); // entry takes over the responsibility to free newEncodedEntry.lberbv.bv_val pEntry->encodedEntry = (unsigned char *)newEncodedEntry.lberbv.bv_val; // Create/Delete appropriate indices for indexed attributes. for (mod = pMods; mod != NULL; mod = mod->next) { if (mod->ignore) { continue; } switch (mod->operation) { case MOD_OP_ADD: if ((dwError = MdbUpdateIndicesForAttr( pTxn, &(mod->attr.type), mod->attr.vals, mod->attr.numVals, pEntry->eId, BE_INDEX_OP_TYPE_CREATE)) != 0) { dwError = MDBToBackendError( dwError, MDB_KEYEXIST, ERROR_BACKEND_CONSTRAINT, pBECtx, VDIR_SAFE_STRING(mod->attr.type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } break; case MOD_OP_DELETE: if ((dwError = MdbUpdateIndicesForAttr( pTxn, &(mod->attr.type), mod->attr.vals, mod->attr.numVals, pEntry->eId, BE_INDEX_OP_TYPE_DELETE )) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, VDIR_SAFE_STRING(mod->attr.type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } break; case MOD_OP_REPLACE: default: assert( FALSE ); } if ((dwError = MdbUpdateAttrMetaData( pTxn, &(mod->attr), pEntry->eId, BE_INDEX_OP_TYPE_UPDATE )) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, VDIR_SAFE_STRING(mod->attr.type.lberbv.bv_val)); BAIL_ON_VMDIR_ERROR( dwError ); } } // Update Entry DB. if ((dwError = MdbCreateEIDIndex(pTxn, pEntry->eId, &newEncodedEntry, FALSE /* update current eId key */)) != 0) { dwError = MDBToBackendError(dwError, 0, ERROR_BACKEND_ERROR, pBECtx, "CreateEIDIndex"); BAIL_ON_VMDIR_ERROR( dwError ); } cleanup: return dwError; error: VMDIR_SET_BACKEND_ERROR(dwError); // if dwError no in BE space, set to ERROR_BACKEND_ERROR VMDIR_LOG_ERROR(LDAP_DEBUG_BACKEND, "ModifyEntry failed: error=%d,DN=%s", dwError, VDIR_SAFE_STRING(pEntry->dn.lberbv.bv_val)); 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; }
// 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; }