diff options
Diffstat (limited to 'servers/slapd/back-mdb')
27 files changed, 15345 insertions, 0 deletions
diff --git a/servers/slapd/back-mdb/Makefile.in b/servers/slapd/back-mdb/Makefile.in new file mode 100644 index 0000000..ad38048 --- /dev/null +++ b/servers/slapd/back-mdb/Makefile.in @@ -0,0 +1,62 @@ +# Makefile.in for back-mdb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2011-2022 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c tools.c config.c \ + add.c bind.c compare.c delete.c modify.c modrdn.c search.c \ + extended.c operational.c \ + attr.c index.c key.c filterindex.c \ + dn2entry.c dn2id.c id2entry.c idl.c \ + nextid.c monitor.c + +OBJS = init.lo tools.lo config.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \ + extended.lo operational.lo \ + attr.lo index.lo key.lo filterindex.lo \ + dn2entry.lo dn2id.lo id2entry.lo idl.lo \ + nextid.lo monitor.lo mdb.lo midl.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries +MDB_SUBDIR = $(srcdir)/$(LDAP_LIBDIR)/liblmdb + +BUILD_OPT = "--enable-mdb" +BUILD_MOD = @BUILD_MDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_MDB@_DEFS) +MOD_LIBS = $(MDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_mdb + +XINCPATH = -I.. -I$(srcdir)/.. -I$(MDB_SUBDIR) +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + +mdb.lo: $(MDB_SUBDIR)/mdb.c + $(LTCOMPILE_MOD) $(MDB_SUBDIR)/mdb.c + +midl.lo: $(MDB_SUBDIR)/midl.c + $(LTCOMPILE_MOD) $(MDB_SUBDIR)/midl.c + +veryclean-local-lib: FORCE + $(RM) $(XXHEADERS) $(XXSRCS) .links diff --git a/servers/slapd/back-mdb/add.c b/servers/slapd/back-mdb/add.c new file mode 100644 index 0000000..f1632e2 --- /dev/null +++ b/servers/slapd/back-mdb/add.c @@ -0,0 +1,419 @@ +/* add.c - ldap mdb back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_add(Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct berval pdn; + Entry *p = NULL, *oe = op->ora_e; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + MDB_txn *txn = NULL; + MDB_cursor *mc = NULL; + MDB_cursor *mcd; + ID eid, pid = 0; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + int subentry; + int numads = mdb->mi_numads; + + int success; + + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_add) ": %s\n", + op->ora_e->e_name.bv_val ); + + ctrls[num_ctrls] = 0; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->ora_e, NULL, + get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": entry failed schema check: " + "%s (%d)\n", rs->sr_text, rs->sr_err ); + goto return_results; + } + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": txn_begin failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* add opattrs to shadow as well, only missing attrs will actually + * be added; helps compatibility with older OL versions */ + rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + subentry = is_entry_subentry( op->ora_e ); + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) { + pdn = slap_empty_bv; + } else { + dnParent( &op->ora_e->e_nname, &pdn ); + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mcd ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n", + rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or parent */ + rs->sr_err = mdb_dn2entry( op, txn, mcd, &op->ora_e->e_nname, &p, NULL, 1 ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + mdb_entry_return( op, p ); + p = NULL; + goto return_results; + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( !p ) + p = (Entry *)&slap_entry_root; + + if ( !bvmatch( &pdn, &p->e_nname ) ) { + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + if ( p != (Entry *)&slap_entry_root && is_entry_referral( p )) { + BerVarray ref = get_entry_referrals( op, p ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + if ( p != (Entry *)&slap_entry_root ) + mdb_entry_return( op, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent " + "does not exist\n" ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + if ( p != (Entry *)&slap_entry_root ) + mdb_entry_return( op, p ); + p = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to parent\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results;; + } + + if ( p != (Entry *)&slap_entry_root ) { + if ( is_entry_subentry( p ) ) { + mdb_entry_return( op, p ); + p = NULL; + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is subentry\n" ); + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "parent is a subentry"; + goto return_results;; + } + + if ( is_entry_alias( p ) ) { + mdb_entry_return( op, p ); + p = NULL; + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is alias\n" ); + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "parent is an alias"; + goto return_results;; + } + + if ( is_entry_referral( p ) ) { + BerVarray ref = get_entry_referrals( op, p ); + /* parent is a referral, don't allow add */ + rs->sr_matched = ber_strdup_x( p->e_name.bv_val, + op->o_tmpmemctx ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + mdb_entry_return( op, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": parent is referral\n" ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + } + + if ( subentry ) { + /* FIXME: */ + /* parent must be an administrative point of the required kind */ + } + + /* free parent */ + if ( p != (Entry *)&slap_entry_root ) { + pid = p->e_id; + if ( p->e_nname.bv_len ) { + struct berval ppdn; + + /* ITS#5326: use parent's DN if differs from provided one */ + dnParent( &op->ora_e->e_name, &ppdn ); + if ( !dn_match( &p->e_name, &ppdn ) ) { + struct berval rdn; + struct berval newdn; + + dnRdn( &op->ora_e->e_name, &rdn ); + + build_new_dn( &newdn, &p->e_name, &rdn, NULL ); + if ( op->ora_e->e_name.bv_val != op->o_req_dn.bv_val ) + ber_memfree( op->ora_e->e_name.bv_val ); + op->ora_e->e_name = newdn; + + /* FIXME: should check whether + * dnNormalize(newdn) == e->e_nname ... */ + } + } + + mdb_entry_return( op, p ); + } + p = NULL; + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to entry\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results;; + } + + /* + * Check ACL for attribute write access + */ + if (!acl_check_modlist(op, oe, op->ora_modlist)) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": no write access to attribute\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to attribute"; + goto return_results;; + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_id2entry, &mc ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n", + rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + rs->sr_err = mdb_next_id( op->o_bd, mc, &eid ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": next_id failed (%d)\n", + rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + op->ora_e->e_id = eid; + + /* dn2id index */ + rs->sr_err = mdb_dn2id_add( op, mcd, mcd, pid, 1, 1, op->ora_e ); + mdb_cursor_close( mcd ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": dn2id_add failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + + switch( rs->sr_err ) { + case MDB_KEYEXIST: + rs->sr_err = LDAP_ALREADY_EXISTS; + break; + default: + rs->sr_err = LDAP_OTHER; + } + goto return_results; + } + + /* attribute indexes */ + rs->sr_err = mdb_index_entry_add( op, txn, op->ora_e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": index_entry_add failed\n" ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "index generation failed"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = mdb_id2entry_add( op, txn, mc, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": id2entry_add failed\n" ); + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) { + rs->sr_text = "entry is too big"; + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry store failed"; + } + goto return_results; + } + + /* post-read */ + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, op->ora_e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_add) ": post-read " + "failed!\n" ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if ( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if ( op->o_noop ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } + + rs->sr_err = mdb_txn_commit( txn ); + txn = NULL; + if ( rs->sr_err != 0 ) { + mdb->mi_numads = numads; + rs->sr_text = "txn_commit failed"; + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_add) ": %s : %s (%d)\n", + rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_add) ": added%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + op->ora_e->e_id, op->ora_e->e_dn ); + + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + success = rs->sr_err; + send_ldap_result( op, rs ); + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( success == LDAP_SUCCESS ) { +#if 0 + if ( mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + } + + slap_graduate_commit_csn( op ); + + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/attr.c b/servers/slapd/back-mdb/attr.c new file mode 100644 index 0000000..9567fb5 --- /dev/null +++ b/servers/slapd/back-mdb/attr.c @@ -0,0 +1,824 @@ +/* attr.c - backend routines for dealing with attributes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "back-mdb.h" +#include "slap-config.h" +#include "lutil.h" + +/* Find the ad, return -1 if not found, + * set point for insertion if ins is non-NULL + */ +int +mdb_attr_slot( struct mdb_info *mdb, AttributeDescription *ad, int *ins ) +{ + unsigned base = 0, cursor = 0; + unsigned n = mdb->mi_nattrs; + int val = 0; + + while ( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot; + + val = SLAP_PTRCMP( ad, mdb->mi_attrs[cursor]->ai_desc ); + if ( val < 0 ) { + n = pivot; + } else if ( val > 0 ) { + base = cursor + 1; + n -= pivot + 1; + } else { + return cursor; + } + } + if ( ins ) { + if ( val > 0 ) + ++cursor; + *ins = cursor; + } + return -1; +} + +static int +ainfo_insert( struct mdb_info *mdb, AttrInfo *a ) +{ + int x; + int i = mdb_attr_slot( mdb, a->ai_desc, &x ); + + /* Is it a dup? */ + if ( i >= 0 ) + return -1; + + mdb->mi_attrs = ch_realloc( mdb->mi_attrs, ( mdb->mi_nattrs+1 ) * + sizeof( AttrInfo * )); + if ( x < mdb->mi_nattrs ) + AC_MEMCPY( &mdb->mi_attrs[x+1], &mdb->mi_attrs[x], + ( mdb->mi_nattrs - x ) * sizeof( AttrInfo *)); + mdb->mi_attrs[x] = a; + mdb->mi_nattrs++; + return 0; +} + +AttrInfo * +mdb_attr_mask( + struct mdb_info *mdb, + AttributeDescription *desc ) +{ + int i = mdb_attr_slot( mdb, desc, NULL ); + return i < 0 ? NULL : mdb->mi_attrs[i]; +} + +/* Open all un-opened index DB handles */ +int +mdb_attr_dbs_open( + BackendDB *be, MDB_txn *tx0, ConfigReply *cr ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + MDB_txn *txn; + MDB_dbi *dbis = NULL; + int i, flags; + int rc; + + txn = tx0; + if ( txn == NULL ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "txn_begin failed: %s (%d).", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg ); + return rc; + } + dbis = ch_calloc( 1, mdb->mi_nattrs * sizeof(MDB_dbi) ); + } else { + rc = 0; + } + + flags = MDB_DUPSORT|MDB_DUPFIXED|MDB_INTEGERDUP; + if ( !(slapMode & SLAP_TOOL_READONLY) ) + flags |= MDB_CREATE; + + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[i]->ai_dbi ) /* already open */ + continue; + if ( !( mdb->mi_attrs[i]->ai_indexmask || mdb->mi_attrs[i]->ai_newmask )) /* not an index record */ + continue; + rc = mdb_dbi_open( txn, mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + flags, &mdb->mi_attrs[i]->ai_dbi ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "mdb_dbi_open(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg ); + break; + } + /* Remember newly opened DBI handles */ + if ( dbis ) + dbis[i] = mdb->mi_attrs[i]->ai_dbi; + } + + /* Only commit if this is our txn */ + if ( tx0 == NULL ) { + if ( !rc ) { + rc = mdb_txn_commit( txn ); + if ( rc ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "txn_commit failed: %s (%d).", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_attr_dbs) ": %s\n", + cr->msg ); + } + } else { + mdb_txn_abort( txn ); + } + /* Something failed, forget anything we just opened */ + if ( rc ) { + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( dbis[i] ) { + mdb->mi_attrs[i]->ai_dbi = 0; + mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING; + } + } + mdb_attr_flush( mdb ); + } + ch_free( dbis ); + } + + return rc; +} + +void +mdb_attr_dbs_close( + struct mdb_info *mdb +) +{ + int i; + for ( i=0; i<mdb->mi_nattrs; i++ ) + if ( mdb->mi_attrs[i]->ai_dbi ) { + mdb_dbi_close( mdb->mi_dbenv, mdb->mi_attrs[i]->ai_dbi ); + mdb->mi_attrs[i]->ai_dbi = 0; + } +} + +int +mdb_attr_index_config( + struct mdb_info *mdb, + const char *fname, + int lineno, + int argc, + char **argv, + struct config_reply_s *c_reply) +{ + int rc = 0; + int i; + slap_mask_t mask; + char **attrs; + char **indexes = NULL; + + attrs = ldap_str2charray( argv[0], "," ); + + if( attrs == NULL ) { + fprintf( stderr, "%s: line %d: " + "no attributes specified: %s\n", + fname, lineno, argv[0] ); + return LDAP_PARAM_ERROR; + } + + if ( argc > 1 ) { + indexes = ldap_str2charray( argv[1], "," ); + + if( indexes == NULL ) { + fprintf( stderr, "%s: line %d: " + "no indexes specified: %s\n", + fname, lineno, argv[1] ); + rc = LDAP_PARAM_ERROR; + goto done; + } + } + + if( indexes == NULL ) { + mask = mdb->mi_defaultmask; + + } else { + mask = 0; + + for ( i = 0; indexes[i] != NULL; i++ ) { + slap_mask_t index; + rc = slap_str2index( indexes[i], &index ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index type \"%s\" undefined", indexes[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + mask |= index; + } + } + + if( !mask ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "no indexes selected" ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_PARAM_ERROR; + goto done; + } + + for ( i = 0; attrs[i] != NULL; i++ ) { + AttrInfo *a; + AttributeDescription *ad; + const char *text; +#ifdef LDAP_COMP_MATCH + ComponentReference* cr = NULL; + AttrInfo *a_cr = NULL; +#endif + + if( strcasecmp( attrs[i], "default" ) == 0 ) { + mdb->mi_defaultmask |= mask; + continue; + } + +#ifdef LDAP_COMP_MATCH + if ( is_component_reference( attrs[i] ) ) { + rc = extract_component_reference( attrs[i], &cr ); + if ( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index component reference\"%s\" undefined", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + goto done; + } + cr->cr_indexmask = mask; + /* + * After extracting a component reference + * only the name of a attribute will be remaining + */ + } else { + cr = NULL; + } +#endif + ad = NULL; + rc = slap_str2ad( attrs[i], &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index attribute \"%s\" undefined", + attrs[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } +fail: +#ifdef LDAP_COMP_MATCH + ch_free( cr ); +#endif + goto done; + } + + if( ad == slap_schema.si_ad_entryDN || slap_ad_is_binary( ad ) ) { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_UNWILLING_TO_PERFORM; + goto fail; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) && !( + ad->ad_type->sat_approx + && ad->ad_type->sat_approx->smr_indexer + && ad->ad_type->sat_approx->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "approx index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto fail; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) && !( + ad->ad_type->sat_equality + && ad->ad_type->sat_equality->smr_indexer + && ad->ad_type->sat_equality->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "equality index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto fail; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) && !( + ad->ad_type->sat_substr + && ad->ad_type->sat_substr->smr_indexer + && ad->ad_type->sat_substr->smr_filter ) ) + { + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "substr index of attribute \"%s\" disallowed", attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + rc = LDAP_INAPPROPRIATE_MATCHING; + goto fail; + } + + Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n", + ad->ad_cname.bv_val, mask ); + + a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) ); + +#ifdef LDAP_COMP_MATCH + a->ai_cr = NULL; +#endif + a->ai_cursor = NULL; + a->ai_root = NULL; + a->ai_desc = ad; + a->ai_dbi = 0; + a->ai_multi_hi = UINT_MAX; + a->ai_multi_lo = UINT_MAX; + + if ( mdb->mi_flags & MDB_IS_OPEN ) { + a->ai_indexmask = 0; + a->ai_newmask = mask; + } else { + a->ai_indexmask = mask; + a->ai_newmask = 0; + } + +#ifdef LDAP_COMP_MATCH + if ( cr ) { + a_cr = mdb_attr_mask( mdb, ad ); + if ( a_cr ) { + /* + * AttrInfo is already in AVL + * just add the extracted component reference + * in the AttrInfo + */ + ch_free( a ); + rc = insert_component_reference( cr, &a_cr->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + goto fail; + } + continue; + } else { + rc = insert_component_reference( cr, &a->ai_cr ); + if ( rc != LDAP_SUCCESS) { + fprintf( stderr, " error during inserting component reference in %s ", attrs[i]); + rc = LDAP_PARAM_ERROR; + ch_free( a ); + goto fail; + } + } + } +#endif + rc = ainfo_insert( mdb, a ); + if( rc ) { + AttrInfo *b = mdb_attr_mask( mdb, ad ); + /* If this is just a multival record, reuse it for index info */ + if ( !( b->ai_indexmask || b->ai_newmask ) && b->ai_multi_lo < UINT_MAX ) { + b->ai_indexmask = a->ai_indexmask; + b->ai_newmask = a->ai_newmask; + ch_free( a ); + rc = 0; + continue; + } + if ( mdb->mi_flags & MDB_IS_OPEN ) { + /* If there is already an index defined for this attribute + * it must be replaced. Otherwise we end up with multiple + * olcIndex values for the same attribute */ + if ( b->ai_indexmask & MDB_INDEX_DELETING ) { + /* If we were editing this attr, reset it */ + b->ai_indexmask &= ~MDB_INDEX_DELETING; + /* If this is leftover from a previous add, commit it */ + if ( b->ai_newmask ) + b->ai_indexmask = b->ai_newmask; + b->ai_newmask = a->ai_newmask; + ch_free( a ); + rc = 0; + continue; + } + } + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "duplicate index definition for attr \"%s\"", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + + rc = LDAP_PARAM_ERROR; + goto done; + } + } + +done: + ldap_charray_free( attrs ); + if ( indexes != NULL ) ldap_charray_free( indexes ); + + return rc; +} + +static int +mdb_attr_index_unparser( void *v1, void *v2 ) +{ + AttrInfo *ai = v1; + BerVarray *bva = v2; + struct berval bv; + char *ptr; + + slap_index2bvlen( ai->ai_indexmask, &bv ); + if ( bv.bv_len ) { + bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1; + ptr = ch_malloc( bv.bv_len+1 ); + bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val ); + *bv.bv_val++ = ' '; + slap_index2bv( ai->ai_indexmask, &bv ); + bv.bv_val = ptr; + ber_bvarray_add( bva, &bv ); + } + return 0; +} + +static AttributeDescription addef = { NULL, NULL, BER_BVC("default") }; +static AttrInfo aidef = { &addef }; + +void +mdb_attr_index_unparse( struct mdb_info *mdb, BerVarray *bva ) +{ + int i; + + if ( mdb->mi_defaultmask ) { + aidef.ai_indexmask = mdb->mi_defaultmask; + mdb_attr_index_unparser( &aidef, bva ); + } + for ( i=0; i<mdb->mi_nattrs; i++ ) + if ( mdb->mi_attrs[i]->ai_indexmask ) + mdb_attr_index_unparser( mdb->mi_attrs[i], bva ); +} + +int +mdb_attr_multi_config( + struct mdb_info *mdb, + const char *fname, + int lineno, + int argc, + char **argv, + struct config_reply_s *c_reply) +{ + int rc = 0; + int i; + unsigned hi,lo; + char **attrs, *next, *s; + + attrs = ldap_str2charray( argv[0], "," ); + + if( attrs == NULL ) { + fprintf( stderr, "%s: line %d: " + "no attributes specified: %s\n", + fname, lineno, argv[0] ); + return LDAP_PARAM_ERROR; + } + + hi = strtoul( argv[1], &next, 10 ); + if ( next == argv[1] || next[0] != ',' ) + goto badval; + s = next+1; + lo = strtoul( s, &next, 10 ); + if ( next == s || next[0] != '\0' ) + goto badval; + + if ( lo > hi ) { +badval: + snprintf(c_reply->msg, sizeof(c_reply->msg), + "invalid hi/lo thresholds" ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + return LDAP_PARAM_ERROR; + } + + for ( i = 0; attrs[i] != NULL; i++ ) { + AttrInfo *a; + AttributeDescription *ad; + const char *text; + + if( strcasecmp( attrs[i], "default" ) == 0 ) { + mdb->mi_multi_hi = hi; + mdb->mi_multi_lo = lo; + continue; + } + + ad = NULL; + rc = slap_str2ad( attrs[i], &ad, &text ); + + if( rc != LDAP_SUCCESS ) { + if ( c_reply ) + { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "multival attribute \"%s\" undefined", + attrs[i] ); + + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } +fail: + goto done; + } + + a = (AttrInfo *) ch_calloc( 1, sizeof(AttrInfo) ); + + a->ai_desc = ad; + a->ai_multi_hi = hi; + a->ai_multi_lo = lo; + + rc = ainfo_insert( mdb, a ); + if( rc ) { + AttrInfo *b = mdb_attr_mask( mdb, ad ); + /* If this is just an index record, reuse it for multival info */ + if ( b->ai_multi_lo == UINT_MAX ) { + b->ai_multi_hi = a->ai_multi_hi; + b->ai_multi_lo = a->ai_multi_lo; + ch_free( a ); + rc = 0; + continue; + } + if (c_reply) { + snprintf(c_reply->msg, sizeof(c_reply->msg), + "duplicate multival definition for attr \"%s\"", + attrs[i] ); + fprintf( stderr, "%s: line %d: %s\n", + fname, lineno, c_reply->msg ); + } + + rc = LDAP_PARAM_ERROR; + goto done; + } + } + +done: + ldap_charray_free( attrs ); + + return rc; +} + +static int +mdb_attr_multi_unparser( void *v1, void *v2 ) +{ + AttrInfo *ai = v1; + BerVarray *bva = v2; + struct berval bv; + char digbuf[sizeof("4294967296,4294967296")]; + char *ptr; + + bv.bv_len = snprintf( digbuf, sizeof(digbuf), "%u,%u", + ai->ai_multi_hi, ai->ai_multi_lo ); + if ( bv.bv_len ) { + bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1; + ptr = ch_malloc( bv.bv_len+1 ); + bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val ); + *bv.bv_val++ = ' '; + strcpy(bv.bv_val, digbuf); + bv.bv_val = ptr; + ber_bvarray_add( bva, &bv ); + } + return 0; +} + +void +mdb_attr_multi_unparse( struct mdb_info *mdb, BerVarray *bva ) +{ + int i; + + if ( mdb->mi_multi_hi < UINT_MAX ) { + aidef.ai_multi_hi = mdb->mi_multi_hi; + aidef.ai_multi_lo = mdb->mi_multi_lo; + mdb_attr_multi_unparser( &aidef, bva ); + } + for ( i=0; i<mdb->mi_nattrs; i++ ) + if ( mdb->mi_attrs[i]->ai_multi_hi < UINT_MAX ) + mdb_attr_multi_unparser( mdb->mi_attrs[i], bva ); +} + +void +mdb_attr_multi_thresh( struct mdb_info *mdb, AttributeDescription *ad, unsigned *hi, unsigned *lo ) +{ + AttrInfo *ai = mdb_attr_mask( mdb, ad ); + if ( ai && ai->ai_multi_hi < UINT_MAX ) + { + if ( hi ) + *hi = ai->ai_multi_hi; + if ( lo ) + *lo = ai->ai_multi_lo; + } else + { + if ( hi ) + *hi = mdb->mi_multi_hi; + if ( lo ) + *lo = mdb->mi_multi_lo; + } +} + +void +mdb_attr_info_free( AttrInfo *ai ) +{ +#ifdef LDAP_COMP_MATCH + free( ai->ai_cr ); +#endif + free( ai ); +} + +void +mdb_attr_index_destroy( struct mdb_info *mdb ) +{ + int i; + + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb_attr_info_free( mdb->mi_attrs[i] ); + + free( mdb->mi_attrs ); +} + +void mdb_attr_index_free( struct mdb_info *mdb, AttributeDescription *ad ) +{ + int i; + + i = mdb_attr_slot( mdb, ad, NULL ); + if ( i >= 0 ) { + mdb_attr_info_free( mdb->mi_attrs[i] ); + mdb->mi_nattrs--; + for (; i<mdb->mi_nattrs; i++) + mdb->mi_attrs[i] = mdb->mi_attrs[i+1]; + } +} + +void mdb_attr_flush( struct mdb_info *mdb ) +{ + int i; + + for ( i=0; i<mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[i]->ai_indexmask & MDB_INDEX_DELETING ) { + /* if this is also a multival rec, just clear index */ + if ( mdb->mi_attrs[i]->ai_multi_lo < UINT_MAX ) { + mdb->mi_attrs[i]->ai_indexmask = 0; + mdb->mi_attrs[i]->ai_newmask = 0; + } else { + int j; + mdb_attr_info_free( mdb->mi_attrs[i] ); + mdb->mi_nattrs--; + for (j=i; j<mdb->mi_nattrs; j++) + mdb->mi_attrs[j] = mdb->mi_attrs[j+1]; + i--; + } + } + } +} + +int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn ) +{ + int i, rc; + MDB_cursor *mc; + MDB_val key, data; + struct berval bdata; + const char *text; + AttributeDescription *ad; + + rc = mdb_cursor_open( txn, mdb->mi_ad2id, &mc ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "mdb_ad_read: cursor_open failed %s(%d)\n", + mdb_strerror(rc), rc ); + return rc; + } + + /* our array is 1-based, an index of 0 means no data */ + i = mdb->mi_numads+1; + key.mv_size = sizeof(int); + key.mv_data = &i; + + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + + while ( rc == MDB_SUCCESS ) { + bdata.bv_len = data.mv_size; + bdata.bv_val = data.mv_data; + ad = NULL; + rc = slap_bv2ad( &bdata, &ad, &text ); + if ( rc ) { + rc = slap_bv2undef_ad( &bdata, &mdb->mi_ads[i], &text, 0 ); + } else { + if ( ad->ad_index >= MDB_MAXADS ) { + Debug( LDAP_DEBUG_ANY, + "mdb_adb_read: too many AttributeDescriptions in use\n" ); + return LDAP_OTHER; + } + mdb->mi_adxs[ad->ad_index] = i; + mdb->mi_ads[i] = ad; + } + i++; + rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT ); + } + mdb->mi_numads = i-1; + +done: + if ( rc == MDB_NOTFOUND ) + rc = 0; + + mdb_cursor_close( mc ); + + return rc; +} + +int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad ) +{ + int i, rc; + MDB_val key, val; + + rc = mdb_ad_read( mdb, txn ); + if (rc) + return rc; + + if ( mdb->mi_adxs[ad->ad_index] ) + return 0; + + i = mdb->mi_numads+1; + key.mv_size = sizeof(int); + key.mv_data = &i; + val.mv_size = ad->ad_cname.bv_len; + val.mv_data = ad->ad_cname.bv_val; + + rc = mdb_put( txn, mdb->mi_ad2id, &key, &val, 0 ); + if ( rc == MDB_SUCCESS ) { + mdb->mi_adxs[ad->ad_index] = i; + mdb->mi_ads[i] = ad; + mdb->mi_numads = i; + } else { + Debug( LDAP_DEBUG_ANY, + "mdb_ad_get: mdb_put failed %s(%d)\n", + mdb_strerror(rc), rc ); + } + + return rc; +} + +void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads ) +{ + int i; + + for (i=mdb->mi_numads; i>prev_ads; i--) { + mdb->mi_adxs[mdb->mi_ads[i]->ad_index] = 0; + mdb->mi_ads[i] = NULL; + } + mdb->mi_numads = i; +} diff --git a/servers/slapd/back-mdb/back-mdb.h b/servers/slapd/back-mdb/back-mdb.h new file mode 100644 index 0000000..2b48aaa --- /dev/null +++ b/servers/slapd/back-mdb/back-mdb.h @@ -0,0 +1,207 @@ +/* back-mdb.h - mdb back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _BACK_MDB_H_ +#define _BACK_MDB_H_ + +#include <portable.h> +#include "slap.h" +#include "lmdb.h" + +LDAP_BEGIN_DECL + +#undef MDB_TOOL_IDL_CACHING /* currently no perf gain */ + +#define DN_BASE_PREFIX SLAP_INDEX_EQUALITY_PREFIX +#define DN_ONE_PREFIX '%' +#define DN_SUBTREE_PREFIX '@' + +#define MDB_AD2ID 0 +#define MDB_DN2ID 1 +#define MDB_ID2ENTRY 2 +#define MDB_ID2VAL 3 +#define MDB_NDB 4 + +/* The default search IDL stack cache depth */ +#define DEFAULT_SEARCH_STACK_DEPTH 16 + +/* The minimum we can function with */ +#define MINIMUM_SEARCH_STACK_DEPTH 8 + +#define MDB_INDICES 128 + +#define MDB_MAXADS 65536 + +/* Default to 10MB max */ +#define DEFAULT_MAPSIZE (10*1048576) + +/* Most users will never see this */ +#define DEFAULT_RTXN_SIZE 10000 + +#ifdef LDAP_DEVEL +#define MDB_MONITOR_IDX +#endif + +typedef struct mdb_monitor_t { + void *mdm_cb; + struct berval mdm_ndn; +} mdb_monitor_t; + +/* From ldap_rq.h */ +struct re_s; + +struct mdb_info { + MDB_env *mi_dbenv; + + /* DB_ENV parameters */ + char *mi_dbenv_home; + unsigned mi_dbenv_flags; + int mi_dbenv_mode; + + size_t mi_mapsize; + ID mi_nextid; + size_t mi_maxentrysize; + + slap_mask_t mi_defaultmask; + int mi_nattrs; + struct mdb_attrinfo **mi_attrs; + void *mi_search_stack; + int mi_search_stack_depth; + int mi_readers; + + unsigned mi_rtxn_size; + int mi_txn_cp; + unsigned mi_txn_cp_min; + unsigned mi_txn_cp_kbyte; + + struct re_s *mi_txn_cp_task; + struct re_s *mi_index_task; + + mdb_monitor_t mi_monitor; + +#ifdef MDB_MONITOR_IDX + ldap_pvt_thread_mutex_t mi_idx_mutex; + Avlnode *mi_idx; +#endif /* MDB_MONITOR_IDX */ + + int mi_flags; +#define MDB_IS_OPEN 0x01 +#define MDB_OPEN_INDEX 0x02 +#define MDB_DEL_INDEX 0x08 +#define MDB_RE_OPEN 0x10 +#define MDB_NEED_UPGRADE 0x20 + + int mi_numads; + + unsigned mi_multi_hi; + /* more than this many values in an attr goes + * into a separate DB */ + unsigned mi_multi_lo; + /* less than this many values in an attr goes + * back into main blob */ + + MDB_dbi mi_dbis[MDB_NDB]; + AttributeDescription *mi_ads[MDB_MAXADS]; + int mi_adxs[MDB_MAXADS]; +}; + +#define mi_id2entry mi_dbis[MDB_ID2ENTRY] +#define mi_dn2id mi_dbis[MDB_DN2ID] +#define mi_ad2id mi_dbis[MDB_AD2ID] +#define mi_id2val mi_dbis[MDB_ID2VAL] + +typedef struct mdb_op_info { + OpExtra moi_oe; + MDB_txn* moi_txn; + int moi_ref; + char moi_flag; +} mdb_op_info; +#define MOI_READER 0x01 +#define MOI_FREEIT 0x02 +#define MOI_KEEPER 0x04 + +LDAP_END_DECL + +/* for the cache of attribute information (which are indexed, etc.) */ +typedef struct mdb_attrinfo { + AttributeDescription *ai_desc; /* attribute description cn;lang-en */ + slap_mask_t ai_indexmask; /* how the attr is indexed */ + slap_mask_t ai_newmask; /* new settings to replace old mask */ +#ifdef LDAP_COMP_MATCH + ComponentReference* ai_cr; /*component indexing*/ +#endif + TAvlnode *ai_root; /* for tools */ + MDB_cursor *ai_cursor; /* for tools */ + int ai_idx; /* position in AI array */ + MDB_dbi ai_dbi; + unsigned ai_multi_hi; + unsigned ai_multi_lo; +} AttrInfo; + +/* tool threaded indexer state */ +typedef struct mdb_attrixinfo { + OpExtra ai_oe; + void *ai_flist; + void *ai_clist; + AttrInfo *ai_ai; +} AttrIxInfo; + +/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */ +#define MDB_INDEX_DELETING 0x8000U /* index is being modified */ +#define MDB_INDEX_UPDATE_OP 0x03 /* performing an index update */ + +/* For slapindex to record which attrs in an entry belong to which + * index database + */ +typedef struct AttrList { + struct AttrList *next; + Attribute *attr; +} AttrList; + +#ifndef CACHELINE +#define CACHELINE 64 +#endif + +#if defined(__i386) || defined(__x86_64) +#define MISALIGNED_OK 1 +#else +#define ALIGNER (sizeof(size_t)-1) +#endif + +typedef struct IndexRbody { + AttrInfo *ai; + AttrList *attrs; + void *tptr; + int i; +} IndexRbody; + +typedef struct IndexRec { + union { + IndexRbody irb; +#define ir_ai iru.irb.ai +#define ir_attrs iru.irb.attrs +#define ir_tptr iru.irb.tptr +#define ir_i iru.irb.i + /* cache line alignment */ + char pad[(sizeof(IndexRbody)+CACHELINE-1) & (!CACHELINE-1)]; + } iru; +} IndexRec; + +#define MAXRDNS SLAP_LDAPDN_MAXLEN/4 + +#include "proto-mdb.h" + +#endif /* _BACK_MDB_H_ */ diff --git a/servers/slapd/back-mdb/bind.c b/servers/slapd/back-mdb/bind.c new file mode 100644 index 0000000..6df3f2d --- /dev/null +++ b/servers/slapd/back-mdb/bind.c @@ -0,0 +1,156 @@ +/* bind.c - mdb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "back-mdb.h" + +int +mdb_bind( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e; + Attribute *a; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(mdb_bind) ": dn: %s\n", + op->o_req_dn.bv_val ); + + /* allow noauth binds */ + switch ( be_rootdn_bind( op, NULL ) ) { + case LDAP_SUCCESS: + /* frontend will send result */ + return rs->sr_err = LDAP_SUCCESS; + + default: + /* give the database a chance */ + /* NOTE: this behavior departs from that of other backends, + * since the others, in case of password checking failure + * do not give the database a chance. If an entry with + * rootdn's name does not exist in the database the result + * will be the same. See ITS#4962 for discussion. */ + break; + } + + rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rs->sr_err) { + case 0: + break; + default: + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + return rs->sr_err; + } + + rtxn = moi->moi_txn; + + /* get entry with reader lock */ + rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 0 ); + + switch(rs->sr_err) { + case MDB_NOTFOUND: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap_server_busy"; + goto done; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + ber_dupbv( &op->oq_bind.rb_edn, &e->e_name ); + + /* check for deleted */ + if ( is_entry_subentry( e ) ) { + /* entry is an subentry, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is subentry\n" ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_alias( e ) ) { + /* entry is an alias, don't allow bind */ + Debug( LDAP_DEBUG_TRACE, "entry is alias\n" ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( is_entry_referral( e ) ) { + Debug( LDAP_DEBUG_TRACE, "entry is referral\n" ); + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + switch ( op->oq_bind.rb_method ) { + case LDAP_AUTH_SIMPLE: + a = attr_find( e->e_attrs, password ); + if ( a == NULL ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + if ( slap_passwd_check( op, e, a, &op->oq_bind.rb_cred, + &rs->sr_text ) != 0 ) + { + /* failure; stop front end from sending result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + rs->sr_err = 0; + break; + + default: + assert( 0 ); /* should not be reachable */ + rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED; + rs->sr_text = "authentication method not supported"; + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + /* free entry and reader lock */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + } + /* front end will send result on success (rs->sr_err==0) */ + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/compare.c b/servers/slapd/back-mdb/compare.c new file mode 100644 index 0000000..10a6ccd --- /dev/null +++ b/servers/slapd/back-mdb/compare.c @@ -0,0 +1,142 @@ +/* compare.c - mdb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_compare( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int manageDSAit = get_manageDSAit( op ); + + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + + rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + rtxn = moi->moi_txn; + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 1 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( rs->sr_err == MDB_NOTFOUND ) { + if ( e != NULL ) { + /* return referral only if "disclose" is granted on the object */ + if ( ! access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + rs->sr_err = LDAP_REFERRAL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + rs->sr_err = rs->sr_ref ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + send_ldap_result( op, rs ); + goto done; + } + + if (!manageDSAit && is_entry_referral( e ) ) { + /* return referral only if "disclose" is granted on the object */ + if ( !access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + /* entry is a referral, don't allow compare */ + rs->sr_ref = get_entry_referrals( op, e ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n" ); + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + rs->sr_err = slap_compare_entry( op, e, op->orc_ava ); + +return_results: + send_ldap_result( op, rs ); + + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rs->sr_err = LDAP_SUCCESS; + break; + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + /* free entry */ + if ( e != NULL ) { + mdb_entry_return( op, e ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/config.c b/servers/slapd/back-mdb/config.c new file mode 100644 index 0000000..ce6d78a --- /dev/null +++ b/servers/slapd/back-mdb/config.c @@ -0,0 +1,828 @@ +/* config.c - mdb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/ctype.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-mdb.h" +#include "idl.h" + +#include "slap-config.h" + +#include "lutil.h" +#include "ldap_rq.h" + + +static ConfigDriver mdb_cf_gen; +static ConfigDriver mdb_bk_cfg; + +enum { + MDB_CHKPT = 1, + MDB_DIRECTORY, + MDB_DBNOSYNC, + MDB_ENVFLAGS, + MDB_INDEX, + MDB_MAXREADERS, + MDB_MAXSIZE, + MDB_MODE, + MDB_SSTACK, + MDB_MULTIVAL, + MDB_IDLEXP, +}; + +static ConfigTable mdbcfg[] = { + { "idlexp", "log", 2, 2, 0, ARG_UINT|ARG_MAGIC|MDB_IDLEXP, + mdb_bk_cfg, "( OLcfgBkAt:12.1 NAME 'olcBkMdbIdlExp' " + "DESC 'Power of 2 used to set IDL size' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|MDB_DIRECTORY, + mdb_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Directory for database content' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "checkpoint", "kbyte> <min", 3, 3, 0, ARG_MAGIC|MDB_CHKPT, + mdb_cf_gen, "( OLcfgDbAt:1.2 NAME 'olcDbCheckpoint' " + "DESC 'Database checkpoint interval in kbytes and minutes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "dbnosync", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|MDB_DBNOSYNC, + mdb_cf_gen, "( OLcfgDbAt:1.4 NAME 'olcDbNoSync' " + "DESC 'Disable synchronous database writes' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "envflags", "flags", 2, 0, 0, ARG_MAGIC|MDB_ENVFLAGS, + mdb_cf_gen, "( OLcfgDbAt:12.3 NAME 'olcDbEnvFlags' " + "DESC 'Database environment flags' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|MDB_INDEX, + mdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute index parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "maxentrysize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct mdb_info, mi_maxentrysize), + "( OLcfgDbAt:12.4 NAME 'olcDbMaxEntrySize' " + "DESC 'Maximum size of an entry in bytes' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "maxreaders", "num", 2, 2, 0, ARG_UINT|ARG_MAGIC|MDB_MAXREADERS, + mdb_cf_gen, "( OLcfgDbAt:12.1 NAME 'olcDbMaxReaders' " + "DESC 'Maximum number of threads that may access the DB concurrently' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "maxsize", "size", 2, 2, 0, ARG_ULONG|ARG_MAGIC|MDB_MAXSIZE, + mdb_cf_gen, "( OLcfgDbAt:12.2 NAME 'olcDbMaxSize' " + "DESC 'Maximum size of DB in bytes' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "mode", "mode", 2, 2, 0, ARG_MAGIC|MDB_MODE, + mdb_cf_gen, "( OLcfgDbAt:0.3 NAME 'olcDbMode' " + "DESC 'Unix permissions of database files' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "multival", "attr> <hi,lo", 3, 3, 0, ARG_MAGIC|MDB_MULTIVAL, + mdb_cf_gen, + "( OLcfgDbAt:12.6 NAME 'olcDbMultival' " + "DESC 'Hi/Lo thresholds for splitting multivalued attr out of main blob' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "rtxnsize", "entries", 2, 2, 0, ARG_UINT|ARG_OFFSET, + (void *)offsetof(struct mdb_info, mi_rtxn_size), + "( OLcfgDbAt:12.5 NAME 'olcDbRtxnSize' " + "DESC 'Number of entries to process in one read transaction' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, + { .v_uint = DEFAULT_RTXN_SIZE } }, + { "searchstack", "depth", 2, 2, 0, ARG_INT|ARG_MAGIC|MDB_SSTACK, + mdb_cf_gen, "( OLcfgDbAt:1.9 NAME 'olcDbSearchStack' " + "DESC 'Depth of search stack in IDLs' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs mdbocs[] = { + { + "( OLcfgBkOc:12.1 " + "NAME 'olcMdbBkConfig' " + "DESC 'MDB backend configuration' " + "SUP olcBackendConfig " + "MAY olcBkMdbIdlExp )", + Cft_Backend, mdbcfg }, + { + "( OLcfgDbOc:12.1 " + "NAME 'olcMdbConfig' " + "DESC 'MDB database configuration' " + "SUP olcDatabaseConfig " + "MUST olcDbDirectory " + "MAY ( olcDbCheckpoint $ olcDbEnvFlags $ " + "olcDbNoSync $ olcDbIndex $ olcDbMaxReaders $ olcDbMaxSize $ " + "olcDbMode $ olcDbSearchStack $ olcDbMaxEntrySize $ olcDbRtxnSize $ " + "olcDbMultival ) )", + Cft_Database, mdbcfg+1 }, + { NULL, 0, NULL } +}; + +static slap_verbmasks mdb_envflags[] = { + { BER_BVC("nosync"), MDB_NOSYNC }, + { BER_BVC("nometasync"), MDB_NOMETASYNC }, + { BER_BVC("writemap"), MDB_WRITEMAP }, + { BER_BVC("mapasync"), MDB_MAPASYNC }, + { BER_BVC("nordahead"), MDB_NORDAHEAD }, + { BER_BVNULL, 0 } +}; + +static int +mdb_bk_cfg( ConfigArgs *c ) +{ + int rc = 0; + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( MDB_idl_logn != MDB_IDL_LOGN ) + c->value_int = MDB_idl_logn; + else + rc = 1; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* We expect to immediately be followed by an Add, but */ + MDB_idl_logn = MDB_IDL_LOGN; /* return to default for safety */ + mdb_idl_reset(); + c->bi->bi_private = 0; + } else { + /* with 32 bit ints, db_size max is 2^30 and um_size max is 2^31 */ + if ( c->value_int >= MDB_IDL_LOGN && ( c->value_int < sizeof(int) * CHAR_BIT - 1 )) { + MDB_idl_logn = c->value_int; + mdb_idl_reset(); + c->bi->bi_private = (void *)8; /* non-NULL to show we're using it */ + } else { + rc = 1; + } + } + return rc; +} + +/* perform periodic syncs */ +static void * +mdb_checkpoint( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct mdb_info *mdb = rtask->arg; + + mdb_env_sync( mdb->mi_dbenv, 1 ); + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + return NULL; +} + +/* reindex entries on the fly */ +static void * +mdb_online_index( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + BackendDB *be = rtask->arg; + struct mdb_info *mdb = be->be_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + MDB_cursor *curs; + MDB_val key, data; + MDB_txn *txn; + ID id; + Entry *e; + int rc, getnext = 1; + int i; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + op->o_bd = be; + + id = 1; + key.mv_size = sizeof(ID); + + while ( 1 ) { + if ( slapd_shutdown ) + break; + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn ); + if ( rc ) + break; + rc = mdb_cursor_open( txn, mdb->mi_id2entry, &curs ); + if ( rc ) { + mdb_txn_abort( txn ); + break; + } + if ( getnext ) { + getnext = 0; + key.mv_data = &id; + rc = mdb_cursor_get( curs, &key, &data, MDB_SET_RANGE ); + if ( rc ) { + mdb_txn_abort( txn ); + if ( rc == MDB_NOTFOUND ) + rc = 0; + break; + } + memcpy( &id, key.mv_data, sizeof( id )); + } + + rc = mdb_id2entry( op, curs, id, &e ); + mdb_cursor_close( curs ); + if ( rc ) { + mdb_txn_abort( txn ); + if ( rc == MDB_NOTFOUND ) { + id++; + getnext = 1; + continue; + } + break; + } + rc = mdb_index_entry( op, txn, MDB_INDEX_UPDATE_OP, e ); + mdb_entry_return( op, e ); + if ( rc == 0 ) { + rc = mdb_txn_commit( txn ); + txn = NULL; + } else { + mdb_txn_abort( txn ); + txn = NULL; + } + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_online_index) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + break; + } + id++; + getnext = 1; + } + + for ( i = 0; i < mdb->mi_nattrs; i++ ) { + if ( mdb->mi_attrs[ i ]->ai_indexmask & MDB_INDEX_DELETING + || mdb->mi_attrs[ i ]->ai_newmask == 0 ) + { + continue; + } + mdb->mi_attrs[ i ]->ai_indexmask = mdb->mi_attrs[ i ]->ai_newmask; + mdb->mi_attrs[ i ]->ai_newmask = 0; + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + mdb->mi_index_task = NULL; + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* Cleanup loose ends after Modify completes */ +static int +mdb_cf_cleanup( ConfigArgs *c ) +{ + struct mdb_info *mdb = c->be->be_private; + int rc = 0; + + if ( mdb->mi_flags & MDB_DEL_INDEX ) { + mdb_attr_flush( mdb ); + mdb->mi_flags ^= MDB_DEL_INDEX; + } + + if ( mdb->mi_flags & MDB_RE_OPEN ) { + mdb->mi_flags ^= MDB_RE_OPEN; + rc = c->be->bd_info->bi_db_close( c->be, &c->reply ); + if ( rc == 0 ) + rc = c->be->bd_info->bi_db_open( c->be, &c->reply ); + /* If this fails, we need to restart */ + if ( rc ) { + slapd_shutdown = 2; + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "failed to reopen database, rc=%d", rc ); + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_cf_cleanup) + ": %s\n", c->cr_msg ); + rc = LDAP_OTHER; + } + } + + if ( mdb->mi_flags & MDB_OPEN_INDEX ) { + mdb->mi_flags ^= MDB_OPEN_INDEX; + rc = mdb_attr_dbs_open( c->be, NULL, &c->reply ); + if ( rc ) + rc = LDAP_OTHER; + } + return rc; +} + +static int +mdb_cf_gen( ConfigArgs *c ) +{ + struct mdb_info *mdb = c->be->be_private; + int rc; + + if ( c->op == SLAP_CONFIG_EMIT ) { + rc = 0; + switch( c->type ) { + case MDB_MODE: { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "0%o", mdb->mi_dbenv_mode ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } break; + + case MDB_CHKPT: + if ( mdb->mi_txn_cp ) { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "%ld %ld", + (long) mdb->mi_txn_cp_kbyte, (long) mdb->mi_txn_cp_min ); + if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) { + bv.bv_val = buf; + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + } else { + rc = 1; + } + break; + + case MDB_DIRECTORY: + if ( mdb->mi_dbenv_home ) { + c->value_string = ch_strdup( mdb->mi_dbenv_home ); + } else { + rc = 1; + } + break; + + case MDB_DBNOSYNC: + if ( mdb->mi_dbenv_flags & MDB_NOSYNC ) + c->value_int = 1; + break; + + case MDB_ENVFLAGS: + if ( mdb->mi_dbenv_flags ) { + mask_to_verbs( mdb_envflags, mdb->mi_dbenv_flags, &c->rvalue_vals ); + } + if ( !c->rvalue_vals ) rc = 1; + break; + + case MDB_INDEX: + mdb_attr_index_unparse( mdb, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; + + case MDB_SSTACK: + c->value_int = mdb->mi_search_stack_depth; + break; + + case MDB_MAXREADERS: + c->value_int = mdb->mi_readers; + break; + + case MDB_MAXSIZE: + c->value_ulong = mdb->mi_mapsize; + break; + + case MDB_MULTIVAL: + mdb_attr_multi_unparse( mdb, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + rc = 0; + switch( c->type ) { + case MDB_MODE: +#if 0 + /* FIXME: does it make any sense to change the mode, + * if we don't exec a chmod()? */ + mdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + break; +#endif + + /* single-valued no-ops */ + case MDB_SSTACK: + case MDB_MAXREADERS: + case MDB_MAXSIZE: + break; + + case MDB_CHKPT: + if ( mdb->mi_txn_cp_task ) { + struct re_s *re = mdb->mi_txn_cp_task; + mdb->mi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + mdb->mi_txn_cp = 0; + break; + case MDB_DIRECTORY: + mdb->mi_flags |= MDB_RE_OPEN; + ch_free( mdb->mi_dbenv_home ); + mdb->mi_dbenv_home = NULL; + config_push_cleanup( c, mdb_cf_cleanup ); + ldap_pvt_thread_pool_purgekey( mdb->mi_dbenv ); + break; + case MDB_DBNOSYNC: + mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC, 0 ); + mdb->mi_dbenv_flags &= ~MDB_NOSYNC; + break; + + case MDB_ENVFLAGS: + if ( c->valx == -1 ) { + int i; + for ( i=0; mdb_envflags[i].mask; i++) { + if ( mdb->mi_dbenv_flags & mdb_envflags[i].mask ) { + /* not all flags are runtime resettable */ + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 ); + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + config_push_cleanup( c, mdb_cf_cleanup ); + rc = 0; + } + mdb->mi_dbenv_flags ^= mdb_envflags[i].mask; + } + } + } else { + int i = verb_to_mask( c->line, mdb_envflags ); + if ( mdb_envflags[i].mask & mdb->mi_dbenv_flags ) { + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 ); + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + config_push_cleanup( c, mdb_cf_cleanup ); + rc = 0; + } + mdb->mi_dbenv_flags ^= mdb_envflags[i].mask; + } else { + /* unknown keyword */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"", + c->argv[0], c->argv[i] ); + Debug( LDAP_DEBUG_CONFIG, "%s %s\n", c->log, c->cr_msg ); + rc = 1; + } + } + break; + + case MDB_INDEX: + if ( c->valx == -1 ) { + int i; + + /* delete all */ + for ( i = 0; i < mdb->mi_nattrs; i++ ) { + mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING; + } + mdb->mi_defaultmask = 0; + mdb->mi_flags |= MDB_DEL_INDEX; + config_push_cleanup( c, mdb_cf_cleanup ); + + } else { + struct berval bv, def = BER_BVC("default"); + char *ptr; + + for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++); + + bv.bv_val = c->line; + bv.bv_len = ptr - bv.bv_val; + if ( bvmatch( &bv, &def )) { + mdb->mi_defaultmask = 0; + + } else { + int i; + char **attrs; + char sep; + + sep = bv.bv_val[ bv.bv_len ]; + bv.bv_val[ bv.bv_len ] = '\0'; + attrs = ldap_str2charray( bv.bv_val, "," ); + + for ( i = 0; attrs[ i ]; i++ ) { + AttributeDescription *ad = NULL; + const char *text; + AttrInfo *ai; + + slap_str2ad( attrs[ i ], &ad, &text ); + /* if we got here... */ + assert( ad != NULL ); + + ai = mdb_attr_mask( mdb, ad ); + /* if we got here... */ + assert( ai != NULL ); + + ai->ai_indexmask |= MDB_INDEX_DELETING; + mdb->mi_flags |= MDB_DEL_INDEX; + config_push_cleanup( c, mdb_cf_cleanup ); + } + + bv.bv_val[ bv.bv_len ] = sep; + ldap_charray_free( attrs ); + } + } + break; + case MDB_MULTIVAL: + if ( c->valx == -1 ) { + int i; + + /* delete all */ + for ( i = 0; i < mdb->mi_nattrs; i++ ) { + mdb->mi_attrs[i]->ai_multi_hi = UINT_MAX; + mdb->mi_attrs[i]->ai_multi_lo = UINT_MAX; + } + mdb->mi_multi_hi = UINT_MAX; + mdb->mi_multi_lo = UINT_MAX; + + } else { + struct berval bv, def = BER_BVC("default"); + char *ptr; + + for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++); + + bv.bv_val = c->line; + bv.bv_len = ptr - bv.bv_val; + if ( bvmatch( &bv, &def )) { + mdb->mi_multi_hi = UINT_MAX; + mdb->mi_multi_lo = UINT_MAX; + + } else { + int i; + char **attrs; + char sep; + + sep = bv.bv_val[ bv.bv_len ]; + bv.bv_val[ bv.bv_len ] = '\0'; + attrs = ldap_str2charray( bv.bv_val, "," ); + + for ( i = 0; attrs[ i ]; i++ ) { + AttributeDescription *ad = NULL; + const char *text; + AttrInfo *ai; + + slap_str2ad( attrs[ i ], &ad, &text ); + /* if we got here... */ + assert( ad != NULL ); + + ai = mdb_attr_mask( mdb, ad ); + /* if we got here... */ + assert( ai != NULL ); + + ai->ai_multi_hi = UINT_MAX; + ai->ai_multi_lo = UINT_MAX; + } + + bv.bv_val[ bv.bv_len ] = sep; + ldap_charray_free( attrs ); + } + } + break; + } + return rc; + } + + switch( c->type ) { + case MDB_MODE: + if ( ASCII_DIGIT( c->argv[1][0] ) ) { + long mode; + char *next; + errno = 0; + mode = strtol( c->argv[1], &next, 0 ); + if ( errno != 0 || next == c->argv[1] || next[0] != '\0' ) { + fprintf( stderr, "%s: " + "unable to parse mode=\"%s\".\n", + c->log, c->argv[1] ); + return 1; + } + mdb->mi_dbenv_mode = mode; + + } else { + char *m = c->argv[1]; + int who, what, mode = 0; + + if ( strlen( m ) != STRLENOF("-rwxrwxrwx") ) { + return 1; + } + + if ( m[0] != '-' ) { + return 1; + } + + m++; + for ( who = 0; who < 3; who++ ) { + for ( what = 0; what < 3; what++, m++ ) { + if ( m[0] == '-' ) { + continue; + } else if ( m[0] != "rwx"[what] ) { + return 1; + } + mode += ((1 << (2 - what)) << 3*(2 - who)); + } + } + mdb->mi_dbenv_mode = mode; + } + break; + case MDB_CHKPT: { + unsigned cp_kbyte, cp_min; + if ( lutil_atoux( &cp_kbyte, c->argv[1], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid kbyte \"%s\" in \"checkpoint\".\n", + c->log, c->argv[1] ); + return 1; + } + if ( lutil_atoux( &cp_min, c->argv[2], 0 ) != 0 ) { + fprintf( stderr, "%s: " + "invalid minutes \"%s\" in \"checkpoint\".\n", + c->log, c->argv[2] ); + return 1; + } + mdb->mi_txn_cp = 1; + mdb->mi_txn_cp_kbyte = cp_kbyte; + mdb->mi_txn_cp_min = cp_min; + /* If we're in server mode and time-based checkpointing is enabled, + * submit a task to perform periodic checkpoints. + */ + if ((slapMode & SLAP_SERVER_MODE) && mdb->mi_txn_cp_min ) { + struct re_s *re = mdb->mi_txn_cp_task; + if ( re ) { + re->interval.tv_sec = mdb->mi_txn_cp_min * 60; + } else { + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"checkpoint\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + mdb->mi_txn_cp_task = ldap_pvt_runqueue_insert( &slapd_rq, + mdb->mi_txn_cp_min * 60, mdb_checkpoint, mdb, + LDAP_XSTRING(mdb_checkpoint), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } break; + + case MDB_DIRECTORY: { + FILE *f; + char *ptr, *testpath; + int len; + + len = strlen( c->value_string ); + testpath = ch_malloc( len + STRLENOF(LDAP_DIRSEP) + STRLENOF("DUMMY") + 1 ); + ptr = lutil_strcopy( testpath, c->value_string ); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "DUMMY" ); + f = fopen( testpath, "w" ); + if ( f ) { + fclose( f ); + unlink( testpath ); + } + ch_free( testpath ); + if ( !f ) { + char ebuf[128]; + int saved_errno = errno; + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid path: %s", + c->log, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ) ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg ); + return -1; + } + + if ( mdb->mi_dbenv_home ) + ch_free( mdb->mi_dbenv_home ); + mdb->mi_dbenv_home = c->value_string; + + } + break; + + case MDB_DBNOSYNC: + if ( c->value_int ) + mdb->mi_dbenv_flags |= MDB_NOSYNC; + else + mdb->mi_dbenv_flags &= ~MDB_NOSYNC; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC, + c->value_int ); + } + break; + + case MDB_ENVFLAGS: { + int i, j; + for ( i=1; i<c->argc; i++ ) { + j = verb_to_mask( c->argv[i], mdb_envflags ); + if ( mdb_envflags[j].mask ) { + if ( mdb->mi_flags & MDB_IS_OPEN ) + rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[j].mask, 1 ); + else + rc = 0; + if ( rc ) { + mdb->mi_flags |= MDB_RE_OPEN; + config_push_cleanup( c, mdb_cf_cleanup ); + rc = 0; + } + mdb->mi_dbenv_flags |= mdb_envflags[j].mask; + } else { + /* unknown keyword */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"", + c->argv[0], c->argv[i] ); + Debug( LDAP_DEBUG_ANY, "%s %s\n", c->log, c->cr_msg ); + return 1; + } + } + } + break; + + case MDB_INDEX: + rc = mdb_attr_index_config( mdb, c->fname, c->lineno, + c->argc - 1, &c->argv[1], &c->reply); + + if( rc != LDAP_SUCCESS ) return 1; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_OPEN_INDEX; + config_push_cleanup( c, mdb_cf_cleanup ); + if ( !mdb->mi_index_task ) { + /* Start the task as soon as we finish here. Set a long + * interval (10 hours) so that it only gets scheduled once. + */ + if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) { + fprintf( stderr, "%s: " + "\"index\" must occur after \"suffix\".\n", + c->log ); + return 1; + } + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + mdb->mi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000, + mdb_online_index, c->be, + LDAP_XSTRING(mdb_online_index), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + break; + + case MDB_SSTACK: + if ( c->value_int < MINIMUM_SEARCH_STACK_DEPTH ) { + fprintf( stderr, + "%s: depth %d too small, using %d\n", + c->log, c->value_int, MINIMUM_SEARCH_STACK_DEPTH ); + c->value_int = MINIMUM_SEARCH_STACK_DEPTH; + } + mdb->mi_search_stack_depth = c->value_int; + break; + + case MDB_MAXREADERS: + mdb->mi_readers = c->value_int; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_RE_OPEN; + config_push_cleanup( c, mdb_cf_cleanup ); + } + break; + + case MDB_MAXSIZE: + mdb->mi_mapsize = c->value_ulong; + if ( mdb->mi_flags & MDB_IS_OPEN ) { + mdb->mi_flags |= MDB_RE_OPEN; + config_push_cleanup( c, mdb_cf_cleanup ); + } + break; + + case MDB_MULTIVAL: + rc = mdb_attr_multi_config( mdb, c->fname, c->lineno, + c->argc - 1, &c->argv[1], &c->reply); + + if( rc != LDAP_SUCCESS ) return 1; + break; + } + return 0; +} + +int mdb_back_init_cf( BackendInfo *bi ) +{ + int rc; + bi->bi_cf_ocs = mdbocs; + + rc = config_register_schema( mdbcfg, mdbocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-mdb/delete.c b/servers/slapd/back-mdb/delete.c new file mode 100644 index 0000000..d4e6b8d --- /dev/null +++ b/servers/slapd/back-mdb/delete.c @@ -0,0 +1,436 @@ +/* delete.c - mdb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "lutil.h" +#include "back-mdb.h" + +int +mdb_delete( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct berval pdn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + MDB_txn *txn = NULL; + MDB_cursor *mc; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + + LDAPControl **preread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + + Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_delete) ": %s\n", + op->o_req_dn.bv_val ); + + ctrls[num_ctrls] = 0; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* allocate CSN */ + if ( BER_BVISNULL( &op->o_csn ) ) { + struct berval csn; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + csn.bv_val = csnbuf; + csn.bv_len = sizeof(csnbuf); + slap_get_csn( op, &csn, 1 ); + } + + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc ); + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + + /* get parent */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &pdn, &p, NULL, 1 ); + switch( rs->sr_err ) { + case 0: + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rs->sr_err == MDB_NOTFOUND ) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val ); + + if ( p && !BER_BVISEMPTY( &p->e_name )) { + rs->sr_matched = ch_strdup( p->e_name.bv_val ); + if ( is_entry_referral( p )) { + BerVarray ref = get_entry_referrals( op, p ); + rs->sr_ref = referral_rewrite( ref, &p->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + if ( p ) { + mdb_entry_return( op, p ); + p = NULL; + } + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + } + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, NULL, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + e = p; + p = NULL; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* FIXME : dn2entry() should return non-glue entry */ + if ( rs->sr_err == MDB_NOTFOUND || ( !manageDSAit && is_entry_glue( e ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val ); + + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( pdn.bv_len != 0 ) { + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": no write " + "access to parent\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + /* no parent, must be root to delete */ + if( ! be_isroot( op ) ) { + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_shadow_update( op ) ) { + p = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + p = NULL; + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) + ": no access to parent\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to parent"; + goto return_results; + } + + } else { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) + ": no parent and not root\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + } + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + rs->sr_err = access_allowed( op, e, + entry, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": no write access " + "to entry\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "no write access to entry"; + goto return_results; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow delete */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": entry is referral\n" ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = ch_strdup( e->e_name.bv_val ); + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + /* pre-read */ + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": pre-read " + "failed!\n" ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + rs->sr_text = NULL; + + /* Can't do it if we have kids */ + rs->sr_err = mdb_dn2id_children( op, txn, e ); + if( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": non-leaf %s\n", + op->o_req_dn.bv_val ); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subordinate objects must be deleted first"; + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + } + goto return_results; + } + + /* delete from dn2id */ + rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, 1 ); + mdb_cursor_close( mc ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": dn2id failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete indices for old attributes */ + rs->sr_err = mdb_index_entry_del( op, txn, e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": index failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_text = "entry index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* fixup delete CSN */ + if ( !SLAP_SHADOW( op->o_bd )) { + struct berval vals[2]; + + assert( !BER_BVISNULL( &op->o_csn ) ); + vals[0] = op->o_csn; + BER_BVZERO( &vals[1] ); + rs->sr_err = mdb_index_values( op, txn, slap_schema.si_ad_entryCSN, + vals, 0, SLAP_INDEX_ADD_OP ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = "entryCSN index update failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + /* delete from id2entry */ + rs->sr_err = mdb_id2entry_delete( op->o_bd, txn, e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_delete) ": id2entry failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_text = "entry delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + if ( pdn.bv_len != 0 ) { + parent_is_glue = is_entry_glue(p); + rs->sr_err = mdb_dn2id_children( op, txn, p ); + if ( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_delete) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + parent_is_leaf = 1; + } + mdb_entry_return( op, p ); + p = NULL; + } + + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } else { + rs->sr_err = mdb_txn_commit( txn ); + } + txn = NULL; + } + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e->e_id, op->o_req_dn.bv_val ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + + if ( p != NULL ) { + mdb_entry_return( op, p ); + } + + /* free entry */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + send_ldap_result( op, rs ); + slap_graduate_commit_csn( op ); + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/dn2entry.c b/servers/slapd/back-mdb/dn2entry.c new file mode 100644 index 0000000..2262147 --- /dev/null +++ b/servers/slapd/back-mdb/dn2entry.c @@ -0,0 +1,79 @@ +/* dn2entry.c - routines to deal with the dn2id / id2entry glue */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +/* + * dn2entry - look up dn in the cache/indexes and return the corresponding + * entry. If the requested DN is not found and matched is TRUE, return info + * for the closest ancestor of the DN. Otherwise e is NULL. + */ + +int +mdb_dn2entry( + Operation *op, + MDB_txn *tid, + MDB_cursor *m2, + struct berval *dn, + Entry **e, + ID *nsubs, + int matched ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int rc, rc2; + ID id = NOID; + struct berval mbv, nmbv; + MDB_cursor *mc; + + Debug(LDAP_DEBUG_TRACE, "mdb_dn2entry(\"%s\")\n", + dn->bv_val ? dn->bv_val : "" ); + + *e = NULL; + + rc = mdb_dn2id( op, tid, m2, dn, &id, nsubs, &mbv, &nmbv ); + if ( rc ) { + if ( matched ) { + rc2 = mdb_cursor_open( tid, mdb->mi_id2entry, &mc ); + if ( rc2 == MDB_SUCCESS ) { + rc2 = mdb_id2entry( op, mc, id, e ); + mdb_cursor_close( mc ); + } + } + + } else { + rc = mdb_cursor_open( tid, mdb->mi_id2entry, &mc ); + if ( rc == MDB_SUCCESS ) { + rc = mdb_id2entry( op, mc, id, e ); + mdb_cursor_close(mc); + } + } + if ( *e ) { + (*e)->e_name = mbv; + if ( rc == MDB_SUCCESS ) + ber_dupbv_x( &(*e)->e_nname, dn, op->o_tmpmemctx ); + else + ber_dupbv_x( &(*e)->e_nname, &nmbv, op->o_tmpmemctx ); + } else { + op->o_tmpfree( mbv.bv_val, op->o_tmpmemctx ); + } + + return rc; +} diff --git a/servers/slapd/back-mdb/dn2id.c b/servers/slapd/back-mdb/dn2id.c new file mode 100644 index 0000000..68000ca --- /dev/null +++ b/servers/slapd/back-mdb/dn2id.c @@ -0,0 +1,981 @@ +/* dn2id.c - routines to deal with the dn2id index */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" +#include "lutil.h" + +/* Management routines for a hierarchically structured database. + * + * Instead of a ldbm-style dn2id database, we use a hierarchical one. Each + * entry in this database is a struct diskNode, keyed by entryID and with + * the data containing the RDN and entryID of the node's children. We use + * a B-Tree with sorted duplicates to store all the children of a node under + * the same key. Also, the first item under the key contains the entry's own + * rdn and the ID of the node's parent, to allow bottom-up tree traversal as + * well as top-down. To keep this info first in the list, the high bit of all + * subsequent nrdnlen's is always set. This means we can only accommodate + * RDNs up to length 32767, but that's fine since full DNs are already + * restricted to 8192. + * + * Also each child node contains a count of the number of entries in + * its subtree, appended after its entryID. + * + * The diskNode is a variable length structure. This definition is not + * directly usable for in-memory manipulation. + */ +typedef struct diskNode { + unsigned char nrdnlen[2]; + char nrdn[1]; + char rdn[1]; /* variable placement */ + unsigned char entryID[sizeof(ID)]; /* variable placement */ + /* unsigned char nsubs[sizeof(ID)]; in child nodes only */ +} diskNode; + +/* Sort function for the sorted duplicate data items of a dn2id key. + * Sorts based on normalized RDN, in length order. + */ +int +mdb_dup_compare( + const MDB_val *usrkey, + const MDB_val *curkey +) +{ + diskNode *un, *cn; + int rc, nrlen; + + un = (diskNode *)usrkey->mv_data; + cn = (diskNode *)curkey->mv_data; + + /* data is not aligned, cannot compare directly */ + rc = un->nrdnlen[0] - cn->nrdnlen[0]; + if ( rc ) return rc; + rc = un->nrdnlen[1] - cn->nrdnlen[1]; + if ( rc ) return rc; + + nrlen = ((un->nrdnlen[0] & 0x7f) << 8) | un->nrdnlen[1]; + return strncmp( un->nrdn, cn->nrdn, nrlen ); +} + +/* We add two elements to the DN2ID database - a data item under the parent's + * entryID containing the child's RDN and entryID, and an item under the + * child's entryID containing the parent's entryID. + */ +int +mdb_dn2id_add( + Operation *op, + MDB_cursor *mcp, + MDB_cursor *mcd, + ID pid, + ID nsubs, + int upsub, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data; + ID nid; + int rc, rlen, nrlen; + diskNode *d; + char *ptr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_add 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn ? e->e_ndn : "" ); + + nrlen = dn_rdnlen( op->o_bd, &e->e_nname ); + if (nrlen) { + rlen = dn_rdnlen( op->o_bd, &e->e_name ); + } else { + nrlen = e->e_nname.bv_len; + rlen = e->e_name.bv_len; + } + + d = op->o_tmpalloc(sizeof(diskNode) + rlen + nrlen + sizeof(ID), op->o_tmpmemctx); + d->nrdnlen[1] = nrlen & 0xff; + d->nrdnlen[0] = (nrlen >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, e->e_nname.bv_val, nrlen ); + *ptr++ = '\0'; + ptr = lutil_strncopy( ptr, e->e_name.bv_val, rlen ); + *ptr++ = '\0'; + memcpy( ptr, &e->e_id, sizeof( ID )); + ptr += sizeof( ID ); + memcpy( ptr, &nsubs, sizeof( ID )); + + key.mv_size = sizeof(ID); + key.mv_data = &nid; + + nid = pid; + + /* Need to make dummy root node once. Subsequent attempts + * will fail harmlessly. + */ + if ( pid == 0 ) { + diskNode dummy = {{0, 0}, "", "", ""}; + data.mv_data = &dummy; + data.mv_size = sizeof(diskNode); + + mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA ); + } + + data.mv_data = d; + data.mv_size = sizeof(diskNode) + rlen + nrlen + sizeof( ID ); + + /* Add our child node under parent's key */ + rc = mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA ); + + /* Add our own node */ + if (rc == 0) { + int flag = MDB_NODUPDATA; + nid = e->e_id; + /* drop subtree count */ + data.mv_size -= sizeof( ID ); + ptr -= sizeof( ID ); + memcpy( ptr, &pid, sizeof( ID )); + d->nrdnlen[0] ^= 0x80; + + if ((slapMode & SLAP_TOOL_MODE) || (e->e_id == mdb->mi_nextid)) + flag |= MDB_APPEND; + rc = mdb_cursor_put( mcd, &key, &data, flag ); + } + op->o_tmpfree( d, op->o_tmpmemctx ); + + /* Add our subtree count to all superiors */ + if ( rc == 0 && upsub && pid ) { + ID subs; + nid = pid; + do { + /* Get parent's RDN */ + rc = mdb_cursor_get( mcp, &key, &data, MDB_SET ); + if ( !rc ) { + char *p2; + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &nid, ptr, sizeof( ID )); + /* Get parent's node under grandparent */ + d = data.mv_data; + rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1]; + p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, rlen+2 ); + *p2 ^= 0x80; + data.mv_data = p2; + rc = mdb_cursor_get( mcp, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + if ( !rc ) { + /* Get parent's subtree count */ + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &subs, ptr, sizeof( ID )); + subs += nsubs; + p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, data.mv_size - sizeof( ID )); + memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID )); + data.mv_data = p2; + rc = mdb_cursor_put( mcp, &key, &data, MDB_CURRENT ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + } + } + if ( rc ) + break; + } while ( nid ); + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_add 0x%lx: %d\n", e->e_id, rc ); + + return rc; +} + +/* mc must have been set by mdb_dn2id */ +int +mdb_dn2id_delete( + Operation *op, + MDB_cursor *mc, + ID id, + ID nsubs ) +{ + ID nid; + char *ptr; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_delete 0x%lx\n", + id ); + + /* Delete our ID from the parent's list */ + rc = mdb_cursor_del( mc, 0 ); + + /* Delete our ID from the tree. With sorted duplicates, this + * will leave any child nodes still hanging around. This is OK + * for modrdn, which will add our info back in later. + */ + if ( rc == 0 ) { + MDB_val key, data; + if ( nsubs ) { + mdb_cursor_get( mc, &key, NULL, MDB_GET_CURRENT ); + memcpy( &nid, key.mv_data, sizeof( ID )); + } + key.mv_size = sizeof(ID); + key.mv_data = &id; + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == 0 ) + rc = mdb_cursor_del( mc, 0 ); + } + + /* Delete our subtree count from all superiors */ + if ( rc == 0 && nsubs && nid ) { + MDB_val key, data; + ID subs; + key.mv_data = &nid; + key.mv_size = sizeof( ID ); + do { + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( !rc ) { + char *p2; + diskNode *d; + int rlen; + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &nid, ptr, sizeof( ID )); + /* Get parent's node under grandparent */ + d = data.mv_data; + rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1]; + p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, rlen+2 ); + *p2 ^= 0x80; + data.mv_data = p2; + rc = mdb_cursor_get( mc, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + if ( !rc ) { + /* Get parent's subtree count */ + ptr = (char *)data.mv_data + data.mv_size - sizeof( ID ); + memcpy( &subs, ptr, sizeof( ID )); + subs -= nsubs; + p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy( p2, data.mv_data, data.mv_size - sizeof( ID )); + memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID )); + data.mv_data = p2; + rc = mdb_cursor_put( mc, &key, &data, MDB_CURRENT ); + op->o_tmpfree( p2, op->o_tmpmemctx ); + } + + } + if ( rc ) + break; + } while ( nid ); + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_delete 0x%lx: %d\n", id, rc ); + return rc; +} + +/* return last found ID in *id if no match + * If mc is provided, it will be left pointing to the RDN's + * record under the parent's ID. If nsubs is provided, return + * the number of entries in this entry's subtree. + */ +int +mdb_dn2id( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + struct berval *in, + ID *id, + ID *nsubs, + struct berval *matched, + struct berval *nmatched ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_cursor *cursor; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + char dn[SLAP_LDAPDN_MAXLEN]; + ID pid, nid; + struct berval tmp; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id(\"%s\")\n", in->bv_val ? in->bv_val : "" ); + + if ( matched ) { + matched->bv_val = dn + sizeof(dn) - 1; + matched->bv_len = 0; + *matched->bv_val-- = '\0'; + } + if ( nmatched ) { + nmatched->bv_len = 0; + nmatched->bv_val = 0; + } + + if ( !in->bv_len ) { + *id = 0; + nid = 0; + goto done; + } + + tmp = *in; + + if ( op->o_bd->be_nsuffix[0].bv_len ) { + nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len; + tmp.bv_val += nrlen; + tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len; + } else { + for ( ptr = tmp.bv_val + tmp.bv_len - 1; ptr >= tmp.bv_val; ptr-- ) + if (DN_SEPARATOR(*ptr)) + break; + ptr++; + tmp.bv_len -= ptr - tmp.bv_val; + tmp.bv_val = ptr; + } + nid = 0; + key.mv_size = sizeof(ID); + + if ( mc ) { + cursor = mc; + } else { + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) goto done; + } + + for (;;) { + key.mv_data = &pid; + pid = nid; + + data.mv_size = sizeof(diskNode) + tmp.bv_len; + d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + d->nrdnlen[1] = tmp.bv_len & 0xff; + d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len ); + *ptr = '\0'; + data.mv_data = d; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( d, op->o_tmpmemctx ); + if ( rc ) + break; + ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID); + memcpy( &nid, ptr, sizeof(ID)); + + /* grab the non-normalized RDN */ + if ( matched ) { + int rlen; + d = data.mv_data; + rlen = data.mv_size - sizeof(diskNode) - tmp.bv_len - sizeof(ID); + matched->bv_len += rlen; + matched->bv_val -= rlen + 1; + ptr = lutil_strcopy( matched->bv_val, d->rdn + tmp.bv_len ); + if ( pid ) { + *ptr = ','; + matched->bv_len++; + } + } + if ( nmatched ) { + nmatched->bv_val = tmp.bv_val; + } + + if ( tmp.bv_val > in->bv_val ) { + for (ptr = tmp.bv_val - 2; ptr > in->bv_val && + !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= in->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + tmp.bv_len = tmp.bv_val - ptr - 1; + tmp.bv_val = ptr; + } + } else { + break; + } + } + *id = nid; + /* return subtree count if requested */ + if ( !rc && nsubs ) { + ptr = (char *)data.mv_data + data.mv_size - sizeof(ID); + memcpy( nsubs, ptr, sizeof( ID )); + } + if ( !mc ) + mdb_cursor_close( cursor ); +done: + if ( matched ) { + if ( matched->bv_len ) { + ptr = op->o_tmpalloc( matched->bv_len+1, op->o_tmpmemctx ); + strcpy( ptr, matched->bv_val ); + matched->bv_val = ptr; + } else { + if ( BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) && !nid ) { + ber_dupbv( matched, (struct berval *)&slap_empty_bv ); + } else { + matched->bv_val = NULL; + } + } + } + if ( nmatched ) { + if ( nmatched->bv_val ) { + nmatched->bv_len = in->bv_len - (nmatched->bv_val - in->bv_val); + } else { + *nmatched = slap_empty_bv; + } + } + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: get failed: %s (%d)\n", + mdb_strerror( rc ), rc ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: got id=0x%lx\n", + nid ); + } + + return rc; +} + +/* return IDs from root to parent of DN */ +int +mdb_dn2sups( + Operation *op, + MDB_txn *txn, + struct berval *in, + ID *ids ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_cursor *cursor; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + ID pid, nid; + struct berval tmp; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2sups(\"%s\")\n", in->bv_val ); + + if ( !in->bv_len ) { + goto done; + } + + tmp = *in; + + nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len; + tmp.bv_val += nrlen; + tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len; + nid = 0; + key.mv_size = sizeof(ID); + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) goto done; + + for (;;) { + key.mv_data = &pid; + pid = nid; + + data.mv_size = sizeof(diskNode) + tmp.bv_len; + d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + d->nrdnlen[1] = tmp.bv_len & 0xff; + d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80; + ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len ); + *ptr = '\0'; + data.mv_data = d; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( d, op->o_tmpmemctx ); + if ( rc ) + break; + ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID); + memcpy( &nid, ptr, sizeof(ID)); + + if ( pid ) + mdb_idl_insert( ids, pid ); + + if ( tmp.bv_val > in->bv_val ) { + for (ptr = tmp.bv_val - 2; ptr > in->bv_val && + !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= in->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + tmp.bv_len = tmp.bv_val - ptr - 1; + tmp.bv_val = ptr; + } + } else { + break; + } + } + mdb_cursor_close( cursor ); +done: + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2sups: get failed: %s (%d)\n", + mdb_strerror( rc ), rc ); + } + + return rc; +} + +int +mdb_dn2id_children( + Operation *op, + MDB_txn *txn, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + int rc; + ID id; + + key.mv_size = sizeof(ID); + key.mv_data = &id; + id = e->e_id; + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) return rc; + + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc == 0 ) { + size_t dkids; + rc = mdb_cursor_count( cursor, &dkids ); + if ( rc == 0 ) { + if ( dkids < 2 ) rc = MDB_NOTFOUND; + } + } + mdb_cursor_close( cursor ); + return rc; +} + +int +mdb_id2name( + Operation *op, + MDB_txn *txn, + MDB_cursor **cursp, + ID id, + struct berval *name, + struct berval *nname ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + int rc, len, nlen; + char dn[SLAP_LDAPDN_MAXLEN], ndn[SLAP_LDAPDN_MAXLEN], *ptr; + char *dptr, *nptr; + diskNode *d; + + key.mv_size = sizeof(ID); + + if ( !*cursp ) { + rc = mdb_cursor_open( txn, dbi, cursp ); + if ( rc ) return rc; + } + cursor = *cursp; + + len = 0; + nlen = 0; + dptr = dn; + nptr = ndn; + while (id) { + unsigned int nrlen, rlen; + key.mv_data = &id; + data.mv_size = 0; + data.mv_data = ""; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) break; + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + memcpy( &id, ptr, sizeof(ID) ); + d = data.mv_data; + nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1]; + rlen = data.mv_size - sizeof(diskNode) - nrlen; + assert( nrlen < 1024 && rlen < 1024 ); /* FIXME: Sanity check */ + if (nptr > ndn) { + *nptr++ = ','; + *dptr++ = ','; + } + /* copy name and trailing NUL */ + memcpy( nptr, d->nrdn, nrlen+1 ); + memcpy( dptr, d->nrdn+nrlen+1, rlen+1 ); + nptr += nrlen; + dptr += rlen; + } + if ( rc == 0 ) { + name->bv_len = dptr - dn; + nname->bv_len = nptr - ndn; + name->bv_val = op->o_tmpalloc( name->bv_len + 1, op->o_tmpmemctx ); + nname->bv_val = op->o_tmpalloc( nname->bv_len + 1, op->o_tmpmemctx ); + memcpy( name->bv_val, dn, name->bv_len ); + name->bv_val[name->bv_len] = '\0'; + memcpy( nname->bv_val, ndn, nname->bv_len ); + nname->bv_val[nname->bv_len] = '\0'; + } + return rc; +} + +/* Find each id in ids that is a child of base and move it to res. + */ +int +mdb_idscope( + Operation *op, + MDB_txn *txn, + ID base, + ID *ids, + ID *res ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + MDB_cursor *cursor; + ID ida, id, cid = 0, ci0 = 0, idc = 0; + char *ptr; + int rc, copy; + + key.mv_size = sizeof(ID); + + MDB_IDL_ZERO( res ); + + rc = mdb_cursor_open( txn, dbi, &cursor ); + if ( rc ) return rc; + + /* first see if base has any children at all */ + key.mv_data = &base; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + goto leave; + } + { + size_t dkids; + rc = mdb_cursor_count( cursor, &dkids ); + if ( rc == 0 ) { + if ( dkids < 2 ) { + goto leave; + } + } + } + + ida = mdb_idl_first( ids, &cid ); + + /* Don't bother moving out of ids if it's a range */ + if (!MDB_IDL_IS_RANGE(ids)) { + idc = ids[0]; + ci0 = cid; + } + + while (ida != NOID) { + copy = 1; + id = ida; + while (id) { + key.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + /* not found, drop this from ids */ + copy = 0; + break; + } + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + memcpy( &id, ptr, sizeof(ID) ); + if ( id == base ) { + if ( res[0] >= MDB_idl_db_max ) { + /* too many aliases in scope. Fallback to range */ + MDB_IDL_RANGE( res, MDB_IDL_FIRST( ids ), MDB_IDL_LAST( ids )); + goto leave; + } + res[0]++; + res[res[0]] = ida; + copy = 0; + break; + } + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + break; + } + if (idc) { + if (copy) { + if (ci0 != cid) + ids[ci0] = ids[cid]; + ci0++; + } else + idc--; + } + ida = mdb_idl_next( ids, &cid ); + } + if (!MDB_IDL_IS_RANGE( ids )) + ids[0] = idc; + +leave: + mdb_cursor_close( cursor ); + return rc; +} + +/* See if base is a child of any of the scopes + */ +int +mdb_idscopes( + Operation *op, + IdScopes *isc ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_dbi dbi = mdb->mi_dn2id; + MDB_val key, data; + ID id, prev; + ID2 id2; + char *ptr; + int rc = 0; + unsigned int x; + unsigned int nrlen, rlen; + diskNode *d; + + key.mv_size = sizeof(ID); + + if ( !isc->mc ) { + rc = mdb_cursor_open( isc->mt, dbi, &isc->mc ); + if ( rc ) return rc; + } + + id = isc->id; + + /* Catch entries from deref'd aliases */ + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) { + isc->nscope = x; + return MDB_SUCCESS; + } + + isc->sctmp[0].mid = 0; + while (id) { + if ( !rc ) { + key.mv_data = &id; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + return rc; + + /* save RDN info */ + } + d = data.mv_data; + nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1]; + rlen = data.mv_size - sizeof(diskNode) - nrlen; + isc->nrdns[isc->numrdns].bv_len = nrlen; + isc->nrdns[isc->numrdns].bv_val = d->nrdn; + isc->rdns[isc->numrdns].bv_len = rlen; + isc->rdns[isc->numrdns].bv_val = d->nrdn+nrlen+1; + isc->numrdns++; + + if (!rc && id != isc->id) { + /* remember our chain of parents */ + id2.mid = id; + id2.mval = data; + mdb_id2l_insert( isc->sctmp, &id2 ); + } + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + prev = id; + memcpy( &id, ptr, sizeof(ID) ); + /* If we didn't advance, some parent is missing */ + if ( id == prev ) + return MDB_NOTFOUND; + + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) { + if ( !isc->scopes[x].mval.mv_data ) { + /* This node is in scope, add parent chain to scope */ + int i; + for ( i = 1; i <= isc->sctmp[0].mid; i++ ) { + rc = mdb_id2l_insert( isc->scopes, &isc->sctmp[i] ); + if ( rc ) + break; + } + /* check id again since inserts may have changed its position */ + if ( isc->scopes[x].mid != id ) + x = mdb_id2l_search( isc->scopes, id ); + isc->nscope = x; + return MDB_SUCCESS; + } + data = isc->scopes[x].mval; + rc = 1; + } + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + break; + } + return MDB_SUCCESS; +} + +/* See if ID is a child of any of the scopes, + * return MDB_KEYEXIST if so. + */ +int +mdb_idscopechk( + Operation *op, + IdScopes *isc ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data; + ID id, prev; + char *ptr; + int rc = 0; + unsigned int x; + + key.mv_size = sizeof(ID); + + if ( !isc->mc ) { + rc = mdb_cursor_open( isc->mt, mdb->mi_dn2id, &isc->mc ); + if ( rc ) return rc; + } + + id = isc->id; + + while (id) { + if ( !rc ) { + key.mv_data = &id; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + return rc; + } + + ptr = data.mv_data; + ptr += data.mv_size - sizeof(ID); + prev = id; + memcpy( &id, ptr, sizeof(ID) ); + /* If we didn't advance, some parent is missing */ + if ( id == prev ) + return MDB_NOTFOUND; + + x = mdb_id2l_search( isc->scopes, id ); + if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) + return MDB_KEYEXIST; + } + return MDB_SUCCESS; +} + +int +mdb_dn2id_walk( + Operation *op, + IdScopes *isc +) +{ + MDB_val key, data; + diskNode *d; + char *ptr; + int rc, n; + ID nsubs; + + if ( !isc->numrdns ) { + key.mv_data = &isc->id; + key.mv_size = sizeof(ID); + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->scopes[0].mid = isc->id; + isc->numrdns++; + isc->nscope = 0; + /* skip base if not a subtree walk */ + if ( isc->oscope == LDAP_SCOPE_SUBTREE || + isc->oscope == LDAP_SCOPE_BASE ) + return rc; + } + if ( isc->oscope == LDAP_SCOPE_BASE ) + return MDB_NOTFOUND; + + for (;;) { + /* Get next sibling */ + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_NEXT_DUP ); + if ( !rc ) { + ptr = (char *)data.mv_data + data.mv_size - 2*sizeof(ID); + d = data.mv_data; + memcpy( &isc->id, ptr, sizeof(ID)); + + /* If we're pushing down, see if there's any children to find */ + if ( isc->nscope ) { + ptr += sizeof(ID); + memcpy( &nsubs, ptr, sizeof(ID)); + /* No children, go to next sibling */ + if ( nsubs < 2 ) + continue; + } + n = isc->numrdns; + isc->scopes[n].mid = isc->id; + n--; + isc->nrdns[n].bv_len = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1]; + isc->nrdns[n].bv_val = d->nrdn; + isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1; + isc->rdns[n].bv_len = data.mv_size - sizeof(diskNode) - isc->nrdns[n].bv_len - sizeof(ID); + /* return this ID to caller */ + if ( !isc->nscope ) + break; + + /* push down to child */ + key.mv_data = &isc->id; + mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->nscope = 0; + isc->numrdns++; + continue; + + } else if ( rc == MDB_NOTFOUND ) { + if ( !isc->nscope && isc->oscope != LDAP_SCOPE_ONELEVEL ) { + /* reset to first dup */ + mdb_cursor_get( isc->mc, &key, NULL, MDB_GET_CURRENT ); + mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + isc->nscope = 1; + continue; + } else { + isc->numrdns--; + /* stack is empty? */ + if ( !isc->numrdns ) + break; + /* pop up to prev node */ + n = isc->numrdns - 1; + key.mv_data = &isc->scopes[n].mid; + key.mv_size = sizeof(ID); + data.mv_data = isc->nrdns[n].bv_val - 2; + data.mv_size = 1; /* just needs to be non-zero, mdb_dup_compare doesn't care */ + mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH ); + continue; + } + } else { + break; + } + } + return rc; +} + +/* restore the nrdn/rdn pointers after a txn reset */ +void mdb_dn2id_wrestore ( + Operation *op, + IdScopes *isc +) +{ + MDB_val key, data; + diskNode *d; + int rc, n, nrlen; + char *ptr; + + /* We only need to restore up to the n-1th element, + * the nth element will be replaced anyway + */ + key.mv_size = sizeof(ID); + for ( n=0; n<isc->numrdns-1; n++ ) { + key.mv_data = &isc->scopes[n+1].mid; + rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET ); + if ( rc ) + continue; + /* we can't use this data directly since its nrlen + * is missing the high bit setting, so copy it and + * set it properly. we just copy enough to satisfy + * mdb_dup_compare. + */ + d = data.mv_data; + nrlen = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1]; + ptr = op->o_tmpalloc( nrlen+2, op->o_tmpmemctx ); + memcpy( ptr, data.mv_data, nrlen+2 ); + key.mv_data = &isc->scopes[n].mid; + data.mv_data = ptr; + data.mv_size = 1; + *ptr |= 0x80; + mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH ); + op->o_tmpfree( ptr, op->o_tmpmemctx ); + + /* now we're back to where we wanted to be */ + d = data.mv_data; + isc->nrdns[n].bv_val = d->nrdn; + isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1; + } +} diff --git a/servers/slapd/back-mdb/extended.c b/servers/slapd/back-mdb/extended.c new file mode 100644 index 0000000..9ede8b7 --- /dev/null +++ b/servers/slapd/back-mdb/extended.c @@ -0,0 +1,54 @@ +/* extended.c - mdb backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "lber_pvt.h" + +static struct exop { + struct berval *oid; + BI_op_extended *extended; +} exop_table[] = { + { NULL, NULL } +}; + +int +mdb_extended( Operation *op, SlapReply *rs ) +/* struct berval *reqoid, + struct berval *reqdata, + char **rspoid, + struct berval **rspdata, + LDAPControl *** rspctrls, + const char** text, + BerVarray *refs +) */ +{ + int i; + + for( i=0; exop_table[i].extended != NULL; i++ ) { + if( ber_bvcmp( exop_table[i].oid, &op->oq_extended.rs_reqoid ) == 0 ) { + return (exop_table[i].extended)( op, rs ); + } + } + + rs->sr_text = "not supported within naming context"; + return rs->sr_err = LDAP_UNWILLING_TO_PERFORM; +} + diff --git a/servers/slapd/back-mdb/filterindex.c b/servers/slapd/back-mdb/filterindex.c new file mode 100644 index 0000000..6d2c487 --- /dev/null +++ b/servers/slapd/back-mdb/filterindex.c @@ -0,0 +1,1173 @@ +/* filterindex.c - generate the list of candidate entries from a filter */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" +#ifdef LDAP_COMP_MATCH +#include <component.h> +#endif + +static int presence_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeDescription *desc, + ID *ids ); + +static int equality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int inequality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ); +static int approx_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int substring_candidates( + Operation *op, + MDB_txn *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ); + +static int list_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *stack ); + +static int +ext_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack); + +#ifdef LDAP_COMP_MATCH +static int +comp_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack); + +static int +ava_comp_candidates ( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack); +#endif + +int +mdb_filter_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ) +{ + int rc = 0; +#ifdef LDAP_COMP_MATCH + AttributeAliasing *aa; +#endif + Debug( LDAP_DEBUG_FILTER, "=> mdb_filter_candidates\n" ); + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + MDB_IDL_ZERO( ids ); + goto out; + } + + switch ( f->f_choice ) { + case SLAPD_FILTER_COMPUTED: + switch( f->f_result ) { + case SLAPD_COMPARE_UNDEFINED: + /* This technically is not the same as FALSE, but it + * certainly will produce no matches. + */ + /* FALL THRU */ + case LDAP_COMPARE_FALSE: + MDB_IDL_ZERO( ids ); + break; + case LDAP_COMPARE_TRUE: + MDB_IDL_ALL( ids ); + break; + case LDAP_SUCCESS: + /* this is a pre-computed scope, leave it alone */ + break; + } + break; + case LDAP_FILTER_PRESENT: + Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n" ); + rc = presence_candidates( op, rtxn, f->f_desc, ids ); + break; + + case LDAP_FILTER_EQUALITY: + Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n" ); +#ifdef LDAP_COMP_MATCH + if ( is_aliased_attribute && ( aa = is_aliased_attribute ( f->f_ava->aa_desc ) ) ) { + rc = ava_comp_candidates ( op, rtxn, f->f_ava, aa, ids, tmp, stack ); + } + else +#endif + { + rc = equality_candidates( op, rtxn, f->f_ava, ids, tmp ); + } + break; + + case LDAP_FILTER_APPROX: + Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n" ); + rc = approx_candidates( op, rtxn, f->f_ava, ids, tmp ); + break; + + case LDAP_FILTER_SUBSTRINGS: + Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n" ); + rc = substring_candidates( op, rtxn, f->f_sub, ids, tmp ); + break; + + case LDAP_FILTER_GE: + /* if no GE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tGE\n" ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_GE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_LE: + /* if no LE index, use pres */ + Debug( LDAP_DEBUG_FILTER, "\tLE\n" ); + if( f->f_ava->aa_desc->ad_type->sat_ordering && + ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) ) + rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_LE ); + else + rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids ); + break; + + case LDAP_FILTER_NOT: + /* no indexing to support NOT filters */ + Debug( LDAP_DEBUG_FILTER, "\tNOT\n" ); + MDB_IDL_ALL( ids ); + break; + + case LDAP_FILTER_AND: + Debug( LDAP_DEBUG_FILTER, "\tAND\n" ); + rc = list_candidates( op, rtxn, + f->f_and, LDAP_FILTER_AND, ids, tmp, stack ); + break; + + case LDAP_FILTER_OR: + Debug( LDAP_DEBUG_FILTER, "\tOR\n" ); + rc = list_candidates( op, rtxn, + f->f_or, LDAP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_FILTER_EXT: + Debug( LDAP_DEBUG_FILTER, "\tEXT\n" ); + rc = ext_candidates( op, rtxn, f->f_mra, ids, tmp, stack ); + break; + default: + Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n", + (unsigned long) f->f_choice ); + /* Must not return NULL, otherwise extended filters break */ + MDB_IDL_ALL( ids ); + } + if ( ids[2] == NOID && MDB_IDL_IS_RANGE( ids )) { + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ID last; + + if ( mdb->mi_nextid ) { + last = mdb->mi_nextid; + } else { + MDB_cursor *mc; + MDB_val key; + + last = 0; + rc = mdb_cursor_open( rtxn, mdb->mi_id2entry, &mc ); + if ( !rc ) { + rc = mdb_cursor_get( mc, &key, NULL, MDB_LAST ); + if ( !rc ) + memcpy( &last, key.mv_data, sizeof( last )); + mdb_cursor_close( mc ); + } + } + if ( last ) { + ids[2] = last; + } else { + MDB_IDL_ZERO( ids ); + } + } + +out: + Debug( LDAP_DEBUG_FILTER, + "<= mdb_filter_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST( ids ), + (long) MDB_IDL_LAST( ids ) ); + + return rc; +} + +#ifdef LDAP_COMP_MATCH +static int +comp_list_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion* mra, + ComponentFilter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + ComponentFilter *f; + + Debug( LDAP_DEBUG_FILTER, "=> comp_list_candidates 0x%x\n", ftype ); + for ( f = flist; f != NULL; f = f->cf_next ) { + /* ignore precomputed scopes */ + if ( f->cf_choice == SLAPD_FILTER_COMPUTED && + f->cf_result == LDAP_SUCCESS ) { + continue; + } + MDB_IDL_ZERO( save ); + rc = comp_candidates( op, rtxn, mra, f, save, tmp, save+MDB_idl_um_size ); + + if ( rc != 0 ) { + if ( ftype == LDAP_COMP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + if ( ftype == LDAP_COMP_FILTER_AND ) { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_intersection( ids, save ); + } + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= comp_list_candidates: undefined rc=%d\n", + rc ); + } + + return rc; +} + +static int +comp_equality_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentAssertion *ca, + ID *ids, + ID *tmp, + ID *stack) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr = mra->ma_rule; + Syntax *sat_syntax; + ComponentReference* cr_list, *cr; + AttrInfo *ai; + + MDB_IDL_ALL( ids ); + + if ( !ca->ca_comp_ref ) + return 0; + + ai = mdb_attr_mask( op->o_bd->be_private, mra->ma_desc ); + if( ai ) { + cr_list = ai->ai_cr; + } + else { + return 0; + } + /* find a component reference to be indexed */ + sat_syntax = ca->ca_ma_rule->smr_syntax; + for ( cr = cr_list ; cr ; cr = cr->cr_next ) { + if ( cr->cr_string.bv_len == ca->ca_comp_ref->cr_string.bv_len && + strncmp( cr->cr_string.bv_val, ca->ca_comp_ref->cr_string.bv_val,cr->cr_string.bv_len ) == 0 ) + break; + } + + if ( !cr ) + return 0; + + rc = mdb_index_param( op->o_bd, mra->ma_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (ca->ca_ma_rule->smr_filter)( + LDAP_FILTER_EQUALITY, + cr->cr_indexmask, + sat_syntax, + ca->ca_ma_rule, + &prefix, + &ca->ca_ma_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + return 0; + } + + if( keys == NULL ) { + return 0; + } + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= comp_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +ava_comp_candidates ( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack ) +{ + MatchingRuleAssertion mra; + + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + if ( !mra.ma_rule ) { + MDB_IDL_ALL( ids ); + return 0; + } + mra.ma_desc = aa->aa_aliased_ad; + mra.ma_rule = ava->aa_desc->ad_type->sat_equality; + + return comp_candidates ( op, rtxn, &mra, ava->aa_cf, ids, tmp, stack ); +} + +static int +comp_candidates ( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack) +{ + int rc; + + if ( !f ) return LDAP_PROTOCOL_ERROR; + + Debug( LDAP_DEBUG_FILTER, "comp_candidates\n" ); + switch ( f->cf_choice ) { + case SLAPD_FILTER_COMPUTED: + rc = f->cf_result; + break; + case LDAP_COMP_FILTER_AND: + rc = comp_list_candidates( op, rtxn, mra, f->cf_and, LDAP_COMP_FILTER_AND, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_OR: + rc = comp_list_candidates( op, rtxn, mra, f->cf_or, LDAP_COMP_FILTER_OR, ids, tmp, stack ); + break; + case LDAP_COMP_FILTER_NOT: + /* No component indexing supported for NOT filter */ + Debug( LDAP_DEBUG_FILTER, "\tComponent NOT\n" ); + MDB_IDL_ALL( ids ); + rc = LDAP_PROTOCOL_ERROR; + break; + case LDAP_COMP_FILTER_ITEM: + rc = comp_equality_candidates( op, rtxn, mra, f->cf_ca, ids, tmp, stack ); + break; + default: + MDB_IDL_ALL( ids ); + rc = LDAP_PROTOCOL_ERROR; + } + + return( rc ); +} +#endif + +static int +ext_candidates( + Operation *op, + MDB_txn *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack) +{ +#ifdef LDAP_COMP_MATCH + /* + * Currently Only Component Indexing for componentFilterMatch is supported + * Indexing for an extensible filter is not supported yet + */ + if ( mra->ma_cf ) { + return comp_candidates ( op, rtxn, mra, mra->ma_cf, ids, tmp, stack); + } +#endif + if ( mra->ma_desc == slap_schema.si_ad_entryDN ) { + int rc; + ID id; + + MDB_IDL_ZERO( ids ); + if ( mra->ma_rule == slap_schema.si_mr_distinguishedNameMatch ) { +base: + rc = mdb_dn2id( op, rtxn, NULL, &mra->ma_value, &id, NULL, NULL, NULL ); + if ( rc == MDB_SUCCESS ) { + mdb_idl_insert( ids, id ); + } + return 0; + } else if ( mra->ma_rule && mra->ma_rule->smr_match == + dnRelativeMatch && dnIsSuffix( &mra->ma_value, + op->o_bd->be_nsuffix )) { + int scope; + if ( mra->ma_rule == slap_schema.si_mr_dnSuperiorMatch ) { + mdb_dn2sups( op, rtxn, &mra->ma_value, ids ); + return 0; + } + if ( mra->ma_rule == slap_schema.si_mr_dnSubtreeMatch ) + scope = LDAP_SCOPE_SUBTREE; + else if ( mra->ma_rule == slap_schema.si_mr_dnOneLevelMatch ) + scope = LDAP_SCOPE_ONELEVEL; + else if ( mra->ma_rule == slap_schema.si_mr_dnSubordinateMatch ) + scope = LDAP_SCOPE_SUBORDINATE; + else + goto base; /* scope = LDAP_SCOPE_BASE; */ +#if 0 + if ( scope > LDAP_SCOPE_BASE ) { + ei = NULL; + rc = mdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( ei ) + mdb_cache_entryinfo_unlock( ei ); + if ( rc == LDAP_SUCCESS ) { + int sc = op->ors_scope; + op->ors_scope = scope; + rc = mdb_dn2idl( op, rtxn, &mra->ma_value, ei, ids, + stack ); + op->ors_scope = sc; + } + return 0; + } +#endif + } + } + + MDB_IDL_ALL( ids ); + return 0; +} + +static int +list_candidates( + Operation *op, + MDB_txn *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + Filter *f; + + Debug( LDAP_DEBUG_FILTER, "=> mdb_list_candidates 0x%x\n", ftype ); + for ( f = flist; f != NULL; f = f->f_next ) { + /* ignore precomputed scopes */ + if ( f->f_choice == SLAPD_FILTER_COMPUTED && + f->f_result == LDAP_SUCCESS ) { + continue; + } + MDB_IDL_ZERO( save ); + rc = mdb_filter_candidates( op, rtxn, f, save, tmp, + save+MDB_idl_um_size ); + + if ( rc != 0 ) { + if ( ftype == LDAP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + + if ( ftype == LDAP_FILTER_AND ) { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_intersection( ids, save ); + } + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + MDB_IDL_CPY( ids, save ); + } else { + mdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_list_candidates: undefined rc=%d\n", + rc ); + } + + return rc; +} + +static int +presence_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeDescription *desc, + ID *ids ) +{ + MDB_dbi dbi; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_presence_candidates (%s)\n", + desc->ad_cname.bv_val ); + + MDB_IDL_ALL( ids ); + + if( desc == slap_schema.si_ad_objectClass ) { + return 0; + } + + rc = mdb_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT, + &dbi, &mask, &prefix ); + + if( rc == LDAP_INAPPROPRIATE_MATCHING ) { + /* not indexed */ + Debug( LDAP_DEBUG_FILTER, + "<= mdb_presence_candidates: (%s) not indexed\n", + desc->ad_cname.bv_val ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: (%s) index_param " + "returned=%d\n", + desc->ad_cname.bv_val, rc ); + return 0; + } + + if( prefix.bv_val == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: (%s) no prefix\n", + desc->ad_cname.bv_val ); + return -1; + } + + rc = mdb_key_read( op->o_bd, rtxn, dbi, &prefix, ids, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_presense_candidates: (%s) " + "key read failed (%d)\n", + desc->ad_cname.bv_val, rc ); + goto done; + } + + Debug(LDAP_DEBUG_TRACE, + "<= mdb_presence_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + +done: + return rc; +} + +static int +equality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_equality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val ); + + if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + ID id; + rc = mdb_dn2id( op, rtxn, NULL, &ava->aa_value, &id, NULL, NULL, NULL ); + if ( rc == LDAP_SUCCESS ) { + /* exactly one ID can match */ + ids[0] = 1; + ids[1] = id; + } + if ( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + } + return rc; + } + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_equality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_equality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= mdb_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + + +static int +approx_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_approx_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_approx_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_approx_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_approx; + if( !mr ) { + /* no approx matching rule, try equality matching rule */ + mr = ava->aa_desc->ad_type->sat_equality; + } + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_APPROX, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) no keys (%s)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_approx_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= mdb_approx_candidates %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +substring_candidates( + Operation *op, + MDB_txn *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ) +{ + MDB_dbi dbi; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_substring_candidates (%s)\n", + sub->sa_desc->ad_cname.bv_val ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_substring_candidates: (%s) not indexed\n", + sub->sa_desc->ad_cname.bv_val ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_substring_candidates: (%s) " + "index_param failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc ); + return 0; + } + + mr = sub->sa_desc->ad_type->sat_substr; + + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_SUBSTRINGS, + mask, + sub->sa_desc->ad_type->sat_syntax, + mr, + &prefix, + sub, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) " + "MR filter failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (0x%04lx) no keys (%s)\n", + mask, sub->sa_desc->ad_cname.bv_val ); + return 0; + } + + for ( i= 0; keys[i].bv_val != NULL; i++ ) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 ); + + if( rc == MDB_NOTFOUND ) { + MDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) " + "key read failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_substring_candidates: (%s) NULL\n", + sub->sa_desc->ad_cname.bv_val ); + MDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + MDB_IDL_CPY( ids, tmp ); + } else { + mdb_idl_intersection( ids, tmp ); + } + + if( MDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= mdb_substring_candidates: %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +inequality_candidates( + Operation *op, + MDB_txn *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ) +{ + MDB_dbi dbi; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + MDB_cursor *cursor = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_inequality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val ); + + MDB_IDL_ALL( ids ); + + rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &dbi, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_FILTER, + "<= mdb_inequality_candidates: (%s) not indexed\n", + ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "<= mdb_inequality_candidates: (%s) " + "index_param failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + mr = ava->aa_desc->ad_type->sat_equality; + if( !mr ) { + return 0; + } + + if( !mr->smr_filter ) { + return 0; + } + + rc = (mr->smr_filter)( + LDAP_FILTER_EQUALITY, + mask, + ava->aa_desc->ad_type->sat_syntax, + mr, + &prefix, + &ava->aa_value, + &keys, op->o_tmpmemctx ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s, %s) " + "MR filter failed (%d)\n", + prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc ); + return 0; + } + + if( keys == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val ); + return 0; + } + + MDB_IDL_ZERO( ids ); + while(1) { + rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[0], tmp, &cursor, gtorlt ); + + if( rc == MDB_NOTFOUND ) { + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc ); + break; + } + + if( MDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val ); + break; + } + + mdb_idl_union( ids, tmp ); + + if( op->ors_limit && op->ors_limit->lms_s_unchecked != -1 && + MDB_IDL_N( ids ) >= (unsigned) op->ors_limit->lms_s_unchecked ) { + mdb_cursor_close( cursor ); + break; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= mdb_inequality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + return( rc ); +} diff --git a/servers/slapd/back-mdb/id2entry.c b/servers/slapd/back-mdb/id2entry.c new file mode 100644 index 0000000..aa6067a --- /dev/null +++ b/servers/slapd/back-mdb/id2entry.c @@ -0,0 +1,1151 @@ +/* id2entry.c - routines to deal with the id2entry database */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "back-mdb.h" + +typedef struct Ecount { + ber_len_t len; /* total entry size */ + ber_len_t dlen; /* contiguous data size */ + int nattrs; + int nvals; + int offset; + Attribute *multi; +} Ecount; + +static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e, + Ecount *eh); +static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data, + Ecount *ec); +static Entry *mdb_entry_alloc( Operation *op, int nattrs, int nvals ); + +#define ID2VKSZ (sizeof(ID)+2) + +int +mdb_id2v_compare( + const MDB_val *usrkey, + const MDB_val *curkey +) +{ + unsigned short *uv, *cv; + ID ui, ci; + int rc; + + memcpy(&ui, usrkey->mv_data, sizeof(ID)); + memcpy(&ci, curkey->mv_data, sizeof(ID)); + if (ui < ci) + return -1; + if (ui > ci) + return 1; + uv = usrkey->mv_data; + cv = curkey->mv_data; + return uv[sizeof(ID)/2] - cv[sizeof(ID)/2]; +} + +/* usrkey[0] is the key in DB format, as described at mdb_mval_put. + * usrkey[1] is the value we'll actually match against. + * usrkey[2] is the attributeDescription for this value. + */ +int +mdb_id2v_dupsort( + const MDB_val *usrkey, + const MDB_val *curkey +) +{ + AttributeDescription *ad = usrkey[2].mv_data; + struct berval bv1, bv2; + int rc, match, olen; + unsigned short s; + char *ptr; + + ptr = curkey->mv_data + curkey->mv_size - 2; + memcpy(&s, ptr, 2); + bv2.bv_val = curkey->mv_data; + bv2.bv_len = curkey->mv_size - 3; + if (s) + bv2.bv_len -= (s+1); + + bv1.bv_val = usrkey[1].mv_data; + bv1.bv_len = usrkey[1].mv_size; + + if (ad && ad->ad_type->sat_equality) { + MatchingRule *mr = ad->ad_type->sat_equality; + rc = mr->smr_match(&match, SLAP_MR_EQUALITY + | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX + | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH + | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + ad->ad_type->sat_syntax, mr, &bv1, &bv2); + } else { + match = ber_bvcmp(&bv1, &bv2); + } + + return match; +} + +/* Values are stored as + * [normalized-value NUL ] original-value NUL 2-byte-len + * The trailing 2-byte-len is zero if there is no normalized value. + * Otherwise, it is the length of the original-value. + */ +int mdb_mval_put(Operation *op, MDB_cursor *mc, ID id, Attribute *a) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data[3]; + char *buf; + char ivk[ID2VKSZ]; + unsigned i; + unsigned short s; + int rc, len; + + memcpy(ivk, &id, sizeof(id)); + s = mdb->mi_adxs[a->a_desc->ad_index]; + memcpy(ivk+sizeof(ID), &s, 2); + key.mv_data = &ivk; + key.mv_size = sizeof(ivk); + if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass) + data[2].mv_data = NULL; + else + data[2].mv_data = a->a_desc; + + for (i=0; i<a->a_numvals; i++) { + len = a->a_nvals[i].bv_len + 1 + 2; + if (a->a_nvals != a->a_vals) { + len += a->a_vals[i].bv_len + 1; + data[1].mv_data = a->a_nvals[i].bv_val; + data[1].mv_size = a->a_nvals[i].bv_len; + } else { + data[1].mv_data = a->a_vals[i].bv_val; + data[1].mv_size = a->a_vals[i].bv_len; + } + data[0].mv_size = len; + buf = op->o_tmpalloc( len, op->o_tmpmemctx ); + data[0].mv_data = buf; + memcpy(buf, a->a_nvals[i].bv_val, a->a_nvals[i].bv_len); + buf += a->a_nvals[i].bv_len; + *buf++ = 0; + if (a->a_nvals != a->a_vals) { + s = a->a_vals[i].bv_len; + memcpy(buf, a->a_vals[i].bv_val, a->a_vals[i].bv_len); + buf += a->a_vals[i].bv_len; + *buf++ = 0; + memcpy(buf, &s, 2); + } else { + *buf++ = 0; + *buf++ = 0; + } + rc = mdb_cursor_put(mc, &key, data, 0); + op->o_tmpfree( data[0].mv_data, op->o_tmpmemctx ); + if (rc) + return rc; + } + return 0; +} + +int mdb_mval_del(Operation *op, MDB_cursor *mc, ID id, Attribute *a) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data[3]; + char *ptr; + char ivk[ID2VKSZ]; + unsigned i; + int rc; + unsigned short s; + + memcpy(ivk, &id, sizeof(id)); + s = mdb->mi_adxs[a->a_desc->ad_index]; + memcpy(ivk+sizeof(ID), &s, 2); + key.mv_data = &ivk; + key.mv_size = sizeof(ivk); + if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass) + data[2].mv_data = NULL; + else + data[2].mv_data = a->a_desc; + + if (a->a_numvals) { + for (i=0; i<a->a_numvals; i++) { + data[0].mv_data = a->a_nvals[i].bv_val; + data[0].mv_size = a->a_nvals[i].bv_len+1; + if (a->a_nvals != a->a_vals) { + data[1].mv_data = a->a_nvals[i].bv_val; + data[1].mv_size = a->a_nvals[i].bv_len; + } else { + data[1].mv_data = a->a_vals[i].bv_val; + data[1].mv_size = a->a_vals[i].bv_len; + } + rc = mdb_cursor_get(mc, &key, data, MDB_GET_BOTH_RANGE); + if (rc) + return rc; + rc = mdb_cursor_del(mc, 0); + if (rc) + return rc; + } + } else { + rc = mdb_cursor_get(mc, &key, data, MDB_SET); + if (rc) + return rc; + rc = mdb_cursor_del(mc, MDB_NODUPDATA); + } + return rc; +} + +static int mdb_mval_get(Operation *op, MDB_cursor *mc, ID id, Attribute *a, int have_nvals) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_val key, data[3]; + char *ptr; + char ivk[ID2VKSZ]; + unsigned i; + int rc = 0; + unsigned short s; + + memcpy(ivk, &id, sizeof(id)); + s = mdb->mi_adxs[a->a_desc->ad_index]; + memcpy(ivk+sizeof(ID), &s, 2); + key.mv_data = &ivk; + key.mv_size = sizeof(ivk); + + /* not needed */ + if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass) + data[2].mv_data = NULL; + else + data[2].mv_data = a->a_desc; + + if (have_nvals) + a->a_nvals = a->a_vals + a->a_numvals + 1; + else + a->a_nvals = a->a_vals; + for (i=0; i<a->a_numvals; i++) { + if (!i) + rc = mdb_cursor_get(mc, &key, data, MDB_SET); + else + rc = mdb_cursor_get(mc, &key, data, MDB_NEXT_DUP); + if (rc) + break; + ptr = (char*)data[0].mv_data + data[0].mv_size - 2; + memcpy(&s, ptr, 2); + if (have_nvals) { + a->a_nvals[i].bv_val = data[0].mv_data; + a->a_vals[i].bv_len = s; + a->a_vals[i].bv_val = ptr - a->a_vals[i].bv_len - 1; + a->a_nvals[i].bv_len = a->a_vals[i].bv_val - a->a_nvals[i].bv_val - 1; + } else { + assert(!s); + a->a_vals[i].bv_val = data[0].mv_data; + a->a_vals[i].bv_len = data[0].mv_size - 3; + } + } + a->a_numvals = i; + BER_BVZERO(&a->a_vals[i]); + if (have_nvals) { + BER_BVZERO(&a->a_nvals[i]); + } + return rc; +} + +#define ADD_FLAGS (MDB_NOOVERWRITE|MDB_APPEND) + +static int mdb_id2entry_put( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e, + int flag ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Ecount ec; + MDB_val key, data; + int rc, adding = flag, prev_ads = mdb->mi_numads; + + /* We only store rdns, and they go in the dn2id database. */ + + key.mv_data = &e->e_id; + key.mv_size = sizeof(ID); + + rc = mdb_entry_partsize( mdb, txn, e, &ec ); + if (rc) { + rc = LDAP_OTHER; + goto fail; + } + + flag |= MDB_RESERVE; + + if (e->e_id < mdb->mi_nextid) + flag &= ~MDB_APPEND; + + if (mdb->mi_maxentrysize && ec.len > mdb->mi_maxentrysize) { + rc = LDAP_ADMINLIMIT_EXCEEDED; + goto fail; + } + +again: + data.mv_size = ec.dlen; + if ( mc ) + rc = mdb_cursor_put( mc, &key, &data, flag ); + else + rc = mdb_put( txn, mdb->mi_id2entry, &key, &data, flag ); + if (rc == MDB_SUCCESS) { + rc = mdb_entry_encode( op, e, &data, &ec ); + if( rc != LDAP_SUCCESS ) + goto fail; + /* Handle adds of large multi-valued attrs here. + * Modifies handle them directly. + */ + if (adding && ec.multi) { + MDB_cursor *mvc; + Attribute *a; + rc = mdb_cursor_open( txn, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if( !rc ) { + for ( a = ec.multi; a; a=a->a_next ) { + if (!(a->a_flags & SLAP_ATTR_BIG_MULTI)) + continue; + rc = mdb_mval_put( op, mvc, e->e_id, a ); + if( rc ) + break; + } + mdb_cursor_close( mvc ); + } + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "mdb_id2entry_put: mdb_mval_put failed: %s(%d) \"%s\"\n", + mdb_strerror(rc), rc, + e->e_nname.bv_val ); + rc = LDAP_OTHER; + goto fail; + } + } + } + if (rc) { + /* Was there a hole from slapadd? */ + if ( (flag & MDB_NOOVERWRITE) && data.mv_size == 0 ) { + flag &= ~ADD_FLAGS; + goto again; + } + Debug( LDAP_DEBUG_ANY, + "mdb_id2entry_put: mdb_put failed: %s(%d) \"%s\"\n", + mdb_strerror(rc), rc, + e->e_nname.bv_val ); + if ( rc != MDB_KEYEXIST ) + rc = LDAP_OTHER; + } +fail: + if (rc) { + mdb_ad_unwind( mdb, prev_ads ); + } + return rc; +} + +/* + * This routine adds (or updates) an entry on disk. + */ +int mdb_id2entry_add( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e ) +{ + return mdb_id2entry_put(op, txn, mc, e, ADD_FLAGS); +} + +int mdb_id2entry_update( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + Entry *e ) +{ + return mdb_id2entry_put(op, txn, mc, e, 0); +} + +int mdb_id2edata( + Operation *op, + MDB_cursor *mc, + ID id, + MDB_val *data ) +{ + MDB_val key; + int rc; + + key.mv_data = &id; + key.mv_size = sizeof(ID); + + /* fetch it */ + rc = mdb_cursor_get( mc, &key, data, MDB_SET ); + /* stubs from missing parents - DB is actually invalid */ + if ( rc == MDB_SUCCESS && !data->mv_size ) + rc = MDB_NOTFOUND; + return rc; +} + +int mdb_id2entry( + Operation *op, + MDB_cursor *mc, + ID id, + Entry **e ) +{ + MDB_val key, data; + int rc = 0; + + *e = NULL; + + key.mv_data = &id; + key.mv_size = sizeof(ID); + + /* fetch it */ + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == MDB_NOTFOUND ) { + /* Looking for root entry on an empty-dn suffix? */ + if ( !id && BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] )) { + struct berval gluebv = BER_BVC("glue"); + Entry *r = mdb_entry_alloc(op, 2, 4); + Attribute *a = r->e_attrs; + struct berval *bptr; + + r->e_id = 0; + r->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END; + bptr = a->a_vals; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + a->a_desc = slap_schema.si_ad_objectClass; + a->a_nvals = a->a_vals; + a->a_numvals = 1; + *bptr++ = gluebv; + BER_BVZERO(bptr); + bptr++; + a->a_next = a+1; + a = a->a_next; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + a->a_desc = slap_schema.si_ad_structuralObjectClass; + a->a_vals = bptr; + a->a_nvals = a->a_vals; + a->a_numvals = 1; + *bptr++ = gluebv; + BER_BVZERO(bptr); + a->a_next = NULL; + *e = r; + return MDB_SUCCESS; + } + } + /* stubs from missing parents - DB is actually invalid */ + if ( rc == MDB_SUCCESS && !data.mv_size ) + rc = MDB_NOTFOUND; + if ( rc ) return rc; + + rc = mdb_entry_decode( op, mdb_cursor_txn( mc ), &data, id, e ); + if ( rc ) return rc; + + (*e)->e_id = id; + (*e)->e_name.bv_val = NULL; + (*e)->e_nname.bv_val = NULL; + + return rc; +} + +int mdb_id2entry_delete( + BackendDB *be, + MDB_txn *tid, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + MDB_dbi dbi = mdb->mi_id2entry; + MDB_val key; + MDB_cursor *mvc; + char kbuf[sizeof(ID) + sizeof(unsigned short)]; + int rc; + + memcpy( kbuf, &e->e_id, sizeof(ID) ); + memset( kbuf+sizeof(ID), 0, sizeof(unsigned short) ); + key.mv_data = kbuf; + key.mv_size = sizeof(kbuf); + + /* delete from database */ + rc = mdb_del( tid, dbi, &key, NULL ); + if (rc) + return rc; + rc = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if (rc) + return rc; + + rc = mdb_cursor_get( mvc, &key, NULL, MDB_SET_RANGE ); + if (rc) { + if (rc == MDB_NOTFOUND) + rc = MDB_SUCCESS; + return rc; + } + while (*(ID *)key.mv_data == e->e_id ) { + rc = mdb_cursor_del( mvc, MDB_NODUPDATA ); + if (rc) + return rc; + rc = mdb_cursor_get( mvc, &key, NULL, MDB_GET_CURRENT ); + if (rc) { + /* no record or DB is empty */ + if (rc == MDB_NOTFOUND || rc == EINVAL) + rc = MDB_SUCCESS; + break; + } + } + return rc; +} + +static Entry * mdb_entry_alloc( + Operation *op, + int nattrs, + int nvals ) +{ + Entry *e = op->o_tmpalloc( sizeof(Entry) + + nattrs * sizeof(Attribute) + + nvals * sizeof(struct berval), op->o_tmpmemctx ); + BER_BVZERO(&e->e_bv); + e->e_private = e; + if (nattrs) { + e->e_attrs = (Attribute *)(e+1); + e->e_attrs->a_vals = (struct berval *)(e->e_attrs+nattrs); + } else { + e->e_attrs = NULL; + } + + return e; +} + +int mdb_entry_return( + Operation *op, + Entry *e +) +{ + if ( !e ) + return 0; + if ( e->e_private ) { + if ( op->o_hdr && op->o_tmpmfuncs ) { + op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( e, op->o_tmpmemctx ); + } else { + ch_free( e->e_nname.bv_val ); + ch_free( e->e_name.bv_val ); + ch_free( e ); + } + } else { + entry_free( e ); + } + return 0; +} + +int mdb_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct mdb_op_info *moi = NULL; + + /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE, + SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */ + + int release = 1; + if ( slapMode & SLAP_SERVER_MODE ) { + OpExtra *oex; + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + release = 0; + if ( oex->oe_key == mdb ) { + mdb_entry_return( op, e ); + moi = (mdb_op_info *)oex; + /* If it was setup by entry_get we should probably free it */ + if (( moi->moi_flag & (MOI_FREEIT|MOI_KEEPER)) == MOI_FREEIT ) { + moi->moi_ref--; + if ( moi->moi_ref < 1 ) { + mdb_txn_reset( moi->moi_txn ); + moi->moi_ref = 0; + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + op->o_tmpfree( moi, op->o_tmpmemctx ); + } + } + break; + } + } + } + + if (release) + mdb_entry_return( op, e ); + + return 0; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int mdb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + struct mdb_op_info *moi = NULL; + MDB_txn *txn = NULL; + Entry *e = NULL; + int rc; + const char *at_name = at ? at->ad_cname.bv_val : "(null)"; + + Debug( LDAP_DEBUG_ARGS, + "=> mdb_entry_get: ndn: \"%s\"\n", ndn->bv_val ); + Debug( LDAP_DEBUG_ARGS, + "=> mdb_entry_get: oc: \"%s\", at: \"%s\"\n", + oc ? oc->soc_cname.bv_val : "(null)", at_name ); + + rc = mdb_opinfo_get( op, mdb, rw == 0, &moi ); + if ( rc ) + return LDAP_OTHER; + txn = moi->moi_txn; + + /* can we find entry */ + rc = mdb_dn2entry( op, txn, NULL, ndn, &e, NULL, 0 ); + switch( rc ) { + case MDB_NOTFOUND: + case 0: + break; + default: + return (rc != LDAP_BUSY) ? LDAP_OTHER : LDAP_BUSY; + } + if (e == NULL) { + Debug( LDAP_DEBUG_ACL, + "=> mdb_entry_get: cannot find entry: \"%s\"\n", + ndn->bv_val ); + rc = LDAP_NO_SUCH_OBJECT; + goto return_results; + } + + Debug( LDAP_DEBUG_ACL, + "=> mdb_entry_get: found entry: \"%s\"\n", + ndn->bv_val ); + + if ( oc && !is_entry_objectclass( e, oc, 0 )) { + Debug( LDAP_DEBUG_ACL, + "<= mdb_entry_get: failed to find objectClass %s\n", + oc->soc_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + + /* NOTE: attr_find() or attrs_find()? */ + if ( at && attr_find( e->e_attrs, at ) == NULL ) { + Debug( LDAP_DEBUG_ACL, + "<= mdb_entry_get: failed to find attribute %s\n", + at->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_results; + } + +return_results: + if( rc != LDAP_SUCCESS ) { + /* free entry */ + mdb_entry_release( op, e, rw ); + } else { + *ent = e; + } + + Debug( LDAP_DEBUG_TRACE, + "mdb_entry_get: rc=%d\n", + rc ); + return(rc); +} + +static void +mdb_reader_free( void *key, void *data ) +{ + MDB_txn *txn = data; + + if ( txn ) mdb_txn_abort( txn ); +} + +/* free up any keys used by the main thread */ +void +mdb_reader_flush( MDB_env *env ) +{ + void *data; + void *ctx = ldap_pvt_thread_pool_context(); + + if ( !ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) { + ldap_pvt_thread_pool_setkey( ctx, env, NULL, 0, NULL, NULL ); + mdb_reader_free( env, data ); + } +} + +extern MDB_txn *mdb_tool_txn; + +int +mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moip ) +{ + int rc, renew = 0; + void *data; + void *ctx; + mdb_op_info *moi = NULL; + OpExtra *oex; + + assert( op != NULL ); + + if ( !mdb || !moip ) return -1; + + /* If no op was provided, try to find the ctx anyway... */ + if ( op ) { + ctx = op->o_threadctx; + } else { + ctx = ldap_pvt_thread_pool_context(); + } + + if ( op ) { + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == mdb ) break; + } + moi = (mdb_op_info *)oex; + } + + if ( !moi ) { + moi = *moip; + + if ( !moi ) { + if ( op ) { + moi = op->o_tmpalloc(sizeof(struct mdb_op_info),op->o_tmpmemctx); + } else { + moi = ch_malloc(sizeof(mdb_op_info)); + } + moi->moi_flag = MOI_FREEIT; + *moip = moi; + } + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next ); + moi->moi_oe.oe_key = mdb; + moi->moi_ref = 0; + moi->moi_txn = NULL; + } + + if ( !rdonly ) { + /* This op started as a reader, but now wants to write. */ + if ( moi->moi_flag & MOI_READER ) { + moi = *moip; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next ); + } else { + /* This op is continuing an existing write txn */ + *moip = moi; + } + moi->moi_ref++; + if ( !moi->moi_txn ) { + if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) { + moi->moi_txn = mdb_tool_txn; + } else { + int flag = 0; +#ifdef SLAP_CONTROL_X_LAZY_COMMIT + if ( get_lazyCommit( op )) + flag |= MDB_NOMETASYNC; +#endif + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, flag, &moi->moi_txn ); + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc ); + } + return rc; + } + } + return 0; + } + + /* OK, this is a reader */ + if ( !moi->moi_txn ) { + if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) { + moi->moi_txn = mdb_tool_txn; + goto ok; + } + if ( !ctx ) { + /* Shouldn't happen unless we're single-threaded */ + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn ); + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc ); + } + return rc; + } + if ( ldap_pvt_thread_pool_getkey( ctx, mdb->mi_dbenv, &data, NULL ) ) { + int retried = 0; +retry: + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn ); + if (rc == MDB_READERS_FULL && !retried) { + int dead; + /* if any stale readers were cleared, a slot should be available */ + if (!mdb_reader_check( mdb->mi_dbenv, &dead ) && dead) { + retried = 1; + goto retry; + } + } + if (rc) { + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n", + mdb_strerror(rc), rc ); + return rc; + } + data = moi->moi_txn; + if ( ( rc = ldap_pvt_thread_pool_setkey( ctx, mdb->mi_dbenv, + data, mdb_reader_free, NULL, NULL ) ) ) { + mdb_txn_abort( moi->moi_txn ); + moi->moi_txn = NULL; + Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: thread_pool_setkey failed err (%d)\n", + rc ); + return rc; + } + } else { + moi->moi_txn = data; + renew = 1; + } + moi->moi_flag |= MOI_READER; + } +ok: + if ( moi->moi_ref < 1 ) { + moi->moi_ref = 0; + } + if ( renew ) { + rc = mdb_txn_renew( moi->moi_txn ); + assert(!rc); + } + moi->moi_ref++; + if ( *moip != moi ) + *moip = moi; + + return 0; +} + +int mdb_txn( Operation *op, int txnop, OpExtra **ptr ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + mdb_op_info **moip = (mdb_op_info **)ptr, *moi = *moip; + int rc; + + switch( txnop ) { + case SLAP_TXN_BEGIN: + rc = mdb_opinfo_get( op, mdb, 0, moip ); + if ( !rc ) { + moi = *moip; + moi->moi_flag |= MOI_KEEPER; + } + return rc; + case SLAP_TXN_COMMIT: + rc = mdb_txn_commit( moi->moi_txn ); + if ( rc ) + mdb->mi_numads = 0; + op->o_tmpfree( moi, op->o_tmpmemctx ); + return rc; + case SLAP_TXN_ABORT: + mdb->mi_numads = 0; + mdb_txn_abort( moi->moi_txn ); + op->o_tmpfree( moi, op->o_tmpmemctx ); + return 0; + } + return LDAP_OTHER; +} + +/* Count up the sizes of the components of an entry */ +static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e, + Ecount *eh) +{ + ber_len_t len, dlen; + int i, nat = 0, nval = 0, nnval = 0, doff = 0; + Attribute *a; + unsigned hi; + + eh->multi = NULL; + len = 4*sizeof(int); /* nattrs, nvals, ocflags, offset */ + dlen = len; + for (a=e->e_attrs; a; a=a->a_next) { + /* For AttributeDesc, we only store the attr index */ + nat++; + if (a->a_desc->ad_index >= MDB_MAXADS) { + Debug( LDAP_DEBUG_ANY, "mdb_entry_partsize: too many AttributeDescriptions used\n" ); + return LDAP_OTHER; + } + if (!mdb->mi_adxs[a->a_desc->ad_index]) { + int rc = mdb_ad_get(mdb, txn, a->a_desc); + if (rc) + return rc; + } + len += 2*sizeof(int); /* AD index, numvals */ + dlen += 2*sizeof(int); + nval += a->a_numvals + 1; /* empty berval at end */ + mdb_attr_multi_thresh( mdb, a->a_desc, &hi, NULL ); + if (a->a_numvals > hi) + a->a_flags |= SLAP_ATTR_BIG_MULTI; + if (a->a_flags & SLAP_ATTR_BIG_MULTI) + doff += a->a_numvals; + for (i=0; i<a->a_numvals; i++) { + int alen = a->a_vals[i].bv_len + 1 + sizeof(int); /* len */ + len += alen; + if (a->a_flags & SLAP_ATTR_BIG_MULTI) { + if (!eh->multi) + eh->multi = a; + } else { + dlen += alen; + } + } + if (a->a_nvals != a->a_vals) { + nval += a->a_numvals + 1; + nnval++; + if (a->a_flags & SLAP_ATTR_BIG_MULTI) + doff += a->a_numvals; + for (i=0; i<a->a_numvals; i++) { + int alen = a->a_nvals[i].bv_len + 1 + sizeof(int); + len += alen; + if (!(a->a_flags & SLAP_ATTR_BIG_MULTI)) + dlen += alen; + } + } + } + /* padding */ + dlen = (dlen + sizeof(ID)-1) & ~(sizeof(ID)-1); + eh->len = len; + eh->dlen = dlen; + eh->nattrs = nat; + eh->nvals = nval; + eh->offset = nat + nval - nnval - doff; + return 0; +} + +/* Flag bits for an encoded attribute */ +#define MDB_AT_SORTED (1U<<(sizeof(unsigned int)*CHAR_BIT-1)) + /* the values are in sorted order */ +#define MDB_AT_MULTI (1<<(sizeof(unsigned int)*CHAR_BIT-2)) + /* the values of this multi-valued attr are stored separately */ + +#define MDB_AT_NVALS (1U<<(sizeof(unsigned int)*CHAR_BIT-1)) + /* this attribute has normalized values */ + +/* Flatten an Entry into a buffer. The buffer starts with the count of the + * number of attributes in the entry, the total number of values in the + * entry, and the e_ocflags. It then contains a list of integers for each + * attribute. For each attribute the first integer gives the index of the + * matching AttributeDescription, followed by the number of values in the + * attribute. If the MDB_AT_SORTED bit of the attr index is set, the + * attribute's values are already sorted. If the MDB_AT_MULTI bit of the + * attr index is set, the values are stored separately. + * + * If the MDB_AT_NVALS bit of numvals is set, the attribute also has + * normalized values present. (Note - a_numvals is an unsigned int, so this + * means it's possible to receive an attribute that we can't encode due + * to size overflow. In practice, this should not be an issue.) + * + * Then the length of each value is listed. If there are normalized values, + * their lengths come next. This continues for each attribute. After all + * of the lengths for the last attribute, the actual values are copied, + * with a NUL terminator after each value. + * The buffer is padded to the sizeof(ID). The entire buffer size is + * precomputed so that a single malloc can be performed. + */ +static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data, Ecount *eh) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ber_len_t i; + Attribute *a; + unsigned char *ptr; + unsigned int *lp, l; + + Debug( LDAP_DEBUG_TRACE, "=> mdb_entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn ); + + /* make sure e->e_ocflags is set */ + if (is_entry_referral(e)) + ; /* empty */ + + lp = (unsigned int *)data->mv_data; + *lp++ = eh->nattrs; + *lp++ = eh->nvals; + *lp++ = (unsigned int)e->e_ocflags; + *lp++ = eh->offset; + ptr = (unsigned char *)(lp + eh->offset); + + for (a=e->e_attrs; a; a=a->a_next) { + if (!a->a_desc->ad_index) + return LDAP_UNDEFINED_TYPE; + l = mdb->mi_adxs[a->a_desc->ad_index]; + if (a->a_flags & SLAP_ATTR_BIG_MULTI) + l |= MDB_AT_MULTI; + if (a->a_flags & SLAP_ATTR_SORTED_VALS) + l |= MDB_AT_SORTED; + *lp++ = l; + l = a->a_numvals; + if (a->a_nvals != a->a_vals) + l |= MDB_AT_NVALS; + *lp++ = l; + if (a->a_flags & SLAP_ATTR_BIG_MULTI) { + continue; + } else { + if (a->a_vals) { + for (i=0; a->a_vals[i].bv_val; i++); + assert( i == a->a_numvals ); + for (i=0; i<a->a_numvals; i++) { + *lp++ = a->a_vals[i].bv_len; + memcpy(ptr, a->a_vals[i].bv_val, + a->a_vals[i].bv_len); + ptr += a->a_vals[i].bv_len; + *ptr++ = '\0'; + } + if (a->a_nvals != a->a_vals) { + for (i=0; i<a->a_numvals; i++) { + *lp++ = a->a_nvals[i].bv_len; + memcpy(ptr, a->a_nvals[i].bv_val, + a->a_nvals[i].bv_len); + ptr += a->a_nvals[i].bv_len; + *ptr++ = '\0'; + } + } + } + } + } + + Debug( LDAP_DEBUG_TRACE, "<= mdb_entry_encode(0x%08lx): %s\n", + (long) e->e_id, e->e_dn ); + + return 0; +} + +/* Retrieve an Entry that was stored using entry_encode above. + * + * Note: everything is stored in a single contiguous block, so + * you can not free individual attributes or names from this + * structure. Attempting to do so will likely corrupt memory. + */ + +int mdb_entry_decode(Operation *op, MDB_txn *txn, MDB_val *data, ID id, Entry **e) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int i, j, nattrs, nvals; + int rc; + Attribute *a; + Entry *x; + const char *text; + unsigned int *lp = (unsigned int *)data->mv_data; + unsigned char *ptr; + BerVarray bptr; + MDB_cursor *mvc = NULL; + + Debug( LDAP_DEBUG_TRACE, + "=> mdb_entry_decode:\n" ); + + nattrs = *lp++; + nvals = *lp++; + x = mdb_entry_alloc(op, nattrs, nvals); + x->e_ocflags = *lp++; + if (!nvals) { + goto done; + } + a = x->e_attrs; + bptr = a->a_vals; + i = *lp++; + ptr = (unsigned char *)(lp + i); + + for (;nattrs>0; nattrs--) { + int have_nval = 0, multi = 0; + a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS; + i = *lp++; + if (i & MDB_AT_SORTED) { + i ^= MDB_AT_SORTED; + a->a_flags |= SLAP_ATTR_SORTED_VALS; + } + if (i & MDB_AT_MULTI) { + i ^= MDB_AT_MULTI; + a->a_flags |= SLAP_ATTR_BIG_MULTI; + multi = 1; + } + if (i > mdb->mi_numads) { + rc = mdb_ad_read(mdb, txn); + if (rc) + goto leave; + if (i > mdb->mi_numads) { + Debug( LDAP_DEBUG_ANY, + "mdb_entry_decode: attribute index %d not recognized\n", + i ); + rc = LDAP_OTHER; + goto leave; + } + } + a->a_desc = mdb->mi_ads[i]; + a->a_numvals = *lp++; + if (a->a_numvals & MDB_AT_NVALS) { + a->a_numvals ^= MDB_AT_NVALS; + have_nval = 1; + } + a->a_vals = bptr; + if (multi) { + if (!mvc) { + rc = mdb_cursor_open(txn, mdb->mi_dbis[MDB_ID2VAL], &mvc); + if (rc) + goto leave; + } + i = a->a_numvals; + mdb_mval_get(op, mvc, id, a, have_nval); + bptr += i + 1; + if (have_nval) + bptr += i + 1; + } else { + for (i=0; i<a->a_numvals; i++) { + bptr->bv_len = *lp++; + bptr->bv_val = (char *)ptr; + ptr += bptr->bv_len+1; + bptr++; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + + if (have_nval) { + a->a_nvals = bptr; + for (i=0; i<a->a_numvals; i++) { + bptr->bv_len = *lp++; + bptr->bv_val = (char *)ptr; + ptr += bptr->bv_len+1; + bptr++; + } + bptr->bv_val = NULL; + bptr->bv_len = 0; + bptr++; + } else { + a->a_nvals = a->a_vals; + } + } + + /* FIXME: This is redundant once a sorted entry is saved into the DB */ + if (( a->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) + && !(a->a_flags & SLAP_ATTR_SORTED_VALS)) { + rc = slap_sort_vals( (Modifications *)a, &text, &j, NULL ); + if ( rc == LDAP_SUCCESS ) { + a->a_flags |= SLAP_ATTR_SORTED_VALS; + } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + /* should never happen */ + Debug( LDAP_DEBUG_ANY, + "mdb_entry_decode: attributeType %s value #%d provided more than once\n", + a->a_desc->ad_cname.bv_val, j ); + goto leave; + } + } + a->a_next = a+1; + a = a->a_next; + } + a[-1].a_next = NULL; +done: + Debug(LDAP_DEBUG_TRACE, "<= mdb_entry_decode\n" ); + *e = x; + rc = 0; + +leave: + if (mvc) + mdb_cursor_close(mvc); + return rc; +} diff --git a/servers/slapd/back-mdb/idl.c b/servers/slapd/back-mdb/idl.c new file mode 100644 index 0000000..c7ffbfa --- /dev/null +++ b/servers/slapd/back-mdb/idl.c @@ -0,0 +1,1293 @@ +/* idl.c - ldap id list handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" + +unsigned int MDB_idl_logn = MDB_IDL_LOGN; +unsigned int MDB_idl_db_size = 1 << MDB_IDL_LOGN; +unsigned int MDB_idl_um_size = 1 << (MDB_IDL_LOGN+1); +unsigned int MDB_idl_db_max = (1 << MDB_IDL_LOGN) - 1; +unsigned int MDB_idl_um_max = (1 << (MDB_IDL_LOGN+1)) - 1; + +#define IDL_MAX(x,y) ( (x) > (y) ? (x) : (y) ) +#define IDL_MIN(x,y) ( (x) < (y) ? (x) : (y) ) +#define IDL_CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) + +#if IDL_DEBUG > 0 +static void idl_check( ID *ids ) +{ + if( MDB_IDL_IS_RANGE( ids ) ) { + assert( MDB_IDL_RANGE_FIRST(ids) <= MDB_IDL_RANGE_LAST(ids) ); + } else { + ID i; + for( i=1; i < ids[0]; i++ ) { + assert( ids[i+1] > ids[i] ); + } + } +} + +#if IDL_DEBUG > 1 +static void idl_dump( ID *ids ) +{ + if( MDB_IDL_IS_RANGE( ids ) ) { + Debug( LDAP_DEBUG_ANY, + "IDL: range ( %ld - %ld )\n", + (long) MDB_IDL_RANGE_FIRST( ids ), + (long) MDB_IDL_RANGE_LAST( ids ) ); + + } else { + ID i; + Debug( LDAP_DEBUG_ANY, "IDL: size %ld", (long) ids[0] ); + + for( i=1; i<=ids[0]; i++ ) { + if( i % 16 == 1 ) { + Debug( LDAP_DEBUG_ANY, "\n" ); + } + Debug( LDAP_DEBUG_ANY, " %02lx", (long) ids[i] ); + } + + Debug( LDAP_DEBUG_ANY, "\n" ); + } + + idl_check( ids ); +} +#endif /* IDL_DEBUG > 1 */ +#endif /* IDL_DEBUG > 0 */ + +void mdb_idl_reset() +{ + if ( !MDB_idl_logn ) + MDB_idl_logn = MDB_IDL_LOGN; + + MDB_idl_db_size = 1 << MDB_idl_logn; + MDB_idl_um_size = 1 << (MDB_idl_logn+1); + MDB_idl_db_max = MDB_idl_db_size - 1; + MDB_idl_um_max = MDB_idl_um_size - 1; +} + +unsigned mdb_idl_search( ID *ids, ID id ) +{ +#define IDL_BINARY_SEARCH 1 +#ifdef IDL_BINARY_SEARCH + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first position greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0]; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor] ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; + +#else + /* (reverse) linear search */ + int i; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + for( i=ids[0]; i; i-- ) { + if( id > ids[i] ) { + break; + } + } + + return i+1; +#endif +} + +int mdb_idl_insert( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "insert: %04lx at %d\n", (long) id, x ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (MDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < MDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > MDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + + x = mdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0] && ids[x] == id ) { + /* duplicate */ + return -1; + } + + if ( ++ids[0] >= MDB_idl_db_max ) { + if( id < ids[1] ) { + ids[1] = id; + ids[2] = ids[ids[0]-1]; + } else if ( ids[ids[0]-1] < id ) { + ids[2] = id; + } else { + ids[2] = ids[ids[0]-1]; + } + ids[0] = NOID; + + } else { + /* insert id */ + AC_MEMCPY( &ids[x+1], &ids[x], (ids[0]-x) * sizeof(ID) ); + ids[x] = id; + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +static int mdb_idl_delete( ID *ids, ID id ) +{ + unsigned x; + +#if IDL_DEBUG > 1 + Debug( LDAP_DEBUG_ANY, "delete: %04lx at %d\n", (long) id, x ); + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + if (MDB_IDL_IS_RANGE( ids )) { + /* If deleting a range boundary, adjust */ + if ( ids[1] == id ) + ids[1]++; + else if ( ids[2] == id ) + ids[2]--; + /* deleting from inside a range is a no-op */ + + /* If the range has collapsed, re-adjust */ + if ( ids[1] > ids[2] ) + ids[0] = 0; + else if ( ids[1] == ids[2] ) + ids[1] = 1; + return 0; + } + + x = mdb_idl_search( ids, id ); + assert( x > 0 ); + + if( x <= 0 ) { + /* internal error */ + return -2; + } + + if( x > ids[0] || ids[x] != id ) { + /* not found */ + return -1; + + } else if ( --ids[0] == 0 ) { + if( x != 1 ) { + return -3; + } + + } else { + AC_MEMCPY( &ids[x], &ids[x+1], (1+ids[0]-x) * sizeof(ID) ); + } + +#if IDL_DEBUG > 1 + idl_dump( ids ); +#elif IDL_DEBUG > 0 + idl_check( ids ); +#endif + + return 0; +} + +static char * +mdb_show_key( + char *buf, + void *val, + size_t len ) +{ + if ( len == 4 /* LUTIL_HASH_BYTES */ ) { + unsigned char *c = val; + sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] ); + return buf; + } else { + return val; + } +} + +int +mdb_idl_fetch_key( + BackendDB *be, + MDB_txn *txn, + MDB_dbi dbi, + MDB_val *key, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag ) +{ + MDB_val data, key2, *kptr; + MDB_cursor *cursor; + ID *i; + size_t len; + int rc; + MDB_cursor_op opflag; + + char keybuf[16]; + + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_fetch_key: %s\n", + mdb_show_key( keybuf, key->mv_data, key->mv_size ) ); + + assert( ids != NULL ); + + if ( saved_cursor && *saved_cursor ) { + opflag = MDB_NEXT; + } else if ( get_flag == LDAP_FILTER_GE ) { + opflag = MDB_SET_RANGE; + } else if ( get_flag == LDAP_FILTER_LE ) { + opflag = MDB_FIRST; + } else { + opflag = MDB_SET; + } + + /* If we're not reusing an existing cursor, get a new one */ + if( opflag != MDB_NEXT ) { + rc = mdb_cursor_open( txn, dbi, &cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "cursor failed: %s (%d)\n", mdb_strerror(rc), rc ); + return rc; + } + } else { + cursor = *saved_cursor; + } + + /* If this is a LE lookup, save original key so we can determine + * when to stop. If this is a GE lookup, save the key since it + * will be overwritten. + */ + if ( get_flag == LDAP_FILTER_LE || get_flag == LDAP_FILTER_GE ) { + key2.mv_data = keybuf; + key2.mv_size = key->mv_size; + AC_MEMCPY( keybuf, key->mv_data, key->mv_size ); + kptr = &key2; + } else { + kptr = key; + } + len = key->mv_size; + rc = mdb_cursor_get( cursor, kptr, &data, opflag ); + + /* skip presence key on range inequality lookups */ + while (rc == 0 && kptr->mv_size != len) { + rc = mdb_cursor_get( cursor, kptr, &data, MDB_NEXT_NODUP ); + } + /* If we're doing a LE compare and the new key is greater than + * our search key, we're done + */ + if (rc == 0 && get_flag == LDAP_FILTER_LE && memcmp( kptr->mv_data, + key->mv_data, key->mv_size ) > 0 ) { + rc = MDB_NOTFOUND; + } + if (rc == 0) { + i = ids+1; + rc = mdb_cursor_get( cursor, key, &data, MDB_GET_MULTIPLE ); + while (rc == 0) { + memcpy( i, data.mv_data, data.mv_size ); + i += data.mv_size / sizeof(ID); + rc = mdb_cursor_get( cursor, key, &data, MDB_NEXT_MULTIPLE ); + } + if ( rc == MDB_NOTFOUND ) rc = 0; + ids[0] = i - &ids[1]; + /* On disk, a range is denoted by 0 in the first element */ + if (ids[1] == 0) { + if (ids[0] != MDB_IDL_RANGE_SIZE) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "range size mismatch: expected %d, got %ld\n", + MDB_IDL_RANGE_SIZE, ids[0] ); + mdb_cursor_close( cursor ); + return -1; + } + MDB_IDL_RANGE( ids, ids[2], ids[3] ); + } + data.mv_size = MDB_IDL_SIZEOF(ids); + } + + if ( saved_cursor && rc == 0 ) { + if ( !*saved_cursor ) + *saved_cursor = cursor; + } + else + mdb_cursor_close( cursor ); + + if( rc == MDB_NOTFOUND ) { + return rc; + + } else if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "get failed: %s (%d)\n", + mdb_strerror(rc), rc ); + return rc; + + } else if ( data.mv_size == 0 || data.mv_size % sizeof( ID ) ) { + /* size not multiple of ID size */ + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "odd size: expected %ld multiple, got %ld\n", + (long) sizeof( ID ), (long) data.mv_size ); + return -1; + + } else if ( data.mv_size != MDB_IDL_SIZEOF(ids) ) { + /* size mismatch */ + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: " + "get size mismatch: expected %ld, got %ld\n", + (long) ((1 + ids[0]) * sizeof( ID )), (long) data.mv_size ); + return -1; + } + + return rc; +} + +int +mdb_idl_insert_keys( + BackendDB *be, + MDB_cursor *cursor, + struct berval *keys, + ID id ) +{ + struct mdb_info *mdb = be->be_private; + MDB_val key, data; + ID lo, hi, *i; + char *err; + int rc = 0, k; + unsigned int flag = MDB_NODUPDATA; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_insert_keys: %lx %s\n", + (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ) ); + } + + assert( id != NOID ); + +#ifndef MISALIGNED_OK + if (keys[0].bv_len & ALIGNER) + kbuf[1] = 0; +#endif + for ( k=0; keys[k].bv_val; k++ ) { + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ +#ifndef MISALIGNED_OK + if (keys[k].bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len); + } else +#endif + { + key.mv_size = keys[k].bv_len; + key.mv_data = keys[k].bv_val; + } + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + err = "c_get"; + if ( rc == 0 ) { + i = data.mv_data; + memcpy(&lo, data.mv_data, sizeof(ID)); + if ( lo != 0 ) { + /* not a range, count the number of items */ + size_t count; + rc = mdb_cursor_count( cursor, &count ); + if ( rc != 0 ) { + err = "c_count"; + goto fail; + } + if ( count >= MDB_idl_db_max ) { + /* No room, convert to a range */ + lo = *i; + rc = mdb_cursor_get( cursor, &key, &data, MDB_LAST_DUP ); + if ( rc != 0 && rc != MDB_NOTFOUND ) { + err = "c_get last_dup"; + goto fail; + } + i = data.mv_data; + hi = *i; + /* Update hi/lo if needed */ + if ( id < lo ) { + lo = id; + } else if ( id > hi ) { + hi = id; + } + /* delete the old key */ + rc = mdb_cursor_del( cursor, MDB_NODUPDATA ); + if ( rc != 0 ) { + err = "c_del dups"; + goto fail; + } + /* Store the range */ + data.mv_size = sizeof(ID); + data.mv_data = &id; + id = 0; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put range"; + goto fail; + } + id = lo; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put lo"; + goto fail; + } + id = hi; + rc = mdb_cursor_put( cursor, &key, &data, 0 ); + if ( rc != 0 ) { + err = "c_put hi"; + goto fail; + } + } else { + /* There's room, just store it */ + if (id == mdb->mi_nextid) + flag |= MDB_APPENDDUP; + goto put1; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + lo = i[1]; + hi = i[2]; + if ( id < lo || id > hi ) { + /* position on lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + if ( id > hi ) { + /* position on hi */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + } + data.mv_size = sizeof(ID); + data.mv_data = &id; + /* Replace the current lo/hi */ + rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } else if ( rc == MDB_NOTFOUND ) { + flag &= ~MDB_APPENDDUP; +put1: data.mv_data = &id; + data.mv_size = sizeof(ID); + rc = mdb_cursor_put( cursor, &key, &data, flag ); + /* Don't worry if it's already there */ + if ( rc == MDB_KEYEXIST ) + rc = 0; + if ( rc ) { + err = "c_put id"; + goto fail; + } + } else { + /* initial c_get failed, nothing was done */ +fail: + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_insert_keys: " + "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc ); + break; + } + } + return rc; +} + +int +mdb_idl_delete_keys( + BackendDB *be, + MDB_cursor *cursor, + struct berval *keys, + ID id ) +{ + int rc = 0, k; + MDB_val key, data; + ID lo, hi, tmp, *i; + char *err; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "mdb_idl_delete_keys: %lx %s\n", + (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ) ); + } + assert( id != NOID ); + +#ifndef MISALIGNED_OK + if (keys[0].bv_len & ALIGNER) + kbuf[1] = 0; +#endif + for ( k=0; keys[k].bv_val; k++) { + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ +#ifndef MISALIGNED_OK + if (keys[k].bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len); + } else +#endif + { + key.mv_size = keys[k].bv_len; + key.mv_data = keys[k].bv_val; + } + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + err = "c_get"; + if ( rc == 0 ) { + memcpy( &tmp, data.mv_data, sizeof(ID) ); + i = data.mv_data; + if ( tmp != 0 ) { + /* Not a range, just delete it */ + data.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH ); + if ( rc != 0 ) { + err = "c_get id"; + goto fail; + } + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del id"; + goto fail; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + lo = i[1]; + hi = i[2]; + if ( id == lo || id == hi ) { + ID lo2 = lo, hi2 = hi; + if ( id == lo ) { + lo2++; + } else if ( id == hi ) { + hi2--; + } + if ( lo2 >= hi2 ) { + /* The range has collapsed... */ + /* delete the range marker */ + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del dup1"; + goto fail; + } + /* skip past deleted marker */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get dup1"; + goto fail; + } + /* delete the requested id */ + if ( id == hi ) { + /* skip lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get dup2"; + goto fail; + } + } + rc = mdb_cursor_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del dup2"; + goto fail; + } + } else { + /* position on lo */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + if ( id == lo ) + data.mv_data = &lo2; + else { + /* position on hi */ + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP ); + data.mv_data = &hi2; + } + /* Replace the current lo/hi */ + data.mv_size = sizeof(ID); + rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } + } else { + /* initial c_get failed, nothing was done */ +fail: + if ( rc == MDB_NOTFOUND ) + rc = 0; + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "=> mdb_idl_delete_key: " + "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc ); + break; + } + } + } + return rc; +} + + +/* + * idl_intersection - return a = a intersection b + */ +int +mdb_idl_intersection( + ID *a, + ID *b ) +{ + ID ida, idb; + ID idmax, idmin; + ID cursora = 0, cursorb = 0, cursorc; + int swap = 0; + + if ( MDB_IDL_IS_ZERO( a ) || MDB_IDL_IS_ZERO( b ) ) { + a[0] = 0; + return 0; + } + + idmin = IDL_MAX( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) ); + idmax = IDL_MIN( MDB_IDL_LAST(a), MDB_IDL_LAST(b) ); + if ( idmin > idmax ) { + a[0] = 0; + return 0; + } else if ( idmin == idmax ) { + a[0] = 1; + a[1] = idmin; + return 0; + } + + if ( MDB_IDL_IS_RANGE( a ) ) { + if ( MDB_IDL_IS_RANGE(b) ) { + /* If both are ranges, just shrink the boundaries */ + a[1] = idmin; + a[2] = idmax; + return 0; + } else { + /* Else swap so that b is the range, a is a list */ + ID *tmp = a; + a = b; + b = tmp; + swap = 1; + } + } + + /* If a range completely covers the list, the result is + * just the list. + */ + if ( MDB_IDL_IS_RANGE( b ) + && MDB_IDL_RANGE_FIRST( b ) <= MDB_IDL_FIRST( a ) + && MDB_IDL_RANGE_LAST( b ) >= MDB_IDL_LLAST( a ) ) { + goto done; + } + + /* Fine, do the intersection one element at a time. + * First advance to idmin in both IDLs. + */ + cursora = cursorb = idmin; + ida = mdb_idl_first( a, &cursora ); + idb = mdb_idl_first( b, &cursorb ); + cursorc = 0; + + while( ida <= idmax || idb <= idmax ) { + if( ida == idb ) { + a[++cursorc] = ida; + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } else if ( ida < idb ) { + ida = mdb_idl_next( a, &cursora ); + } else { + idb = mdb_idl_next( b, &cursorb ); + } + } + a[0] = cursorc; +done: + if (swap) + MDB_IDL_CPY( b, a ); + + return 0; +} + + +/* + * idl_union - return a = a union b + */ +int +mdb_idl_union( + ID *a, + ID *b ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0, cursorc; + + if ( MDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( MDB_IDL_IS_ZERO( a ) ) { + MDB_IDL_CPY( a, b ); + return 0; + } + + if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) ) { +over: ida = IDL_MIN( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) ); + idb = IDL_MAX( MDB_IDL_LAST(a), MDB_IDL_LAST(b) ); + a[0] = NOID; + a[1] = ida; + a[2] = idb; + return 0; + } + + ida = mdb_idl_first( a, &cursora ); + idb = mdb_idl_first( b, &cursorb ); + + cursorc = b[0]; + + /* The distinct elements of a are cat'd to b */ + while( ida != NOID || idb != NOID ) { + if ( ida < idb ) { + if( ++cursorc > MDB_idl_um_max ) { + goto over; + } + b[cursorc] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else { + if ( ida == idb ) + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } + } + + /* b is copied back to a in sorted order */ + a[0] = cursorc; + cursora = 1; + cursorb = 1; + cursorc = b[0]+1; + while (cursorb <= b[0] || cursorc <= a[0]) { + if (cursorc > a[0]) + idb = NOID; + else + idb = b[cursorc]; + if (cursorb <= b[0] && b[cursorb] < idb) + a[cursora++] = b[cursorb++]; + else { + a[cursora++] = idb; + cursorc++; + } + } + + return 0; +} + + +#if 0 +/* + * mdb_idl_notin - return a intersection ~b (or a minus b) + */ +int +mdb_idl_notin( + ID *a, + ID *b, + ID *ids ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0; + + if( MDB_IDL_IS_ZERO( a ) || + MDB_IDL_IS_ZERO( b ) || + MDB_IDL_IS_RANGE( b ) ) + { + MDB_IDL_CPY( ids, a ); + return 0; + } + + if( MDB_IDL_IS_RANGE( a ) ) { + MDB_IDL_CPY( ids, a ); + return 0; + } + + ida = mdb_idl_first( a, &cursora ), + idb = mdb_idl_first( b, &cursorb ); + + ids[0] = 0; + + while( ida != NOID ) { + if ( idb == NOID ) { + /* we could shortcut this */ + ids[++ids[0]] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else if ( ida < idb ) { + ids[++ids[0]] = ida; + ida = mdb_idl_next( a, &cursora ); + + } else if ( ida > idb ) { + idb = mdb_idl_next( b, &cursorb ); + + } else { + ida = mdb_idl_next( a, &cursora ); + idb = mdb_idl_next( b, &cursorb ); + } + } + + return 0; +} +#endif + +ID mdb_idl_first( ID *ids, ID *cursor ) +{ + ID pos; + + if ( ids[0] == 0 ) { + *cursor = NOID; + return NOID; + } + + if ( MDB_IDL_IS_RANGE( ids ) ) { + if( *cursor < ids[1] ) { + *cursor = ids[1]; + } + return *cursor; + } + + if ( *cursor == 0 ) + pos = 1; + else + pos = mdb_idl_search( ids, *cursor ); + + if( pos > ids[0] ) { + return NOID; + } + + *cursor = pos; + return ids[pos]; +} + +ID mdb_idl_next( ID *ids, ID *cursor ) +{ + if ( MDB_IDL_IS_RANGE( ids ) ) { + if( ids[2] < ++(*cursor) ) { + return NOID; + } + return *cursor; + } + + if ( ++(*cursor) <= ids[0] ) { + return ids[*cursor]; + } + + return NOID; +} + +/* Add one ID to an unsorted list. We ensure that the first element is the + * minimum and the last element is the maximum, for fast range compaction. + * this means IDLs up to length 3 are always sorted... + */ +int mdb_idl_append_one( ID *ids, ID id ) +{ + if (MDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < MDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > MDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + if ( ids[0] ) { + ID tmp; + + if (id < ids[1]) { + tmp = ids[1]; + ids[1] = id; + id = tmp; + } + if ( ids[0] > 1 && id < ids[ids[0]] ) { + tmp = ids[ids[0]]; + ids[ids[0]] = id; + id = tmp; + } + } + ids[0]++; + if ( ids[0] >= MDB_idl_um_max ) { + ids[0] = NOID; + ids[2] = id; + } else { + ids[ids[0]] = id; + } + return 0; +} + +/* Append sorted list b to sorted list a. The result is unsorted but + * a[1] is the min of the result and a[a[0]] is the max. + */ +int mdb_idl_append( ID *a, ID *b ) +{ + ID ida, idb, tmp, swap = 0; + + if ( MDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( MDB_IDL_IS_ZERO( a ) ) { + MDB_IDL_CPY( a, b ); + return 0; + } + + ida = MDB_IDL_LAST( a ); + idb = MDB_IDL_LAST( b ); + if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) || + a[0] + b[0] >= MDB_idl_um_max ) { + a[2] = IDL_MAX( ida, idb ); + a[1] = IDL_MIN( a[1], b[1] ); + a[0] = NOID; + return 0; + } + + if ( b[0] > 1 && ida > idb ) { + swap = idb; + a[a[0]] = idb; + b[b[0]] = ida; + } + + if ( b[1] < a[1] ) { + tmp = a[1]; + a[1] = b[1]; + } else { + tmp = b[1]; + } + a[0]++; + a[a[0]] = tmp; + + if ( b[0] > 1 ) { + int i = b[0] - 1; + AC_MEMCPY(a+a[0]+1, b+2, i * sizeof(ID)); + a[0] += i; + } + if ( swap ) { + b[b[0]] = swap; + } + return 0; +} + +#if 1 + +/* Quicksort + Insertion sort for small arrays */ + +#define SMALL 8 +#define SWAP(a,b) itmp=(a);(a)=(b);(b)=itmp + +void +mdb_idl_sort( ID *ids, ID *tmp ) +{ + int *istack = (int *)tmp; /* Private stack, not used by caller */ + int i,j,k,l,ir,jstack; + ID a, itmp; + + if ( MDB_IDL_IS_RANGE( ids )) + return; + + ir = ids[0]; + l = 1; + jstack = 0; + for(;;) { + if (ir - l < SMALL) { /* Insertion sort */ + for (j=l+1;j<=ir;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + if (jstack == 0) break; + ir = istack[jstack--]; + l = istack[jstack--]; + } else { + k = (l + ir) >> 1; /* Choose median of left, center, right */ + SWAP(ids[k], ids[l+1]); + if (ids[l] > ids[ir]) { + SWAP(ids[l], ids[ir]); + } + if (ids[l+1] > ids[ir]) { + SWAP(ids[l+1], ids[ir]); + } + if (ids[l] > ids[l+1]) { + SWAP(ids[l], ids[l+1]); + } + i = l+1; + j = ir; + a = ids[l+1]; + for(;;) { + do i++; while(ids[i] < a); + do j--; while(ids[j] > a); + if (j < i) break; + SWAP(ids[i],ids[j]); + } + ids[l+1] = ids[j]; + ids[j] = a; + jstack += 2; + if (ir-i+1 >= j-l) { + istack[jstack] = ir; + istack[jstack-1] = i; + ir = j-1; + } else { + istack[jstack] = j-1; + istack[jstack-1] = l; + l = i; + } + } + } +} + +#else + +/* 8 bit Radix sort + insertion sort + * + * based on code from http://www.cubic.org/docs/radix.htm + * with improvements by ebackes@symas.com and hyc@symas.com + * + * This code is O(n) but has a relatively high constant factor. For lists + * up to ~50 Quicksort is slightly faster; up to ~100 they are even. + * Much faster than quicksort for lists longer than ~100. Insertion + * sort is actually superior for lists <50. + */ + +#define BUCKETS (1<<8) +#define SMALL 50 + +void +mdb_idl_sort( ID *ids, ID *tmp ) +{ + int count, soft_limit, phase = 0, size = ids[0]; + ID *idls[2]; + unsigned char *maxv = (unsigned char *)&ids[size]; + + if ( MDB_IDL_IS_RANGE( ids )) + return; + + /* Use insertion sort for small lists */ + if ( size <= SMALL ) { + int i,j; + ID a; + + for (j=1;j<=size;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] <= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + return; + } + + tmp[0] = size; + idls[0] = ids; + idls[1] = tmp; + +#if BYTE_ORDER == BIG_ENDIAN + for (soft_limit = 0; !maxv[soft_limit]; soft_limit++); +#else + for (soft_limit = sizeof(ID)-1; !maxv[soft_limit]; soft_limit--); +#endif + + for ( +#if BYTE_ORDER == BIG_ENDIAN + count = sizeof(ID)-1; count >= soft_limit; --count +#else + count = 0; count <= soft_limit; ++count +#endif + ) { + unsigned int num[BUCKETS], * np, n, sum; + int i; + ID *sp, *source, *dest; + unsigned char *bp, *source_start; + + source = idls[phase]+1; + dest = idls[phase^1]+1; + source_start = ((unsigned char *) source) + count; + + np = num; + for ( i = BUCKETS; i > 0; --i ) *np++ = 0; + + /* count occurrences of every byte value */ + bp = source_start; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) + num[*bp]++; + + /* transform count into index by summing elements and storing + * into same array + */ + sum = 0; + np = num; + for ( i = BUCKETS; i > 0; --i ) { + n = *np; + *np++ = sum; + sum += n; + } + + /* fill dest with the right values in the right place */ + bp = source_start; + sp = source; + for ( i = size; i > 0; --i, bp += sizeof(ID) ) { + np = num + *bp; + dest[*np] = *sp++; + ++(*np); + } + phase ^= 1; + } + + /* copy back from temp if needed */ + if ( phase ) { + ids++; tmp++; + for ( count = 0; count < size; ++count ) + *ids++ = *tmp++; + } +} +#endif /* Quick vs Radix */ + +unsigned mdb_id2l_search( ID2L ids, ID id ) +{ + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first position greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0].mid; + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor].mid ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; +} + +int mdb_id2l_insert( ID2L ids, ID2 *id ) +{ + unsigned x, i; + + x = mdb_id2l_search( ids, id->mid ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0].mid && ids[x].mid == id->mid ) { + /* duplicate */ + return -1; + } + + if ( ids[0].mid >= MDB_idl_um_max ) { + /* too big */ + return -2; + + } else { + /* insert id */ + ids[0].mid++; + for (i=ids[0].mid; i>x; i--) + ids[i] = ids[i-1]; + ids[x] = *id; + } + + return 0; +} diff --git a/servers/slapd/back-mdb/idl.h b/servers/slapd/back-mdb/idl.h new file mode 100644 index 0000000..1405571 --- /dev/null +++ b/servers/slapd/back-mdb/idl.h @@ -0,0 +1,118 @@ +/* idl.h - ldap mdb back-end ID list header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _MDB_IDL_H_ +#define _MDB_IDL_H_ + +/* IDL sizes - likely should be even bigger + * limiting factors: sizeof(ID), thread stack size + */ +#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +extern unsigned int MDB_idl_logn; +extern unsigned int MDB_idl_db_size; +extern unsigned int MDB_idl_um_size; +extern unsigned int MDB_idl_db_max; +extern unsigned int MDB_idl_um_max; + +#define MDB_IDL_IS_RANGE(ids) ((ids)[0] == NOID) +#define MDB_IDL_RANGE_SIZE (3) +#define MDB_IDL_RANGE_SIZEOF (MDB_IDL_RANGE_SIZE * sizeof(ID)) +#define MDB_IDL_SIZEOF(ids) ((MDB_IDL_IS_RANGE(ids) \ + ? MDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID)) + +#define MDB_IDL_RANGE_FIRST(ids) ((ids)[1]) +#define MDB_IDL_RANGE_LAST(ids) ((ids)[2]) + +#define MDB_IDL_RANGE( ids, f, l ) \ + do { \ + (ids)[0] = NOID; \ + (ids)[1] = (f); \ + (ids)[2] = (l); \ + } while(0) + +#define MDB_IDL_ZERO(ids) \ + do { \ + (ids)[0] = 0; \ + (ids)[1] = 0; \ + (ids)[2] = 0; \ + } while(0) + +#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 ) +#define MDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \ + && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] ) + +#define MDB_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, MDB_IDL_SIZEOF( src ) )) + +#define MDB_IDL_ID( mdb, ids, id ) MDB_IDL_RANGE( ids, id, NOID ) +#define MDB_IDL_ALL( ids ) MDB_IDL_RANGE( ids, 1, NOID ) + +#define MDB_IDL_FIRST( ids ) ( (ids)[1] ) +#define MDB_IDL_LLAST( ids ) ( (ids)[(ids)[0]] ) +#define MDB_IDL_LAST( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? (ids)[2] : (ids)[(ids)[0]] ) + +#define MDB_IDL_N( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? ((ids)[2]-(ids)[1])+1 : (ids)[0] ) + + /** An ID2 is an ID/value pair. + */ +typedef struct ID2 { + ID mid; /**< The ID */ + MDB_val mval; /**< The value */ +} ID2; + + /** An ID2L is an ID2 List, a sorted array of ID2s. + * The first element's \b mid member is a count of how many actual + * elements are in the array. The \b mptr member of the first element is unused. + * The array is sorted in ascending order by \b mid. + */ +typedef ID2 *ID2L; + +typedef struct IdScopes { + MDB_txn *mt; + MDB_cursor *mc; + ID id; + ID2L scopes; + ID2L sctmp; + int numrdns; + int nscope; + int oscope; + struct berval rdns[MAXRDNS]; + struct berval nrdns[MAXRDNS]; +} IdScopes; + +LDAP_BEGIN_DECL + /** Reset IDL params after changing logn */ +void mdb_idl_reset(); + + + /** Search for an ID in an ID2L. + * @param[in] ids The ID2L to search. + * @param[in] id The ID to search for. + * @return The index of the first ID2 whose \b mid member is greater than or equal to \b id. + */ +unsigned mdb_id2l_search( ID2L ids, ID id ); + + + /** Insert an ID2 into a ID2L. + * @param[in,out] ids The ID2L to insert into. + * @param[in] id The ID2 to insert. + * @return 0 on success, -1 if the ID was already present in the MIDL2. + */ +int mdb_id2l_insert( ID2L ids, ID2 *id ); +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-mdb/index.c b/servers/slapd/back-mdb/index.c new file mode 100644 index 0000000..fa9006c --- /dev/null +++ b/servers/slapd/back-mdb/index.c @@ -0,0 +1,577 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" +#include "lutil_hash.h" + +static char presence_keyval[] = {0,0,0,0,0}; +static struct berval presence_key[2] = {BER_BVC(presence_keyval), BER_BVNULL}; + +AttrInfo *mdb_index_mask( + Backend *be, + AttributeDescription *desc, + struct berval *atname ) +{ + AttributeType *at; + AttrInfo *ai = mdb_attr_mask( be->be_private, desc ); + + if( ai ) { + *atname = desc->ad_cname; + return ai; + } + + /* If there is a tagging option, did we ever index the base + * type? If so, check for mask, otherwise it's not there. + */ + if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) { + /* has tagging option */ + ai = mdb_attr_mask( be->be_private, desc->ad_type->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOTAGS ) ) { + *atname = desc->ad_type->sat_cname; + return ai; + } + } + + /* see if supertype defined mask for its subtypes */ + for( at = desc->ad_type; at != NULL ; at = at->sat_sup ) { + /* If no AD, we've never indexed this type */ + if ( !at->sat_ad ) continue; + + ai = mdb_attr_mask( be->be_private, at->sat_ad ); + + if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOSUBTYPES ) ) { + *atname = at->sat_cname; + return ai; + } + } + + return 0; +} + +/* This function is only called when evaluating search filters. + */ +int mdb_index_param( + Backend *be, + AttributeDescription *desc, + int ftype, + MDB_dbi *dbip, + slap_mask_t *maskp, + struct berval *prefixp ) +{ + AttrInfo *ai; + slap_mask_t mask, type = 0; + + ai = mdb_index_mask( be, desc, prefixp ); + + if ( !ai ) { +#ifdef MDB_MONITOR_IDX + switch ( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + break; + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + break; + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + break; + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + break; + default: + return LDAP_INAPPROPRIATE_MATCHING; + } + mdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* MDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + } + mask = ai->ai_indexmask; + + switch( ftype ) { + case LDAP_FILTER_PRESENT: + type = SLAP_INDEX_PRESENT; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + *prefixp = presence_key[0]; + goto done; + } + break; + + case LDAP_FILTER_APPROX: + type = SLAP_INDEX_APPROX; + if ( desc->ad_type->sat_approx ) { + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + goto done; + } + break; + } + + /* Use EQUALITY rule and index for approximate match */ + /* fall thru */ + + case LDAP_FILTER_EQUALITY: + type = SLAP_INDEX_EQUALITY; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + goto done; + } + break; + + case LDAP_FILTER_SUBSTRINGS: + type = SLAP_INDEX_SUBSTR; + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + goto done; + } + break; + + default: + return LDAP_OTHER; + } + +#ifdef MDB_MONITOR_IDX + mdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* MDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + +done: + *dbip = ai->ai_dbi; + *maskp = mask; + return LDAP_SUCCESS; +} + +static int indexer( + Operation *op, + MDB_txn *txn, + struct mdb_attrinfo *ai, + AttributeDescription *ad, + struct berval *atname, + BerVarray vals, + ID id, + int opid, + slap_mask_t mask ) +{ + int rc; + struct berval *keys; + MDB_cursor *mc = ai->ai_cursor; + mdb_idl_keyfunc *keyfunc; + char *err; + + assert( mask != 0 ); + + if ( !mc ) { + err = "c_open"; + rc = mdb_cursor_open( txn, ai->ai_dbi, &mc ); + if ( rc ) goto done; + if ( slapMode & SLAP_TOOL_QUICK ) + ai->ai_cursor = mc; + } + + if ( opid == SLAP_INDEX_ADD_OP ) { +#ifdef MDB_TOOL_IDL_CACHING + if (( slapMode & SLAP_TOOL_QUICK ) && slap_tool_thread_max > 2 ) { + AttrIxInfo *ax = (AttrIxInfo *)LDAP_SLIST_FIRST(&op->o_extra); + ax->ai_ai = ai; + keyfunc = mdb_tool_idl_add; + mc = (MDB_cursor *)ax; + } else +#endif + keyfunc = mdb_idl_insert_keys; + } else + keyfunc = mdb_idl_delete_keys; + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + rc = keyfunc( op->o_bd, mc, presence_key, id ); + if( rc ) { + err = "presence"; + goto done; + } + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) { + rc = ad->ad_type->sat_equality->smr_indexer( + LDAP_FILTER_EQUALITY, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_equality, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if ( rc ) { + err = "equality"; + goto done; + } + } + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) { + rc = ad->ad_type->sat_approx->smr_indexer( + LDAP_FILTER_APPROX, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_approx, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if ( rc ) { + err = "approx"; + goto done; + } + } + + rc = LDAP_SUCCESS; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) { + rc = ad->ad_type->sat_substr->smr_indexer( + LDAP_FILTER_SUBSTRINGS, + mask, + ad->ad_type->sat_syntax, + ad->ad_type->sat_substr, + atname, vals, &keys, op->o_tmpmemctx ); + + if( rc == LDAP_SUCCESS && keys != NULL ) { + rc = keyfunc( op->o_bd, mc, keys, id ); + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + if( rc ) { + err = "substr"; + goto done; + } + } + + rc = LDAP_SUCCESS; + } + +done: + if ( !(slapMode & SLAP_TOOL_QUICK)) + mdb_cursor_close( mc ); + switch( rc ) { + /* The callers all know how to deal with these results */ + case 0: + break; + /* Anything else is bad news */ + default: + rc = LDAP_OTHER; + } + return rc; +} + +static int index_at_values( + Operation *op, + MDB_txn *txn, + AttributeDescription *ad, + AttributeType *type, + struct berval *tags, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + slap_mask_t mask = 0; + int ixop = opid; + AttrInfo *ai = NULL; + + if ( opid == MDB_INDEX_UPDATE_OP ) + ixop = SLAP_INDEX_ADD_OP; + + if( type->sat_sup ) { + /* recurse */ + rc = index_at_values( op, txn, NULL, + type->sat_sup, tags, + vals, id, opid ); + + if( rc ) return rc; + } + + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + ai = mdb_attr_mask( op->o_bd->be_private, type->sat_ad ); + if ( ai && ( ai->ai_indexmask || ai->ai_newmask )) { +#ifdef LDAP_COMP_MATCH + /* component indexing */ + if ( ai->ai_cr ) { + ComponentReference *cr; + for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) { + rc = indexer( op, txn, ai, cr->cr_ad, &type->sat_cname, + cr->cr_nvals, id, ixop, + cr->cr_indexmask ); + } + } +#endif + ad = type->sat_ad; + /* If we're updating the index, just set the new bits that aren't + * already in the old mask. + */ + if ( opid == MDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + /* For regular updates, if there is a newmask use it. Otherwise + * just use the old mask. + */ + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if( mask ) { + rc = indexer( op, txn, ai, ad, &type->sat_cname, + vals, id, ixop, mask ); + + if( rc ) return rc; + } + } + } + + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + ai = mdb_attr_mask( op->o_bd->be_private, desc ); + + if( ai && ( ai->ai_indexmask || ai->ai_newmask )) { + if ( opid == MDB_INDEX_UPDATE_OP ) + mask = ai->ai_newmask & ~ai->ai_indexmask; + else + mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask; + if ( mask ) { + rc = indexer( op, txn, ai, desc, &desc->ad_cname, + vals, id, ixop, mask ); + + if( rc ) { + return rc; + } + } + } + } + } + + return LDAP_SUCCESS; +} + +int mdb_index_values( + Operation *op, + MDB_txn *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid ) +{ + int rc; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + rc = index_at_values( op, txn, desc, + desc->ad_type, &desc->ad_tags, + vals, id, opid ); + + return rc; +} + +/* Get the list of which indices apply to this attr */ +int +mdb_index_recset( + struct mdb_info *mdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir ) +{ + int rc, slot; + AttrList *al; + + if( type->sat_sup ) { + /* recurse */ + rc = mdb_index_recset( mdb, a, type->sat_sup, tags, ir ); + if( rc ) return rc; + } + /* If this type has no AD, we've never used it before */ + if( type->sat_ad ) { + slot = mdb_attr_slot( mdb, type->sat_ad, NULL ); + if ( slot >= 0 ) { + ir[slot].ir_ai = mdb->mi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].ir_attrs; + ir[slot].ir_attrs = al; + } + } + if( tags->bv_len ) { + AttributeDescription *desc; + + desc = ad_find_tags( type, tags ); + if( desc ) { + slot = mdb_attr_slot( mdb, desc, NULL ); + if ( slot >= 0 ) { + ir[slot].ir_ai = mdb->mi_attrs[slot]; + al = ch_malloc( sizeof( AttrList )); + al->attr = a; + al->next = ir[slot].ir_attrs; + ir[slot].ir_attrs = al; + } + } + } + return LDAP_SUCCESS; +} + +/* Apply the indices for the recset */ +int mdb_index_recrun( + Operation *op, + MDB_txn *txn, + struct mdb_info *mdb, + IndexRec *ir0, + ID id, + int base ) +{ + IndexRec *ir; + AttrList *al; + int i, rc = 0; + + /* Never index ID 0 */ + if ( id == 0 ) + return 0; + + for (i=base; i<mdb->mi_nattrs; i+=slap_tool_thread_max-1) { + ir = ir0 + i; + if ( !ir->ir_ai ) continue; + while (( al = ir->ir_attrs )) { + ir->ir_attrs = al->next; + rc = indexer( op, txn, ir->ir_ai, ir->ir_ai->ai_desc, + &ir->ir_ai->ai_desc->ad_type->sat_cname, + al->attr->a_nvals, id, SLAP_INDEX_ADD_OP, + ir->ir_ai->ai_indexmask ); + free( al ); + if ( rc ) break; + } + } + return rc; +} + +int +mdb_index_entry( + Operation *op, + MDB_txn *txn, + int opid, + Entry *e ) +{ + int rc; + Attribute *ap = e->e_attrs; +#if 0 /* ifdef LDAP_COMP_MATCH */ + ComponentReference *cr_list = NULL; + ComponentReference *cr = NULL, *dupped_cr = NULL; + void* decoded_comp; + ComponentSyntaxInfo* csi_attr; + Syntax* syn; + AttributeType* at; + int i, num_attr; + void* mem_op; + struct berval value = {0}; +#endif + + /* Never index ID 0 */ + if ( e->e_id == 0 ) + return 0; + + Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ? e->e_dn : "" ); + + /* add each attribute to the indexes */ + for ( ; ap != NULL; ap = ap->a_next ) { +#if 0 /* ifdef LDAP_COMP_MATCH */ + AttrInfo *ai; + /* see if attribute has components to be indexed */ + ai = mdb_attr_mask( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad ); + if ( !ai ) continue; + cr_list = ai->ai_cr; + if ( attr_converter && cr_list ) { + syn = ap->a_desc->ad_type->sat_syntax; + ap->a_comp_data = op->o_tmpalloc( sizeof( ComponentData ), op->o_tmpmemctx ); + /* Memory chunk(nibble) pre-allocation for decoders */ + mem_op = nibble_mem_allocator ( 1024*16, 1024*4 ); + ap->a_comp_data->cd_mem_op = mem_op; + for( cr = cr_list ; cr ; cr = cr->cr_next ) { + /* count how many values in an attribute */ + for( num_attr=0; ap->a_vals[num_attr].bv_val != NULL; num_attr++ ); + num_attr++; + cr->cr_nvals = (BerVarray)op->o_tmpalloc( sizeof( struct berval )*num_attr, op->o_tmpmemctx ); + for( i=0; ap->a_vals[i].bv_val != NULL; i++ ) { + /* decoding attribute value */ + decoded_comp = attr_converter ( ap, syn, &ap->a_vals[i] ); + if ( !decoded_comp ) + return LDAP_DECODING_ERROR; + /* extracting the referenced component */ + dupped_cr = dup_comp_ref( op, cr ); + csi_attr = ((ComponentSyntaxInfo*)decoded_comp)->csi_comp_desc->cd_extract_i( mem_op, dupped_cr, decoded_comp ); + if ( !csi_attr ) + return LDAP_DECODING_ERROR; + cr->cr_asn_type_id = csi_attr->csi_comp_desc->cd_type_id; + cr->cr_ad = (AttributeDescription*)get_component_description ( cr->cr_asn_type_id ); + if ( !cr->cr_ad ) + return LDAP_INVALID_SYNTAX; + at = cr->cr_ad->ad_type; + /* encoding the value of component in GSER */ + rc = component_encoder( mem_op, csi_attr, &value ); + if ( rc != LDAP_SUCCESS ) + return LDAP_ENCODING_ERROR; + /* Normalize the encoded component values */ + if ( at->sat_equality && at->sat_equality->smr_normalize ) { + rc = at->sat_equality->smr_normalize ( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + at->sat_syntax, at->sat_equality, + &value, &cr->cr_nvals[i], op->o_tmpmemctx ); + } else { + cr->cr_nvals[i] = value; + } + } + /* The end of BerVarray */ + cr->cr_nvals[num_attr-1].bv_val = NULL; + cr->cr_nvals[num_attr-1].bv_len = 0; + } + op->o_tmpfree( ap->a_comp_data, op->o_tmpmemctx ); + nibble_mem_free ( mem_op ); + ap->a_comp_data = NULL; + } +#endif + rc = mdb_index_values( op, txn, ap->a_desc, + ap->a_nvals, e->e_id, opid ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= index_entry_%s( %ld, \"%s\" ) failure\n", + opid == SLAP_INDEX_ADD_OP ? "add" : "del", + (long) e->e_id, e->e_dn ); + return rc; + } + } + + Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n", + opid == SLAP_INDEX_DELETE_OP ? "del" : "add", + (long) e->e_id, e->e_dn ? e->e_dn : "" ); + + return LDAP_SUCCESS; +} diff --git a/servers/slapd/back-mdb/init.c b/servers/slapd/back-mdb/init.c new file mode 100644 index 0000000..8aa9cbb --- /dev/null +++ b/servers/slapd/back-mdb/init.c @@ -0,0 +1,508 @@ +/* init.c - initialize mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "back-mdb.h" +#include <lutil.h> +#include <ldap_rq.h> +#include "slap-config.h" + +static const struct berval mdmi_databases[] = { + BER_BVC("ad2i"), + BER_BVC("dn2i"), + BER_BVC("id2e"), + BER_BVC("id2v"), + BER_BVNULL +}; + +static int +mdb_id_compare( const MDB_val *a, const MDB_val *b ) +{ + return *(ID *)a->mv_data < *(ID *)b->mv_data ? -1 : *(ID *)a->mv_data > *(ID *)b->mv_data; +} + +static int +mdb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct mdb_info *mdb; + int rc; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_db_init) ": Initializing mdb database\n" ); + + /* allocate backend-database-specific stuff */ + mdb = (struct mdb_info *) ch_calloc( 1, sizeof(struct mdb_info) ); + + /* DBEnv parameters */ + mdb->mi_dbenv_home = ch_strdup( SLAPD_DEFAULT_DB_DIR ); + mdb->mi_dbenv_flags = 0; + mdb->mi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + + mdb->mi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + mdb->mi_search_stack = NULL; + + mdb->mi_mapsize = DEFAULT_MAPSIZE; + mdb->mi_rtxn_size = DEFAULT_RTXN_SIZE; + mdb->mi_multi_hi = UINT_MAX; + mdb->mi_multi_lo = UINT_MAX; + + be->be_private = mdb; + be->be_cf_ocs = be->bd_info->bi_cf_ocs+1; + +#ifndef MDB_MULTIPLE_SUFFIXES + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX; +#endif + + rc = mdb_monitor_db_init( be ); + + return rc; +} + +static int +mdb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +mdb_db_open( BackendDB *be, ConfigReply *cr ) +{ + int rc, i; + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + struct stat stat1; + unsigned flags; + char *dbhome; + MDB_txn *txn; + + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": need suffix.\n" ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(mdb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val ); + + /* Check existence of dbenv_home. Any error means trouble */ + rc = stat( mdb->mi_dbenv_home, &stat1 ); + if( rc != 0 ) { + int saved_errno = errno; + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "cannot access database directory \"%s\" (%d).\n", + be->be_suffix[0].bv_val, mdb->mi_dbenv_home, saved_errno ); + return -1; + } + + /* mdb is always clean */ + be->be_flags |= SLAP_DBFLAG_CLEAN; + + rc = mdb_env_create( &mdb->mi_dbenv ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + if ( mdb->mi_readers ) { + rc = mdb_env_set_maxreaders( mdb->mi_dbenv, mdb->mi_readers ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_maxreaders failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + } + + rc = mdb_env_set_mapsize( mdb->mi_dbenv, mdb->mi_mapsize ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_mapsize failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + rc = mdb_env_set_maxdbs( mdb->mi_dbenv, MDB_INDICES ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "mdb_env_set_maxdbs failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + +#ifdef HAVE_EBCDIC + strcpy( path, mdb->mi_dbenv_home ); + __atoe( path ); + dbhome = path; +#else + dbhome = mdb->mi_dbenv_home; +#endif + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_db_open) ": database \"%s\": " + "dbenv_open(%s).\n", + be->be_suffix[0].bv_val, mdb->mi_dbenv_home ); + + flags = mdb->mi_dbenv_flags; + + if ( slapMode & SLAP_TOOL_QUICK ) + flags |= MDB_NOSYNC|MDB_WRITEMAP; + + if ( slapMode & SLAP_TOOL_READONLY) + flags |= MDB_RDONLY; + + rc = mdb_env_open( mdb->mi_dbenv, dbhome, + flags, mdb->mi_dbenv_mode ); + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). " + "Restore from backup!\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, flags & MDB_RDONLY, &txn ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). " + "Restore from backup!\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + /* open (and create) main databases */ + for( i = 0; mdmi_databases[i].bv_val; i++ ) { + flags = MDB_INTEGERKEY; + if( i == MDB_ID2ENTRY ) { + if ( !(slapMode & (SLAP_TOOL_READMAIN|SLAP_TOOL_READONLY) )) + flags |= MDB_CREATE; + } else { + if ( i == MDB_DN2ID ) + flags |= MDB_DUPSORT; + if ( i == MDB_ID2VAL ) + flags ^= MDB_INTEGERKEY|MDB_DUPSORT; + if ( !(slapMode & SLAP_TOOL_READONLY) ) + flags |= MDB_CREATE; + } + + rc = mdb_dbi_open( txn, + mdmi_databases[i].bv_val, + flags, + &mdb->mi_dbis[i] ); + + if ( rc != 0 ) { + /* when read-only, it's ok for ID2VAL or IDXCKP to not exist */ + if (( flags & MDB_CREATE ) || ( i < MDB_ID2VAL )) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "mdb_dbi_open(%s/%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + mdb->mi_dbenv_home, mdmi_databases[i].bv_val, + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": %s\n", + cr->msg ); + goto fail; + } + } + + if ( i == MDB_ID2ENTRY ) + mdb_set_compare( txn, mdb->mi_dbis[i], mdb_id_compare ); + else if ( i == MDB_ID2VAL ) { + mdb_set_compare( txn, mdb->mi_dbis[i], mdb_id2v_compare ); + mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_id2v_dupsort ); + } else if ( i == MDB_DN2ID ) { + MDB_cursor *mc; + MDB_val key, data; + mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_dup_compare ); + /* check for old dn2id format */ + rc = mdb_cursor_open( txn, mdb->mi_dbis[i], &mc ); + /* first record is always ID 0 */ + rc = mdb_cursor_get( mc, &key, &data, MDB_FIRST ); + if ( rc == 0 ) { + rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT ); + if ( rc == 0 ) { + int len; + unsigned char *ptr; + ptr = data.mv_data; + len = (ptr[0] & 0x7f) << 8 | ptr[1]; + if (data.mv_size < 2*len + 4 + 2*sizeof(ID)) { + snprintf( cr->msg, sizeof(cr->msg), + "database \"%s\": DN index needs upgrade, " + "run \"slapindex entryDN\".", + be->be_suffix[0].bv_val ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": %s\n", + cr->msg ); + if ( !(slapMode & SLAP_TOOL_READMAIN )) + rc = LDAP_OTHER; + mdb->mi_flags |= MDB_NEED_UPGRADE; + } + } + } + mdb_cursor_close( mc ); + if ( rc == LDAP_OTHER ) + goto fail; + } + } + + rc = mdb_ad_read( mdb, txn ); + if ( rc ) { + mdb_txn_abort( txn ); + goto fail; + } + + /* slapcat doesn't need indexes. avoid a failure if + * a configured index wasn't created yet. + */ + if ( !(slapMode & SLAP_TOOL_READONLY) ) { + rc = mdb_attr_dbs_open( be, txn, cr ); + if ( rc ) { + mdb_txn_abort( txn ); + goto fail; + } + } + + rc = mdb_txn_commit(txn); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_db_open) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + goto fail; + } + + /* monitor setup */ + rc = mdb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } + + mdb->mi_flags |= MDB_IS_OPEN; + + return 0; + +fail: + mdb_db_close( be, NULL ); + return rc; +} + +static int +mdb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int rc; + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* monitor handling */ + (void)mdb_monitor_db_close( be ); + + mdb->mi_flags &= ~MDB_IS_OPEN; + + if( mdb->mi_dbenv ) { + mdb_reader_flush( mdb->mi_dbenv ); + } + + if ( mdb->mi_dbenv ) { + if ( mdb->mi_dbis[0] ) { + int i; + + mdb_attr_dbs_close( mdb ); + for ( i=0; i<MDB_NDB; i++ ) + mdb_dbi_close( mdb->mi_dbenv, mdb->mi_dbis[i] ); + + /* force a sync, but not if we were ReadOnly, + * and not in Quick mode. + */ + if (!(slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY))) { + rc = mdb_env_sync( mdb->mi_dbenv, 1 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "mdb_db_close: database \"%s\": " + "mdb_env_sync failed: %s (%d).\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + } + } + } + + mdb_env_close( mdb->mi_dbenv ); + mdb->mi_dbenv = NULL; + } + + return 0; +} + +static int +mdb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* stop and remove checkpoint task */ + if ( mdb->mi_txn_cp_task ) { + struct re_s *re = mdb->mi_txn_cp_task; + mdb->mi_txn_cp_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + /* monitor handling */ + (void)mdb_monitor_db_destroy( be ); + + if( mdb->mi_dbenv_home ) ch_free( mdb->mi_dbenv_home ); + + mdb_attr_index_destroy( mdb ); + + ch_free( mdb ); + be->be_private = NULL; + + return 0; +} + +int +mdb_back_initialize( + BackendInfo *bi ) +{ + int rc; + + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, + LDAP_CONTROL_TXN_SPEC, + NULL + }; + + /* initialize the underlying database system */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_back_initialize) ": initialize " + MDB_UCTYPE " backend\n" ); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS | + SLAP_BFLAG_TXNS; + + bi->bi_controls = controls; + + { /* version check */ + int major, minor, patch, ver; + char *version = mdb_version( &major, &minor, &patch ); +#ifdef HAVE_EBCDIC + char v2[1024]; + + /* All our stdio does an ASCII to EBCDIC conversion on + * the output. Strings from the MDB library are already + * in EBCDIC; we have to go back and forth... + */ + strcpy( v2, version ); + __etoa( v2 ); + version = v2; +#endif + ver = (major << 24) | (minor << 16) | patch; + if( ver != MDB_VERSION_FULL ) { + /* fail if a versions don't match */ + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_back_initialize) ": " + "MDB library version mismatch:" + " expected " MDB_VERSION_STRING "," + " got %s\n", version ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_back_initialize) + ": %s\n", version ); + } + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = mdb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = mdb_db_open; + bi->bi_db_close = mdb_db_close; + bi->bi_db_destroy = mdb_db_destroy; + + bi->bi_op_add = mdb_add; + bi->bi_op_bind = mdb_bind; + bi->bi_op_compare = mdb_compare; + bi->bi_op_delete = mdb_delete; + bi->bi_op_modify = mdb_modify; + bi->bi_op_modrdn = mdb_modrdn; + bi->bi_op_search = mdb_search; + + bi->bi_op_unbind = 0; + bi->bi_op_txn = mdb_txn; + + bi->bi_extended = mdb_extended; + + bi->bi_chk_referrals = 0; + bi->bi_operational = mdb_operational; + + bi->bi_has_subordinates = mdb_hasSubordinates; + bi->bi_entry_release_rw = mdb_entry_release; + bi->bi_entry_get_rw = mdb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = mdb_tool_entry_open; + bi->bi_tool_entry_close = mdb_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = mdb_tool_entry_first_x; + bi->bi_tool_entry_next = mdb_tool_entry_next; + bi->bi_tool_entry_get = mdb_tool_entry_get; + bi->bi_tool_entry_put = mdb_tool_entry_put; + bi->bi_tool_entry_reindex = mdb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = mdb_tool_dn2id_get; + bi->bi_tool_entry_modify = mdb_tool_entry_modify; + bi->bi_tool_entry_delete = mdb_tool_entry_delete; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = mdb_back_init_cf( bi ); + + return rc; +} + +#if (SLAPD_MDB == SLAPD_MOD_DYNAMIC) + +SLAP_BACKEND_INIT_MODULE( mdb ) + +#endif /* SLAPD_MDB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-mdb/key.c b/servers/slapd/back-mdb/key.c new file mode 100644 index 0000000..b0b453b --- /dev/null +++ b/servers/slapd/back-mdb/key.c @@ -0,0 +1,72 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" +#include "idl.h" + +/* read a key */ +int +mdb_key_read( + Backend *be, + MDB_txn *txn, + MDB_dbi dbi, + struct berval *k, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag +) +{ + int rc; + MDB_val key; +#ifndef MISALIGNED_OK + int kbuf[2]; +#endif + + Debug( LDAP_DEBUG_TRACE, "=> key_read\n" ); + +#ifndef MISALIGNED_OK + if (k->bv_len & ALIGNER) { + key.mv_size = sizeof(kbuf); + key.mv_data = kbuf; + kbuf[1] = 0; + memcpy(kbuf, k->bv_val, k->bv_len); + } else +#endif + { + key.mv_size = k->bv_len; + key.mv_data = k->bv_val; + } + + rc = mdb_idl_fetch_key( be, txn, dbi, &key, ids, saved_cursor, get_flag ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read: failed (%d)\n", + rc ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read %ld candidates\n", + (long) MDB_IDL_N(ids) ); + } + + return rc; +} diff --git a/servers/slapd/back-mdb/modify.c b/servers/slapd/back-mdb/modify.c new file mode 100644 index 0000000..f2233e8 --- /dev/null +++ b/servers/slapd/back-mdb/modify.c @@ -0,0 +1,843 @@ +/* modify.c - mdb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "back-mdb.h" + +static struct berval scbva[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +#define CHECK_ADD 1 +#define CHECK_DEL 2 + +static void +mdb_modify_idxflags( + Operation *op, + AttributeDescription *desc, + int ixcheck, + Attribute *newattrs, + Attribute *oldattrs ) +{ + struct berval ix_at; + AttrInfo *ai; + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + ai = mdb_index_mask( op->o_bd, desc, &ix_at ); + if ( ai ) { + if ( ixcheck & CHECK_DEL ) { + Attribute *ap; + struct berval ix2; + + ap = attr_find( oldattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL; + + /* ITS#8678 FIXME + * If using 32bit hashes, or substring index, must account for + * possible index collisions. If no substring index, and using + * 64bit hashes, assume we don't need to check for collisions. + * + * In 2.5 use refcounts and avoid all of this mess. + */ + if (!slap_hash64(-1) || (ai->ai_indexmask & SLAP_INDEX_SUBSTR)) { + /* Find all other attrs that index to same slot */ + for ( ap = newattrs; ap; ap = ap->a_next ) { + ai = mdb_index_mask( op->o_bd, ap->a_desc, &ix2 ); + if ( ai && ix2.bv_val == ix_at.bv_val ) + ap->a_flags |= SLAP_ATTR_IXADD; + } + } + + } + if ( ixcheck & CHECK_ADD ) { + Attribute *ap; + + ap = attr_find( newattrs, desc ); + if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD; + } + } +} + +int mdb_modify_internal( + Operation *op, + MDB_txn *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int rc, err; + Modification *mod; + Modifications *ml; + Attribute *save_attrs; + Attribute *ap, *aold, *anew; + int glue_attr_delete = 0; + int softop, chkpresent; + int ixcheck; + int a_flags; + MDB_cursor *mvc = NULL; + + Debug( LDAP_DEBUG_TRACE, "mdb_modify_internal: 0x%08lx: %s\n", + e->e_id, e->e_dn ); + + if ( !acl_check_modlist( op, e, modlist )) { + return LDAP_INSUFFICIENT_ACCESS; + } + + /* save_attrs will be disposed of by caller */ + save_attrs = e->e_attrs; + e->e_attrs = attrs_dup( e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + int match; + mod = &ml->sml_mod; + switch( mod->sm_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) { + value_match( &match, slap_schema.si_ad_structuralObjectClass, + slap_schema.si_ad_structuralObjectClass-> + ad_type->sat_equality, + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + &mod->sm_values[0], &scbva[0], text ); + if ( !match ) glue_attr_delete = 1; + } + } + if ( glue_attr_delete ) + break; + } + + if ( glue_attr_delete ) { + Attribute **app = &e->e_attrs; + while ( *app != NULL ) { + if ( !is_at_operational( (*app)->a_desc->ad_type )) { + Attribute *save = *app; + *app = (*app)->a_next; + attr_free( save ); + continue; + } + app = &(*app)->a_next; + } + } + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + ixcheck = 0; + + aold = attr_find( e->e_attrs, mod->sm_desc ); + if (aold) + a_flags = aold->a_flags; + else + a_flags = 0; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + softop = 0; + chkpresent = 0; + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: add %s\n", + mod->sm_desc->ad_cname.bv_val ); + +do_add: + err = modify_add_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + if( softop ) { + mod->sm_op = SLAP_MOD_SOFTADD; + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) + err = LDAP_SUCCESS; + } + if( chkpresent ) { + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text ); + } else { + unsigned hi; + if (!aold) + anew = attr_find( e->e_attrs, mod->sm_desc ); + else + anew = aold; + mdb_attr_multi_thresh( mdb, mod->sm_desc, &hi, NULL ); + /* check for big multivalued attrs */ + if ( anew->a_numvals > hi ) + anew->a_flags |= SLAP_ATTR_BIG_MULTI; + if ( anew->a_flags & SLAP_ATTR_BIG_MULTI ) { + if (!mvc) { + err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if (err) { +mval_fail: strncpy( textbuf, mdb_strerror( err ), textlen ); + err = LDAP_OTHER; + break; + } + } + /* if prev was set, just add new values */ + if (a_flags & SLAP_ATTR_BIG_MULTI ) { + anew = (Attribute *)mod; + /* Tweak nvals */ + if (!anew->a_nvals) + anew->a_nvals = anew->a_vals; + } + err = mdb_mval_put(op, mvc, e->e_id, anew); + if (a_flags & SLAP_ATTR_BIG_MULTI ) { + /* Undo nvals tweak */ + if (anew->a_nvals == anew->a_vals) + anew->a_nvals = NULL; + } + if ( err ) + goto mval_fail; + } + ixcheck |= CHECK_ADD; + } + break; + + case LDAP_MOD_DELETE: + if ( glue_attr_delete ) { + err = LDAP_SUCCESS; + break; + } + + softop = 0; + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: delete %s\n", + mod->sm_desc->ad_cname.bv_val ); +do_del: + err = modify_delete_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + if (softop) { + mod->sm_op = SLAP_MOD_SOFTDEL; + if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + softop = 2; + } + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text ); + } else { + if (softop != 2) + ixcheck |= CHECK_DEL; + /* check for big multivalued attrs */ + if (a_flags & SLAP_ATTR_BIG_MULTI) { + Attribute a_dummy; + if (!mvc) { + err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if (err) + goto mval_fail; + } + if ( mod->sm_numvals ) { + anew = attr_find( e->e_attrs, mod->sm_desc ); + if ( anew ) { + unsigned lo; + mdb_attr_multi_thresh( mdb, mod->sm_desc, NULL, &lo ); + if ( anew->a_numvals < lo ) { + anew->a_flags ^= SLAP_ATTR_BIG_MULTI; + anew = NULL; + } else { + anew = (Attribute *)mod; + } + } + } else { + anew = NULL; + } + if (!anew) { + /* delete all values */ + anew = &a_dummy; + anew->a_desc = mod->sm_desc; + anew->a_numvals = 0; + } + err = mdb_mval_del( op, mvc, e->e_id, anew ); + if ( err ) + goto mval_fail; + } + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: replace %s\n", + mod->sm_desc->ad_cname.bv_val ); + err = modify_replace_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text ); + } else { + unsigned hi; + ixcheck = CHECK_DEL; + if ( mod->sm_numvals ) + ixcheck |= CHECK_ADD; + if (a_flags & SLAP_ATTR_BIG_MULTI) { + Attribute a_dummy; + if (!mvc) { + err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if (err) + goto mval_fail; + } + /* delete all values */ + anew = &a_dummy; + anew->a_desc = mod->sm_desc; + anew->a_numvals = 0; + err = mdb_mval_del( op, mvc, e->e_id, anew ); + if (err) + goto mval_fail; + } + anew = attr_find( e->e_attrs, mod->sm_desc ); + mdb_attr_multi_thresh( mdb, mod->sm_desc, &hi, NULL ); + if (mod->sm_numvals > hi) { + anew->a_flags |= SLAP_ATTR_BIG_MULTI; + if (!mvc) { + err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc ); + if (err) + goto mval_fail; + } + err = mdb_mval_put(op, mvc, e->e_id, anew); + if (err) + goto mval_fail; + } else if (anew) { + /* revert back to normal attr */ + anew->a_flags &= ~SLAP_ATTR_BIG_MULTI; + } + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: increment %s\n", + mod->sm_desc->ad_cname.bv_val ); + err = modify_increment_values( e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: %d %s\n", + err, *text ); + } else { + ixcheck = CHECK_ADD|CHECK_DEL; + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: softadd %s\n", + mod->sm_desc->ad_cname.bv_val ); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + softop = 1; + chkpresent = 0; + goto do_add; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: softdel %s\n", + mod->sm_desc->ad_cname.bv_val ); + /* Avoid problems in index_delete_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_DELETE; + softop = 1; + goto do_del; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) { + /* skip */ + err = LDAP_SUCCESS; + break; + } + + Debug(LDAP_DEBUG_ARGS, + "mdb_modify_internal: add_if_not_present %s\n", + mod->sm_desc->ad_cname.bv_val ); + /* Avoid problems in index_add_mods() + * We need to add index if necessary. + */ + mod->sm_op = LDAP_MOD_ADD; + softop = 0; + chkpresent = 1; + goto do_add; + + default: + Debug(LDAP_DEBUG_ANY, "mdb_modify_internal: invalid op %d\n", + mod->sm_op ); + *text = "Invalid modify operation"; + err = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n", + err, *text ); + } + + if ( err != LDAP_SUCCESS ) { + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + /* unlock entry, delete from cache */ + return err; + } + + /* If objectClass was modified, reset the flags */ + if ( mod->sm_desc == slap_schema.si_ad_objectClass ) { + e->e_ocflags = 0; + } + + if ( glue_attr_delete ) e->e_ocflags = 0; + + + /* check if modified attribute was indexed + * but not in case of NOOP... */ + if ( !op->o_noop ) { + mdb_modify_idxflags( op, mod->sm_desc, ixcheck, e->e_attrs, save_attrs ); + } + } + + /* check that the entry still obeys the schema */ + ap = NULL; + rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap, + text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS || op->o_noop ) { + attrs_free( e->e_attrs ); + /* clear the indexing flags */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL); + } + e->e_attrs = save_attrs; + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "entry failed schema check: %s\n", + *text ); + } + + /* if NOOP then silently revert to saved attrs */ + return rc; + } + + /* structuralObjectClass modified! */ + if ( ap ) { + assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass ); + if ( !op->o_noop ) { + mdb_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass, + CHECK_ADD|CHECK_DEL, e->e_attrs, save_attrs ); + } + } + + /* update the indices of the modified attributes */ + + /* start with deleting the old index entries */ + for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) { + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + struct berval *vals; + Attribute *a2; + ap->a_flags &= ~SLAP_ATTR_IXDEL; + a2 = attr_find( e->e_attrs, ap->a_desc ); + if ( a2 ) { + /* need to detect which values were deleted */ + int i, j, k; + /* let add know there were deletes */ + if ( a2->a_flags & SLAP_ATTR_IXADD ) + a2->a_flags |= SLAP_ATTR_IXDEL; + vals = op->o_tmpalloc( (ap->a_numvals + 1) * + sizeof(struct berval), op->o_tmpmemctx ); + j = 0; + for ( i=k=0; i < ap->a_numvals; i++ ) { + char found = 0; + BerValue* current = &ap->a_nvals[i]; + int k2 = k; + for (k2 = k ; k2 < a2->a_numvals; k2 ++) { + int match = -1, rc; + const char *text; + + rc = ordered_value_match( &match, a2->a_desc, + ap->a_desc->ad_type->sat_equality, 0, + &a2->a_nvals[k2], current, &text ); + if ( rc == LDAP_SUCCESS && match == 0 ) { + found = 1; + break; + } + } + + if (!found) { + vals[j++] = *current; + } else { + k = k2 + 1; + } + } + BER_BVZERO(vals+j); + } else { + /* attribute was completely deleted */ + vals = ap->a_nvals; + } + rc = 0; + if ( !BER_BVISNULL( vals )) { + rc = mdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_DELETE_OP ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index delete failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + } + } + if ( vals != ap->a_nvals ) + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( rc ) return rc; + } + } + + /* add the new index entries */ + for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) { + if (ap->a_flags & SLAP_ATTR_IXADD) { + ap->a_flags &= ~SLAP_ATTR_IXADD; + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + /* if any values were deleted, we must readd index + * for all remaining values. + */ + ap->a_flags &= ~SLAP_ATTR_IXDEL; + rc = mdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } else { + int found = 0; + /* if this was only an add, we only need to index + * the added values. + */ + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + struct berval *vals; + if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals ) + continue; + found = 1; + switch( ml->sml_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + case SLAP_MOD_SOFTADD: + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( ml->sml_op == LDAP_MOD_INCREMENT ) + vals = ap->a_nvals; + else if ( ml->sml_nvalues ) + vals = ml->sml_nvalues; + else + vals = ml->sml_values; + rc = mdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_ADD_OP ); + break; + } + if ( rc ) + break; + } + /* This attr was affected by a modify of a subtype, so + * there was no direct match in the modlist. Just readd + * all of its values. + */ + if ( !found ) { + rc = mdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } + } + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: attribute \"%s\" index add failure\n", + op->o_log_prefix, ap->a_desc->ad_cname.bv_val ); + attrs_free( e->e_attrs ); + e->e_attrs = save_attrs; + return rc; + } + } + } + + return rc; +} + + +int +mdb_modify( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + MDB_txn *txn = NULL; + mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + Entry dummy = {0}; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + int numads = mdb->mi_numads; + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(mdb_modify) ": %s\n", + op->o_req_dn.bv_val ); + + ctrls[num_ctrls] = NULL; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + /* Don't touch the opattrs, if this is a contextCSN update + * initiated from updatedn */ + if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next || + op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) { + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + } + + /* get entry or ancestor */ + rs->sr_err = mdb_dn2entry( op, txn, NULL, &op->o_req_ndn, &e, NULL, 1 ); + + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": dn2entry failed (%d)\n", + rs->sr_err ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + /* acquire and lock entry */ + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == MDB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if ( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + goto done; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow modify */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": entry is referral\n" ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e->e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if ( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modify) ": pre-read " + "failed!\n" ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Modify the entry */ + dummy = *e; + rs->sr_err = mdb_modify_internal( op, txn, op->orm_modlist, + &dummy, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": modify failed (%d)\n", + rs->sr_err ); + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + + /* change the entry itself */ + rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": id2entry update failed " "(%d)\n", + rs->sr_err ); + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) { + rs->sr_text = "entry too big"; + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry update failed"; + } + goto return_results; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modify) + ": post-read failed!\n" ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + } else { + rs->sr_err = mdb_txn_commit( txn ); + if ( rs->sr_err ) + mdb->mi_numads = numads; + txn = NULL; + } + } + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modify) ": updated%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + +done: + slap_graduate_commit_csn( op ); + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb->mi_numads = numads; + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + + rs->sr_text = NULL; + + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/modrdn.c b/servers/slapd/back-mdb/modrdn.c new file mode 100644 index 0000000..5b234ba --- /dev/null +++ b/servers/slapd/back-mdb/modrdn.c @@ -0,0 +1,624 @@ +/* modrdn.c - mdb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_modrdn( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + struct berval p_dn, p_ndn; + struct berval new_dn = {0, NULL}, new_ndn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + /* LDAP v2 supporting correct attribute handling. */ + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + MDB_txn *txn = NULL; + MDB_cursor *mc; + struct mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo; + Entry dummy = {0}; + + Entry *np = NULL; /* newSuperior Entry */ + struct berval *np_dn = NULL; /* newSuperior dn */ + struct berval *np_ndn = NULL; /* newSuperior ndn */ + struct berval *new_parent_dn = NULL; /* np_dn, p_dn, or NULL */ + + int manageDSAit = get_manageDSAit( op ); + + ID nid, nsubs; + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(mdb_modrdn) "(%s,%s,%s)\n", + op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val, + op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" ); + + ctrls[num_ctrls] = NULL; + + /* begin transaction */ + rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": txn_begin failed: " + "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + txn = moi->moi_txn; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) { +#ifdef MDB_MULTIPLE_SUFFIXES + /* Allow renaming one suffix entry to another */ + p_ndn = slap_empty_bv; +#else + /* There can only be one suffix entry */ + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "cannot rename suffix entry"; + goto return_results; +#endif + } else { + dnParent( &op->o_req_ndn, &p_ndn ); + } + np_ndn = &p_ndn; + /* Make sure parent entry exist and we can write its + * children. + */ + rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": cursor_open failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN cursor_open failed"; + goto return_results; + } + rs->sr_err = mdb_dn2entry( op, txn, mc, &p_ndn, &p, NULL, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) + ": parent does not exist\n" ); + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + rs->sr_err = LDAP_REFERRAL; + + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + goto done; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, + op->oq_modrdn.rs_newSup == NULL ? + ACL_WRITE : ACL_WDEL, + NULL ); + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, "no access to parent\n" ); + rs->sr_text = "no write access to parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": wr to children " + "of entry %s OK\n", p_ndn.bv_val ); + + if ( p_ndn.bv_val == slap_empty_bv.bv_val ) { + p_dn = slap_empty_bv; + } else { + dnParent( &op->o_req_dn, &p_dn ); + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) ": parent dn=%s\n", + p_dn.bv_val ); + + /* get entry */ + rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, &nsubs, 0 ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + e = p; + p = NULL; + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == MDB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + if ( is_entry_referral( e )) { + BerVarray ref = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + } else { + rs->sr_ref = NULL; + } + mdb_entry_return( op, e ); + e = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* check write on old entry */ + rs->sr_err = access_allowed( op, e, entry, NULL, ACL_WRITE, NULL ); + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, "no access to entry\n" ); + rs->sr_text = "no write access to old entry"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + if (!manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral, don't allow rename */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) + ": entry %s is referral\n", e->e_dn ); + + rs->sr_err = LDAP_REFERRAL, + rs->sr_matched = e->e_name.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + goto done; + } + + new_parent_dn = &p_dn; /* New Parent unless newSuperior given */ + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": new parent \"%s\" requested...\n", + op->oq_modrdn.rs_newSup->bv_val ); + + /* newSuperior == oldParent? */ + if( dn_match( &p_ndn, op->oq_modrdn.rs_nnewSup ) ) { + Debug( LDAP_DEBUG_TRACE, "mdb_back_modrdn: " + "new parent \"%s\" same as the old parent \"%s\"\n", + op->oq_modrdn.rs_newSup->bv_val, p_dn.bv_val ); + op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */ + } + } + + /* There's a MDB_MULTIPLE_SUFFIXES case here that this code doesn't + * support. E.g., two suffixes dc=foo,dc=com and dc=bar,dc=net. + * We do not allow modDN + * dc=foo,dc=com + * newrdn dc=bar + * newsup dc=net + * and we probably should. But since MULTIPLE_SUFFIXES is deprecated + * I'm ignoring this problem for now. + */ + if ( op->oq_modrdn.rs_newSup != NULL ) { + if ( op->oq_modrdn.rs_newSup->bv_len ) { + np_dn = op->oq_modrdn.rs_newSup; + np_ndn = op->oq_modrdn.rs_nnewSup; + + /* newSuperior == oldParent? - checked above */ + /* newSuperior == entry being moved?, if so ==> ERROR */ + if ( dnIsSuffix( np_ndn, &e->e_nname )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "new superior not found"; + goto return_results; + } + /* Get Entry with dn=newSuperior. Does newSuperior exist? */ + rs->sr_err = mdb_dn2entry( op, txn, NULL, np_ndn, &np, NULL, 0 ); + + switch( rs->sr_err ) { + case 0: + break; + case MDB_NOTFOUND: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": newSup(ndn=%s) not here!\n", + np_ndn->bv_val ); + rs->sr_text = "new superior not found"; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto return_results; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, np, children, + NULL, ACL_WADD, NULL ); + + if( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": no wr to newSup children\n" ); + rs->sr_text = "no write access to new superior's children"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": wr to new parent OK np=%p, id=%ld\n", + (void *) np, (long) np->e_id ); + + if ( is_entry_alias( np ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": entry is alias\n" ); + rs->sr_text = "new superior is an alias"; + rs->sr_err = LDAP_ALIAS_PROBLEM; + goto return_results; + } + + if ( is_entry_referral( np ) ) { + /* parent is a referral, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": entry is referral\n" ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + np_dn = &np->e_name; + + } else { + np_dn = NULL; + + /* no parent, modrdn entry directly under root */ + if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv ) + || be_isupdate( op ) ) { + np = (Entry *)&slap_entry_root; + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, np, + children, NULL, ACL_WADD, NULL ); + + np = NULL; + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, + "no access to new superior\n" ); + rs->sr_text = + "no write access to new superior's children"; + goto return_results; + } + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": wr to new parent's children OK\n" ); + + new_parent_dn = np_dn; + } + + /* Build target dn and make sure target entry doesn't exist already. */ + if (!new_dn.bv_val) { + build_new_dn( &new_dn, new_parent_dn, &op->oq_modrdn.rs_newrdn, op->o_tmpmemctx ); + } + + if (!new_ndn.bv_val) { + dnNormalize( 0, NULL, NULL, &new_dn, &new_ndn, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) ": new ndn=%s\n", + new_ndn.bv_val ); + + /* Shortcut the search */ + rs->sr_err = mdb_dn2id ( op, txn, NULL, &new_ndn, &nid, NULL, NULL, NULL ); + switch( rs->sr_err ) { + case MDB_NOTFOUND: + break; + case 0: + /* Allow rename to same DN */ + if ( nid == e->e_id ) + break; + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + if( op->o_preread ) { + if( preread_ctrl == NULL ) { + preread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, e, + &slap_pre_read_bv, preread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": pre-read failed!\n" ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* delete old DN + * If moving to a new parent, must delete current subtree count, + * otherwise leave it unchanged since we'll be adding it right back. + */ + rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, np ? nsubs : 0 ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": dn2id del failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index delete fail"; + goto return_results; + } + + /* copy the entry, then override some fields */ + dummy = *e; + dummy.e_name = new_dn; + dummy.e_nname = new_ndn; + dummy.e_attrs = NULL; + + /* add new DN */ + rs->sr_err = mdb_dn2id_add( op, mc, mc, np ? np->e_id : p->e_id, + nsubs, np != NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": dn2id add failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index add failed"; + goto return_results; + } + + dummy.e_attrs = e->e_attrs; + + if ( op->orr_modlist != NULL ) { + /* modify entry */ + rs->sr_err = mdb_modify_internal( op, txn, op->orr_modlist, &dummy, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": modify failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + goto return_results; + } + } + + /* id2entry index */ + rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": id2entry failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) { + rs->sr_text = "entry too big"; + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "entry update failed"; + } + goto return_results; + } + + if ( p_ndn.bv_len != 0 ) { + if ((parent_is_glue = is_entry_glue(p))) { + rs->sr_err = mdb_dn2id_children( op, txn, p ); + if ( rs->sr_err != MDB_NOTFOUND ) { + switch( rs->sr_err ) { + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": has_children failed: %s (%d)\n", + mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } else { + parent_is_leaf = 1; + } + } + mdb_entry_return( op, p ); + p = NULL; + } + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &dummy, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(mdb_modrdn) + ": post-read failed!\n" ); + if ( op->o_postread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + if( moi == &opinfo ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + opinfo.moi_oe.oe_key = NULL; + if( op->o_noop ) { + mdb_txn_abort( txn ); + rs->sr_err = LDAP_X_NO_OPERATION; + txn = NULL; + goto return_results; + + } else { + if(( rs->sr_err=mdb_txn_commit( txn )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + txn = NULL; + } + } + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_modrdn) ": %s : %s (%d)\n", + rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_modrdn) + ": rdn modified%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + dummy.e_id, op->o_req_dn.bv_val ); + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( e != NULL && dummy.e_attrs != e->e_attrs ) { + attrs_free( dummy.e_attrs ); + } + send_ldap_result( op, rs ); + +#if 0 + if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( mdb->bi_dbenv, + mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 ); + } +#endif + + if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) { + op->o_delete_glue_parent = 1; + } + +done: + slap_graduate_commit_csn( op ); + + if( new_ndn.bv_val != NULL ) op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx ); + if( new_dn.bv_val != NULL ) op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx ); + + /* LDAP v3 Support */ + if( np != NULL ) { + /* free new parent */ + mdb_entry_return( op, np ); + } + + if( p != NULL ) { + /* free parent */ + mdb_entry_return( op, p ); + } + + /* free entry */ + if( e != NULL ) { + mdb_entry_return( op, e ); + } + + if( moi == &opinfo ) { + if( txn != NULL ) { + mdb_txn_abort( txn ); + } + if ( opinfo.moi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next ); + } + } else { + moi->moi_ref--; + } + + if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) { + slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *preread_ctrl, op->o_tmpmemctx ); + } + if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) { + slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx ); + slap_sl_free( *postread_ctrl, op->o_tmpmemctx ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-mdb/monitor.c b/servers/slapd/back-mdb/monitor.c new file mode 100644 index 0000000..fc77bc6 --- /dev/null +++ b/servers/slapd/back-mdb/monitor.c @@ -0,0 +1,807 @@ +/* monitor.c - monitor mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> +#include <ac/stdlib.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include "lutil.h" +#include "back-mdb.h" + +#include "../back-monitor/back-monitor.h" + +#include "slap-config.h" + +static ObjectClass *oc_olmMDBDatabase; + +static AttributeDescription *ad_olmDbDirectory; + +#ifdef MDB_MONITOR_IDX +static int +mdb_monitor_idx_entry_add( + struct mdb_info *mdb, + Entry *e ); + +static AttributeDescription *ad_olmDbNotIndexed; +#endif /* MDB_MONITOR_IDX */ + +static AttributeDescription *ad_olmMDBPagesMax, + *ad_olmMDBPagesUsed, *ad_olmMDBPagesFree; + +static AttributeDescription *ad_olmMDBReadersMax, + *ad_olmMDBReadersUsed; + +static AttributeDescription *ad_olmMDBEntries; + +/* + * NOTE: there's some confusion in monitor OID arc; + * by now, let's consider: + * + * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0 + * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1 + * MDB database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.3 + * + * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0 + * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1 + * MDB database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.3 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmMDBAttributes", "olmDatabaseAttributes:1" }, + { "olmMDBObjectClasses", "olmDatabaseObjectClasses:1" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmDatabaseAttributes:1 " + "NAME ( 'olmDbDirectory' ) " + "DESC 'Path name of the directory " + "where the database environment resides' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbDirectory }, + +#ifdef MDB_MONITOR_IDX + { "( olmDatabaseAttributes:2 " + "NAME ( 'olmDbNotIndexed' ) " + "DESC 'Missing indexes resulting from candidate selection' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbNotIndexed }, +#endif /* MDB_MONITOR_IDX */ + + { "( olmMDBAttributes:1 " + "NAME ( 'olmMDBPagesMax' ) " + "DESC 'Maximum number of pages' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesMax }, + + { "( olmMDBAttributes:2 " + "NAME ( 'olmMDBPagesUsed' ) " + "DESC 'Number of pages in use' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesUsed }, + + { "( olmMDBAttributes:3 " + "NAME ( 'olmMDBPagesFree' ) " + "DESC 'Number of free pages' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBPagesFree }, + + { "( olmMDBAttributes:4 " + "NAME ( 'olmMDBReadersMax' ) " + "DESC 'Maximum number of readers' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBReadersMax }, + + { "( olmMDBAttributes:5 " + "NAME ( 'olmMDBReadersUsed' ) " + "DESC 'Number of readers in use' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBReadersUsed }, + + { "( olmMDBAttributes:6 " + "NAME ( 'olmMDBEntries' ) " + "DESC 'Number of entries in DB' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmMDBEntries }, + { NULL } +}; + +static struct { + char *desc; + ObjectClass **oc; +} s_oc[] = { + /* augments an existing object, so it must be AUXILIARY + * FIXME: derive from some ABSTRACT "monitoredEntity"? */ + { "( olmMDBObjectClasses:2 " + "NAME ( 'olmMDBDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmDbDirectory " +#ifdef MDB_MONITOR_IDX + "$ olmDbNotIndexed " +#endif /* MDB_MONITOR_IDX */ + "$ olmMDBPagesMax $ olmMDBPagesUsed $ olmMDBPagesFree " + "$ olmMDBReadersMax $ olmMDBReadersUsed $ olmMDBEntries " + ") )", + &oc_olmMDBDatabase }, + + { NULL } +}; + +static int +mdb_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct mdb_info *mdb = (struct mdb_info *) priv; + Attribute *a; + char buf[ BUFSIZ ]; + struct berval bv; + MDB_stat mst; + MDB_envinfo mei; + MDB_txn *txn; + int rc; + +#ifdef MDB_MONITOR_IDX + + mdb_monitor_idx_entry_add( mdb, e ); +#endif /* MDB_MONITOR_IDX */ + + mdb_env_stat( mdb->mi_dbenv, &mst ); + mdb_env_info( mdb->mi_dbenv, &mei ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesMax ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_mapsize / mst.ms_psize ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesUsed ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_last_pgno+1 ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBReadersMax ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%u", mei.me_maxreaders ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmMDBReadersUsed ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%u", mei.me_numreaders ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &txn ); + if ( !rc ) { + MDB_cursor *cursor; + MDB_val key, data; + size_t pages = 0, *iptr; + + rc = mdb_cursor_open( txn, 0, &cursor ); + if ( !rc ) { + while (( rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT )) == 0 ) { + iptr = data.mv_data; + pages += *iptr; + } + mdb_cursor_close( cursor ); + } + + mdb_stat( txn, mdb->mi_id2entry, &mst ); + a = attr_find( e->e_attrs, ad_olmMDBEntries ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mst.ms_entries ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + mdb_txn_abort( txn ); + + a = attr_find( e->e_attrs, ad_olmMDBPagesFree ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", pages ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + return SLAP_CB_CONTINUE; +} + +#if 0 /* uncomment if required */ +static int +mdb_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + return SLAP_CB_CONTINUE; +} +#endif + +static int +mdb_monitor_free( + Entry *e, + void **priv ) +{ + struct berval values[ 2 ]; + Modification mod = { 0 }; + + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + int i, rc; + + /* NOTE: if slap_shutdown != 0, priv might have already been freed */ + *priv = NULL; + + /* Remove objectClass */ + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = slap_schema.si_ad_objectClass; + mod.sm_values = values; + mod.sm_numvals = 1; + values[ 0 ] = oc_olmMDBDatabase->soc_cname; + BER_BVZERO( &values[ 1 ] ); + + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_numvals = 0; + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + mod.sm_desc = *s_at[ i ].ad; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + } + + return SLAP_CB_CONTINUE; +} + +/* + * call from within mdb_initialize() + */ +static int +mdb_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int mdb_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int mdb_monitor_initialized_failure = 1; + + if ( mdb_monitor_initialized++ ) { + return mdb_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + /* register schema here */ + + argv[ 0 ] = "back-mdb monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + c.lineno = i; + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": unable to add " + "objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid ); + return 2; + } + } + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": register_at failed for attributeType (%s)\n", + s_at[ i ].desc ); + return 3; + + } else { + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + for ( i = 0; s_oc[ i ].desc != NULL; i++ ) { + code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize) + ": register_oc failed for objectClass (%s)\n", + s_oc[ i ].desc ); + return 4; + + } else { + (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + } + + return ( mdb_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within mdb_db_init() + */ +int +mdb_monitor_db_init( BackendDB *be ) +{ +#ifdef MDB_MONITOR_IDX + struct mdb_info *mdb = (struct mdb_info *) be->be_private; +#endif /* MDB_MONITOR_IDX */ + + if ( mdb_monitor_initialize() == LDAP_SUCCESS ) { + /* monitoring in back-mdb is on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + +#ifdef MDB_MONITOR_IDX + mdb->mi_idx = NULL; + ldap_pvt_thread_mutex_init( &mdb->mi_idx_mutex ); +#endif /* MDB_MONITOR_IDX */ + + return 0; +} + +/* + * call from within mdb_db_open() + */ +int +mdb_monitor_db_open( BackendDB *be ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_CONFIG, LDAP_XSTRING(mdb_monitor_db_open) + ": monitoring disabled; " + "configure monitor database to enable\n" ); + } + + return 0; + } + + /* alloc as many as required (plus 1 for objectClass) */ + a = attrs_alloc( 1 + 7 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmMDBDatabase->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_olmMDBPagesMax; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBPagesUsed; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBPagesFree; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBReadersMax; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBReadersUsed; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmMDBEntries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + { + struct berval bv, nbv; + ber_len_t pathlen = 0, len = 0; + char path[ MAXPATHLEN ] = { '\0' }; + char *fname = mdb->mi_dbenv_home, + *ptr; + + len = strlen( fname ); + if ( fname[ 0 ] != '/' ) { + /* get full path name */ + getcwd( path, sizeof( path ) ); + pathlen = strlen( path ); + + if ( fname[ 0 ] == '.' && fname[ 1 ] == '/' ) { + fname += 2; + len -= 2; + } + } + + bv.bv_len = pathlen + STRLENOF( "/" ) + len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + STRLENOF( "/" ) + 1 ); + if ( pathlen ) { + ptr = lutil_strncopy( ptr, path, pathlen ); + ptr[ 0 ] = '/'; + ptr++; + } + ptr = lutil_strncopy( ptr, fname, len ); + if ( ptr[ -1 ] != '/' ) { + ptr[ 0 ] = '/'; + ptr++; + } + ptr[ 0 ] = '\0'; + + attr_normalize_one( ad_olmDbDirectory, &bv, &nbv, NULL ); + + next->a_desc = ad_olmDbDirectory; + next->a_vals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_vals[ 0 ] = bv; + next->a_numvals = 1; + + if ( BER_BVISNULL( &nbv ) ) { + next->a_nvals = next->a_vals; + + } else { + next->a_nvals = ch_calloc( sizeof( struct berval ), 2 ); + next->a_nvals[ 0 ] = nbv; + } + + next = next->a_next; + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = mdb_monitor_update; +#if 0 /* uncomment if required */ + cb->mc_modify = mdb_monitor_modify; +#endif + cb->mc_free = mdb_monitor_free; + cb->mc_private = (void *)mdb; + + /* make sure the database is registered; then add monitor attributes */ + rc = mbe->register_database( be, &mdb->mi_monitor.mdm_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &mdb->mi_monitor.mdm_ndn, a, cb, + NULL, -1, NULL ); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + mdb->mi_monitor.mdm_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * mdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +/* + * call from within mdb_db_close() + */ +int +mdb_monitor_db_close( BackendDB *be ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + if ( !BER_BVISNULL( &mdb->mi_monitor.mdm_ndn ) ) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && mi->bi_extra ) { + struct berval dummy = BER_BVNULL; + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &mdb->mi_monitor.mdm_ndn, + (monitor_callback_t *)mdb->mi_monitor.mdm_cb, + &dummy, 0, &dummy ); + } + + memset( &mdb->mi_monitor, 0, sizeof( mdb->mi_monitor ) ); + } + + return 0; +} + +/* + * call from within mdb_db_destroy() + */ +int +mdb_monitor_db_destroy( BackendDB *be ) +{ +#ifdef MDB_MONITOR_IDX + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + + /* TODO: free tree */ + ldap_pvt_thread_mutex_destroy( &mdb->mi_idx_mutex ); + ldap_avl_free( mdb->mi_idx, ch_free ); +#endif /* MDB_MONITOR_IDX */ + + return 0; +} + +#ifdef MDB_MONITOR_IDX + +#define MDB_MONITOR_IDX_TYPES (4) + +typedef struct monitor_idx_t monitor_idx_t; + +struct monitor_idx_t { + AttributeDescription *idx_ad; + unsigned long idx_count[MDB_MONITOR_IDX_TYPES]; +}; + +static int +mdb_monitor_bitmask2key( slap_mask_t bitmask ) +{ + int key; + + for ( key = 0; key < 8 * (int)sizeof(slap_mask_t) && !( bitmask & 0x1U ); + key++ ) + bitmask >>= 1; + + return key; +} + +static struct berval idxbv[] = { + BER_BVC( "present=" ), + BER_BVC( "equality=" ), + BER_BVC( "approx=" ), + BER_BVC( "substr=" ), + BER_BVNULL +}; + +static ber_len_t +mdb_monitor_idx2len( monitor_idx_t *idx ) +{ + int i; + ber_len_t len = 0; + + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] != 0 ) { + len += idxbv[i].bv_len; + } + } + + return len; +} + +static int +monitor_idx_cmp( const void *p1, const void *p2 ) +{ + const monitor_idx_t *idx1 = (const monitor_idx_t *)p1; + const monitor_idx_t *idx2 = (const monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ); +} + +static int +monitor_idx_dup( void *p1, void *p2 ) +{ + monitor_idx_t *idx1 = (monitor_idx_t *)p1; + monitor_idx_t *idx2 = (monitor_idx_t *)p2; + + return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ) == 0 ? -1 : 0; +} + +int +mdb_monitor_idx_add( + struct mdb_info *mdb, + AttributeDescription *desc, + slap_mask_t type ) +{ + monitor_idx_t idx_dummy = { 0 }, + *idx; + int rc = 0, key; + + idx_dummy.idx_ad = desc; + key = mdb_monitor_bitmask2key( type ) - 1; + if ( key >= MDB_MONITOR_IDX_TYPES ) { + /* invalid index type */ + return -1; + } + + ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex ); + + idx = (monitor_idx_t *)ldap_avl_find( mdb->mi_idx, + (caddr_t)&idx_dummy, monitor_idx_cmp ); + if ( idx == NULL ) { + idx = (monitor_idx_t *)ch_calloc( sizeof( monitor_idx_t ), 1 ); + idx->idx_ad = desc; + idx->idx_count[ key ] = 1; + + switch ( ldap_avl_insert( &mdb->mi_idx, (caddr_t)idx, + monitor_idx_cmp, monitor_idx_dup ) ) + { + case 0: + break; + + default: + ch_free( idx ); + rc = -1; + } + + } else { + idx->idx_count[ key ]++; + } + + ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex ); + + return rc; +} + +static int +mdb_monitor_idx_apply( void *v_idx, void *v_valp ) +{ + monitor_idx_t *idx = (monitor_idx_t *)v_idx; + BerVarray *valp = (BerVarray *)v_valp; + + struct berval bv; + char *ptr; + char count_buf[ MDB_MONITOR_IDX_TYPES ][ SLAP_TEXT_BUFLEN ]; + ber_len_t count_len[ MDB_MONITOR_IDX_TYPES ], + idx_len; + int i, num = 0; + + idx_len = mdb_monitor_idx2len( idx ); + + bv.bv_len = 0; + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + count_len[ i ] = snprintf( count_buf[ i ], + sizeof( count_buf[ i ] ), "%lu", idx->idx_count[ i ] ); + bv.bv_len += count_len[ i ]; + num++; + } + + bv.bv_len += idx->idx_ad->ad_cname.bv_len + + num + + idx_len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( ptr, idx->idx_ad->ad_cname.bv_val ); + for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) { + if ( idx->idx_count[ i ] == 0 ) { + continue; + } + + ptr[ 0 ] = '#'; + ++ptr; + ptr = lutil_strcopy( ptr, idxbv[ i ].bv_val ); + ptr = lutil_strcopy( ptr, count_buf[ i ] ); + } + + ber_bvarray_add( valp, &bv ); + + return 0; +} + +static int +mdb_monitor_idx_entry_add( + struct mdb_info *mdb, + Entry *e ) +{ + BerVarray vals = NULL; + Attribute *a; + + a = attr_find( e->e_attrs, ad_olmDbNotIndexed ); + + ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex ); + + ldap_avl_apply( mdb->mi_idx, mdb_monitor_idx_apply, + &vals, -1, AVL_INORDER ); + + ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex ); + + if ( vals != NULL ) { + if ( a != NULL ) { + assert( a->a_nvals == a->a_vals ); + + ber_bvarray_free( a->a_vals ); + + } else { + Attribute **ap; + + for ( ap = &e->e_attrs; *ap != NULL; ap = &(*ap)->a_next ) + ; + *ap = attr_alloc( ad_olmDbNotIndexed ); + a = *ap; + } + a->a_vals = vals; + a->a_nvals = a->a_vals; + } + + return 0; +} + +#endif /* MDB_MONITOR_IDX */ diff --git a/servers/slapd/back-mdb/nextid.c b/servers/slapd/back-mdb/nextid.c new file mode 100644 index 0000000..b342b48 --- /dev/null +++ b/servers/slapd/back-mdb/nextid.c @@ -0,0 +1,53 @@ +/* init.c - initialize mdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *out ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + int rc; + ID id = 0; + MDB_val key; + + rc = mdb_cursor_get(mc, &key, NULL, MDB_LAST); + + switch(rc) { + case MDB_NOTFOUND: + rc = 0; + *out = 1; + break; + case 0: + memcpy( &id, key.mv_data, sizeof( id )); + *out = ++id; + break; + + default: + Debug( LDAP_DEBUG_ANY, + "=> mdb_next_id: get failed: %s (%d)\n", + mdb_strerror(rc), rc ); + goto done; + } + mdb->mi_nextid = *out; + +done: + return rc; +} diff --git a/servers/slapd/back-mdb/operational.c b/servers/slapd/back-mdb/operational.c new file mode 100644 index 0000000..e2d4495 --- /dev/null +++ b/servers/slapd/back-mdb/operational.c @@ -0,0 +1,121 @@ +/* operational.c - mdb backend operational attributes function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "back-mdb.h" + +/* + * sets *hasSubordinates to LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE + * if the entry has children or not. + */ +int +mdb_hasSubordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + MDB_txn *rtxn; + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + int rc; + + assert( e != NULL ); + + rc = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rc) { + case 0: + break; + default: + rc = LDAP_OTHER; + goto done; + } + + rtxn = moi->moi_txn; + + rc = mdb_dn2id_children( op, rtxn, e ); + + switch( rc ) { + case 0: + *hasSubordinates = LDAP_COMPARE_TRUE; + break; + + case MDB_NOTFOUND: + *hasSubordinates = LDAP_COMPARE_FALSE; + rc = LDAP_SUCCESS; + break; + + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(mdb_hasSubordinates) + ": has_children failed: %s (%d)\n", + mdb_strerror(rc), rc ); + rc = LDAP_OTHER; + } + +done:; + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +int +mdb_operational( + Operation *op, + SlapReply *rs ) +{ + Attribute **ap; + + assert( rs->sr_entry != NULL ); + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) { + if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) { + break; + } + } + + if ( *ap == NULL && + attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL && + ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) ) + { + int hasSubordinates, rc; + + rc = mdb_hasSubordinates( op, rs->sr_entry, &hasSubordinates ); + if ( rc == LDAP_SUCCESS ) { + *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE ); + assert( *ap != NULL ); + + ap = &(*ap)->a_next; + } + } + + return LDAP_SUCCESS; +} + diff --git a/servers/slapd/back-mdb/proto-mdb.h b/servers/slapd/back-mdb/proto-mdb.h new file mode 100644 index 0000000..25fdee7 --- /dev/null +++ b/servers/slapd/back-mdb/proto-mdb.h @@ -0,0 +1,411 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#ifndef _PROTO_MDB_H +#define _PROTO_MDB_H + +LDAP_BEGIN_DECL + +#define MDB_UCTYPE "MDB" + +/* + * attr.c + */ + +AttrInfo *mdb_attr_mask( struct mdb_info *mdb, + AttributeDescription *desc ); + +void mdb_attr_flush( struct mdb_info *mdb ); + +int mdb_attr_slot( struct mdb_info *mdb, + AttributeDescription *desc, int *insert ); + +int mdb_attr_dbs_open( BackendDB *be, MDB_txn *txn, struct config_reply_s *cr ); +void mdb_attr_dbs_close( struct mdb_info *mdb ); + +int mdb_attr_index_config LDAP_P(( struct mdb_info *mdb, + const char *fname, int lineno, + int argc, char **argv, struct config_reply_s *cr )); + +void mdb_attr_index_unparse LDAP_P(( struct mdb_info *mdb, BerVarray *bva )); +void mdb_attr_index_destroy LDAP_P(( struct mdb_info *mdb )); +void mdb_attr_index_free LDAP_P(( struct mdb_info *mdb, + AttributeDescription *ad )); + +int mdb_attr_multi_config LDAP_P(( struct mdb_info *mdb, + const char *fname, int lineno, + int argc, char **argv, struct config_reply_s *cr )); + +void mdb_attr_multi_unparse LDAP_P(( struct mdb_info *mdb, BerVarray *bva )); + +void mdb_attr_multi_thresh LDAP_P(( struct mdb_info *mdb, AttributeDescription *ad, + unsigned *hi, unsigned *lo )); + +void mdb_attr_info_free( AttrInfo *ai ); + +int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn ); +int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad ); +void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads ); + +/* + * config.c + */ + +int mdb_back_init_cf( BackendInfo *bi ); + +/* + * dn2entry.c + */ + +int mdb_dn2entry LDAP_P(( Operation *op, MDB_txn *tid, MDB_cursor *mc, + struct berval *dn, Entry **e, ID *nsubs, int matched )); + +/* + * dn2id.c + */ + +int mdb_dn2id( + Operation *op, + MDB_txn *txn, + MDB_cursor *mc, + struct berval *ndn, + ID *id, + ID *nsubs, + struct berval *matched, + struct berval *nmatched ); + +int mdb_dn2id_add( + Operation *op, + MDB_cursor *mcp, + MDB_cursor *mcd, + ID pid, + ID nsubs, + int upsub, + Entry *e ); + +int mdb_dn2id_delete( + Operation *op, + MDB_cursor *mc, + ID id, + ID nsubs ); + +int mdb_dn2id_children( + Operation *op, + MDB_txn *tid, + Entry *e ); + +int mdb_dn2sups ( + Operation *op, + MDB_txn *tid, + struct berval *dn, + ID *sups + ); + +int mdb_dn2idl( + Operation *op, + MDB_txn *txn, + struct berval *ndn, + ID eid, + ID *ids, + ID *stack ); + +int mdb_dn2id_parent( + Operation *op, + MDB_txn *txn, + ID eid, + ID *idp ); + +int mdb_id2name( + Operation *op, + MDB_txn *txn, + MDB_cursor **cursp, + ID eid, + struct berval *name, + struct berval *nname); + +int mdb_idscope( + Operation *op, + MDB_txn *txn, + ID base, + ID *ids, + ID *res ); + +struct IdScopes; + +int mdb_idscopes( + Operation *op, + struct IdScopes *isc ); + +int mdb_idscopechk( + Operation *op, + struct IdScopes *isc ); + +int mdb_dn2id_walk( + Operation *op, + struct IdScopes *isc ); + +void mdb_dn2id_wrestore( + Operation *op, + struct IdScopes *isc ); + +MDB_cmp_func mdb_dup_compare; + +/* + * filterentry.c + */ + +int mdb_filter_candidates( + Operation *op, + MDB_txn *txn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ); + +/* + * id2entry.c + */ + +MDB_cmp_func mdb_id2v_compare; +MDB_cmp_func mdb_id2v_dupsort; + +int mdb_id2entry_add( + Operation *op, + MDB_txn *tid, + MDB_cursor *mc, + Entry *e ); + +int mdb_id2entry_update( + Operation *op, + MDB_txn *tid, + MDB_cursor *mc, + Entry *e ); + +int mdb_id2entry_delete( + BackendDB *be, + MDB_txn *tid, + Entry *e); + +int mdb_id2entry( + Operation *op, + MDB_cursor *mc, + ID id, + Entry **e); + +int mdb_id2edata( + Operation *op, + MDB_cursor *mc, + ID id, + MDB_val *data); + +int mdb_entry_return( Operation *op, Entry *e ); +BI_entry_release_rw mdb_entry_release; +BI_entry_get_rw mdb_entry_get; +BI_op_txn mdb_txn; + +int mdb_entry_decode( Operation *op, MDB_txn *txn, MDB_val *data, ID id, Entry **e ); + +void mdb_reader_flush( MDB_env *env ); +int mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moi ); + +int mdb_mval_put(Operation *op, MDB_cursor *mc, ID id, Attribute *a); +int mdb_mval_del(Operation *op, MDB_cursor *mc, ID id, Attribute *a); + +/* + * idl.c + */ + +unsigned mdb_idl_search( ID *ids, ID id ); + +int mdb_idl_fetch_key( + BackendDB *be, + MDB_txn *txn, + MDB_dbi dbi, + MDB_val *key, + ID *ids, + MDB_cursor **saved_cursor, + int get_flag ); + +int mdb_idl_insert( ID *ids, ID id ); + +typedef int (mdb_idl_keyfunc)( + BackendDB *be, + MDB_cursor *mc, + struct berval *key, + ID id ); + +mdb_idl_keyfunc mdb_idl_insert_keys; +mdb_idl_keyfunc mdb_idl_delete_keys; + +int +mdb_idl_intersection( + ID *a, + ID *b ); + +int +mdb_idl_union( + ID *a, + ID *b ); + +ID mdb_idl_first( ID *ids, ID *cursor ); +ID mdb_idl_next( ID *ids, ID *cursor ); + +void mdb_idl_sort( ID *ids, ID *tmp ); +int mdb_idl_append( ID *a, ID *b ); +int mdb_idl_append_one( ID *ids, ID id ); + + +/* + * index.c + */ + +extern AttrInfo * +mdb_index_mask LDAP_P(( + Backend *be, + AttributeDescription *desc, + struct berval *name )); + +extern int +mdb_index_param LDAP_P(( + Backend *be, + AttributeDescription *desc, + int ftype, + MDB_dbi *dbi, + slap_mask_t *mask, + struct berval *prefix )); + +extern int +mdb_index_values LDAP_P(( + Operation *op, + MDB_txn *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid )); + +extern int +mdb_index_recset LDAP_P(( + struct mdb_info *mdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir )); + +extern int +mdb_index_recrun LDAP_P(( + Operation *op, + MDB_txn *txn, + struct mdb_info *mdb, + IndexRec *ir, + ID id, + int base )); + +int mdb_index_entry LDAP_P(( Operation *op, MDB_txn *t, int r, Entry *e )); + +#define mdb_index_entry_add(op,t,e) \ + mdb_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e)) +#define mdb_index_entry_del(op,t,e) \ + mdb_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e)) + +/* + * key.c + */ + +extern int +mdb_key_read( + Backend *be, + MDB_txn *txn, + MDB_dbi dbi, + struct berval *k, + ID *ids, + MDB_cursor **saved_cursor, + int get_flags ); + +/* + * nextid.c + */ + +int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *id ); + +/* + * modify.c + */ + +int mdb_modify_internal( + Operation *op, + MDB_txn *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ); + +/* + * monitor.c + */ + +int mdb_monitor_db_init( BackendDB *be ); +int mdb_monitor_db_open( BackendDB *be ); +int mdb_monitor_db_close( BackendDB *be ); +int mdb_monitor_db_destroy( BackendDB *be ); + +#ifdef MDB_MONITOR_IDX +int +mdb_monitor_idx_add( + struct mdb_info *mdb, + AttributeDescription *desc, + slap_mask_t type ); +#endif /* MDB_MONITOR_IDX */ + +/* + * former external.h + */ + +extern BI_init mdb_back_initialize; + +extern BI_db_config mdb_db_config; + +extern BI_op_add mdb_add; +extern BI_op_bind mdb_bind; +extern BI_op_compare mdb_compare; +extern BI_op_delete mdb_delete; +extern BI_op_modify mdb_modify; +extern BI_op_modrdn mdb_modrdn; +extern BI_op_search mdb_search; +extern BI_op_extended mdb_extended; + +extern BI_chk_referrals mdb_referrals; + +extern BI_operational mdb_operational; + +extern BI_has_subordinates mdb_hasSubordinates; + +/* tools.c */ +extern BI_tool_entry_open mdb_tool_entry_open; +extern BI_tool_entry_close mdb_tool_entry_close; +extern BI_tool_entry_first_x mdb_tool_entry_first_x; +extern BI_tool_entry_next mdb_tool_entry_next; +extern BI_tool_entry_get mdb_tool_entry_get; +extern BI_tool_entry_put mdb_tool_entry_put; +extern BI_tool_entry_reindex mdb_tool_entry_reindex; +extern BI_tool_dn2id_get mdb_tool_dn2id_get; +extern BI_tool_entry_modify mdb_tool_entry_modify; +extern BI_tool_entry_delete mdb_tool_entry_delete; + +extern mdb_idl_keyfunc mdb_tool_idl_add; + +LDAP_END_DECL + +#endif /* _PROTO_MDB_H */ diff --git a/servers/slapd/back-mdb/referral.c b/servers/slapd/back-mdb/referral.c new file mode 100644 index 0000000..3164890 --- /dev/null +++ b/servers/slapd/back-mdb/referral.c @@ -0,0 +1,151 @@ +/* referral.c - MDB backend referral handler */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" + +int +mdb_referrals( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + Entry *e = NULL; + int rc = LDAP_SUCCESS; + + MDB_txn *rtxn; + mdb_op_info opinfo = {0}, *moi = &opinfo; + + if( op->o_tag == LDAP_REQ_SEARCH ) { + /* let search take care of itself */ + return rc; + } + + if( get_manageDSAit( op ) ) { + /* let op take care of DSA management */ + return rc; + } + + rc = mdb_opinfo_get(op, mdb, 1, &moi); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + + rtxn = moi->moi_txn; + + /* get entry */ + rc = mdb_dn2entry( op, rtxn, &op->o_req_ndn, &e, 1 ); + + switch(rc) { + case MDB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto done; + default: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": dn2entry failed: %s (%d)\n", + mdb_strerror(rc), rc ); + rs->sr_text = "internal error"; + rc = LDAP_OTHER; + goto done; + } + + if ( rc == MDB_NOTFOUND ) { + rc = LDAP_SUCCESS; + rs->sr_matched = NULL; + if ( e != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + if( is_entry_referral( e ) ) { + BerVarray ref = get_entry_referrals( op, e ); + rc = LDAP_OTHER; + rs->sr_ref = referral_rewrite( ref, &e->e_name, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + ber_bvarray_free( ref ); + if ( rs->sr_ref ) { + rs->sr_matched = ber_strdup_x( + e->e_name.bv_val, op->o_tmpmemctx ); + } + } + + mdb_entry_return( op, e ); + e = NULL; + } + + if( rs->sr_ref != NULL ) { + /* send referrals */ + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else if ( rc != LDAP_SUCCESS ) { + rs->sr_text = rs->sr_matched ? "bad referral object" : NULL; + } + + if (rs->sr_matched) { + op->o_tmpfree( (char *)rs->sr_matched, op->o_tmpmemctx ); + rs->sr_matched = NULL; + } + goto done; + } + + if ( is_entry_referral( e ) ) { + /* entry is a referral */ + BerVarray refs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( + refs, &e->e_name, &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_referrals) + ": tag=%lu target=\"%s\" matched=\"%s\"\n", + (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val ); + + rs->sr_matched = e->e_name.bv_val; + if( rs->sr_ref != NULL ) { + rc = rs->sr_err = LDAP_REFERRAL; + send_ldap_result( op, rs ); + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } else { + rc = LDAP_OTHER; + rs->sr_text = "bad referral object"; + } + + rs->sr_matched = NULL; + ber_bvarray_free( refs ); + } + +done: + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + if ( e ) + mdb_entry_return( op, e ); + return rc; +} diff --git a/servers/slapd/back-mdb/search.c b/servers/slapd/back-mdb/search.c new file mode 100644 index 0000000..0889597 --- /dev/null +++ b/servers/slapd/back-mdb/search.c @@ -0,0 +1,1543 @@ +/* search.c - search operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-mdb.h" +#include "idl.h" + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ); + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + IdScopes *isc, + MDB_cursor *mci, + ID *ids, + ID *stack ); + +static int parse_paged_cookie( Operation *op, SlapReply *rs ); + +static void send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ); + +/* Dereference aliases for a single alias entry. Return the final + * dereferenced entry on success, NULL on any failure. + */ +static Entry * deref_base ( + Operation *op, + SlapReply *rs, + Entry *e, + Entry **matched, + MDB_txn *txn, + ID *tmp, + ID *visited ) +{ + struct berval ndn; + + rs->sr_err = LDAP_ALIAS_DEREF_PROBLEM; + rs->sr_text = "maximum deref depth exceeded"; + + for (;;) { + /* Remember the last entry we looked at, so we can + * report broken links + */ + *matched = e; + + if (MDB_IDL_N(tmp) >= op->o_bd->be_max_deref_depth) { + e = NULL; + break; + } + + /* If this is part of a subtree or onelevel search, + * have we seen this ID before? If so, quit. + */ + if ( visited && mdb_idl_insert( visited, e->e_id ) ) { + e = NULL; + break; + } + + /* If we've seen this ID during this deref iteration, + * we've hit a loop. + */ + if ( mdb_idl_insert( tmp, e->e_id ) ) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "circular alias"; + e = NULL; + break; + } + + /* If there was a problem getting the aliasedObjectName, + * get_alias_dn will have set the error status. + */ + if ( get_alias_dn(e, &ndn, &rs->sr_err, &rs->sr_text) ) { + e = NULL; + break; + } + + rs->sr_err = mdb_dn2entry( op, txn, NULL, &ndn, &e, NULL, 0 ); + if (rs->sr_err) { + rs->sr_err = LDAP_ALIAS_PROBLEM; + rs->sr_text = "aliasedObject not found"; + break; + } + + /* Free the previous entry, continue to work with the + * one we just retrieved. + */ + mdb_entry_return( op, *matched ); + + /* We found a regular entry. Return this to the caller. + */ + if (!is_entry_alias(e)) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + break; + } + } + return e; +} + +/* Look for and dereference all aliases within the search scope. + * Requires "stack" to be able to hold 6 levels of DB_SIZE IDLs. + * Of course we're hardcoded to require a minimum of 8 UM_SIZE + * IDLs so this is never a problem. + */ +static int search_aliases( + Operation *op, + SlapReply *rs, + ID e_id, + IdScopes *isc, + MDB_cursor *mci, + ID *stack ) +{ + ID *aliases, *curscop, *visited, *newsubs, *oldsubs, *tmp; + ID cursora, ida, cursoro, ido; + Entry *matched, *a; + struct berval bv_alias = BER_BVC( "alias" ); + AttributeAssertion aa_alias = ATTRIBUTEASSERTION_INIT; + Filter af; + + aliases = stack; /* IDL of all aliases in the database */ + curscop = aliases + MDB_idl_db_size; /* Aliases in the current scope */ + visited = curscop + MDB_idl_db_size; /* IDs we've seen in this search */ + newsubs = visited + MDB_idl_db_size; /* New subtrees we've added */ + oldsubs = newsubs + MDB_idl_db_size; /* Subtrees added previously */ + tmp = oldsubs + MDB_idl_db_size; /* Scratch space for deref_base() */ + + af.f_choice = LDAP_FILTER_EQUALITY; + af.f_ava = &aa_alias; + af.f_av_desc = slap_schema.si_ad_objectClass; + af.f_av_value = bv_alias; + af.f_next = NULL; + + /* Find all aliases in database */ + MDB_IDL_ZERO( aliases ); + rs->sr_err = mdb_filter_candidates( op, isc->mt, &af, aliases, + curscop, visited ); + if (rs->sr_err != LDAP_SUCCESS || MDB_IDL_IS_ZERO( aliases )) { + return rs->sr_err; + } + if ( op->ors_limit /* isroot == FALSE */ && + op->ors_limit->lms_s_unchecked != -1 && + MDB_IDL_N( aliases ) > (unsigned) op->ors_limit->lms_s_unchecked ) + { + return LDAP_ADMINLIMIT_EXCEEDED; + } + oldsubs[0] = 1; + oldsubs[1] = e_id; + + MDB_IDL_ZERO( visited ); + MDB_IDL_ZERO( newsubs ); + + cursoro = 0; + ido = mdb_idl_first( oldsubs, &cursoro ); + + for (;;) { + /* Set curscop to only the aliases in the current scope. Start with + * all the aliases, then get the intersection with the scope. + */ + rs->sr_err = mdb_idscope( op, isc->mt, e_id, aliases, curscop ); + + /* Dereference all of the aliases in the current scope. */ + cursora = 0; + for (ida = mdb_idl_first(curscop, &cursora); ida != NOID; + ida = mdb_idl_next(curscop, &cursora)) + { + rs->sr_err = mdb_id2entry(op, mci, ida, &a); + if (rs->sr_err != LDAP_SUCCESS) { + continue; + } + + /* This should only happen if the curscop IDL has maxed out and + * turned into a range that spans IDs indiscriminately + */ + if (!is_entry_alias(a)) { + mdb_entry_return(op, a); + continue; + } + + /* Actually dereference the alias */ + MDB_IDL_ZERO(tmp); + a = deref_base( op, rs, a, &matched, isc->mt, + tmp, visited ); + if (a) { + /* If the target was not already in our current scopes, + * make note of it in the newsubs list. + */ + ID2 mid; + mid.mid = a->e_id; + mid.mval.mv_data = NULL; + if (op->ors_scope == LDAP_SCOPE_SUBTREE) { + isc->id = a->e_id; + /* if ID is a child of any of our current scopes, + * ignore it, it's already included. + */ + if (mdb_idscopechk(op, isc)) + goto skip; + } + if (mdb_id2l_insert(isc->scopes, &mid) == 0) { + mdb_idl_insert(newsubs, a->e_id); + } +skip: mdb_entry_return( op, a ); + + } else if (matched) { + /* Alias could not be dereferenced, or it deref'd to + * an ID we've already seen. Ignore it. + */ + mdb_entry_return( op, matched ); + rs->sr_text = NULL; + rs->sr_err = 0; + } + } + /* If this is a OneLevel search, we're done; oldsubs only had one + * ID in it. For a Subtree search, oldsubs may be a list of scope IDs. + */ + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) break; +nextido: + ido = mdb_idl_next( oldsubs, &cursoro ); + + /* If we're done processing the old scopes, did we add any new + * scopes in this iteration? If so, go back and do those now. + */ + if (ido == NOID) { + if (MDB_IDL_IS_ZERO(newsubs)) break; + MDB_IDL_CPY(oldsubs, newsubs); + MDB_IDL_ZERO(newsubs); + cursoro = 0; + ido = mdb_idl_first( oldsubs, &cursoro ); + } + + /* Find the entry corresponding to the next scope. If it can't + * be found, ignore it and move on. This should never happen; + * we should never see the ID of an entry that doesn't exist. + */ + { + MDB_val edata; + rs->sr_err = mdb_id2edata(op, mci, ido, &edata); + if ( rs->sr_err != MDB_SUCCESS ) { + goto nextido; + } + e_id = ido; + } + } + return rs->sr_err; +} + +/* Get the next ID from the DB. Used if the candidate list is + * a range and simple iteration hits missing entryIDs + */ +static int +mdb_get_nextid(MDB_cursor *mci, ID *cursor) +{ + MDB_val key; + ID id; + int rc; + + id = *cursor + 1; + key.mv_data = &id; + key.mv_size = sizeof(ID); + rc = mdb_cursor_get( mci, &key, NULL, MDB_SET_RANGE ); + if ( rc ) + return rc; + memcpy( cursor, key.mv_data, sizeof(ID)); + return 0; +} + +static void scope_chunk_free( void *key, void *data ) +{ + ID2 *p1, *p2; + for (p1 = data; p1; p1 = p2) { + p2 = p1[0].mval.mv_data; + ber_memfree_x(p1, NULL); + } +} + +static ID2 *scope_chunk_get( Operation *op ) +{ + ID2 *ret = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get, + (void *)&ret, NULL ); + if ( !ret ) { + ret = ch_malloc( MDB_idl_um_size * sizeof( ID2 )); + } else { + void *r2 = ret[0].mval.mv_data; + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get, + r2, scope_chunk_free, NULL, NULL ); + } + return ret; +} + +static void scope_chunk_ret( Operation *op, ID2 *scopes ) +{ + void *ret = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get, + &ret, NULL ); + scopes[0].mval.mv_data = ret; + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get, + (void *)scopes, scope_chunk_free, NULL, NULL ); +} + +static void *search_stack( Operation *op ); + +typedef struct ww_ctx { + MDB_txn *txn; + MDB_cursor *mcd; /* if set, save cursor context */ + ID key; + MDB_val data; + int flag; + unsigned nentries; +} ww_ctx; + +/* ITS#7904 if we get blocked while writing results to client, + * release the current reader txn and reacquire it after we + * unblock. + * Slight problem - if we're doing a scope-based walk (mdb_dn2id_walk) + * to return results, we need to remember the state of the mcd cursor. + * If the node that cursor was pointing to gets deleted while we're + * blocked, we may be unable to restore the cursor position. In that + * case return an LDAP_BUSY error - let the client know this search + * couldn't succeed, but might succeed on a retry. + */ +static void +mdb_rtxn_snap( Operation *op, ww_ctx *ww ) +{ + /* save cursor position and release read txn */ + if ( ww->mcd ) { + MDB_val key, data; + mdb_cursor_get( ww->mcd, &key, &data, MDB_GET_CURRENT ); + memcpy( &ww->key, key.mv_data, sizeof(ID) ); + ww->data.mv_size = data.mv_size; + ww->data.mv_data = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx ); + memcpy(ww->data.mv_data, data.mv_data, data.mv_size); + } + mdb_txn_reset( ww->txn ); + ww->flag = 1; +} + +static void +mdb_writewait( Operation *op, slap_callback *sc ) +{ + ww_ctx *ww = sc->sc_private; + if ( !ww->flag ) { + mdb_rtxn_snap( op, ww ); + } +} + +static int +mdb_waitfixup( Operation *op, ww_ctx *ww, MDB_cursor *mci, MDB_cursor *mcd, IdScopes *isc ) +{ + MDB_val key; + int rc = 0; + ww->flag = 0; + mdb_txn_renew( ww->txn ); + mdb_cursor_renew( ww->txn, mci ); + mdb_cursor_renew( ww->txn, mcd ); + + key.mv_size = sizeof(ID); + if ( ww->mcd ) { /* scope-based search using dn2id_walk */ + MDB_val data; + + if ( isc->numrdns ) + mdb_dn2id_wrestore( op, isc ); + + key.mv_data = &ww->key; + data = ww->data; + rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH ); + if ( rc == MDB_NOTFOUND ) { + data = ww->data; + rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH_RANGE ); + /* the loop will skip this node using NEXT_DUP but we want it + * sent, so go back one space first + */ + if ( rc == MDB_SUCCESS ) + mdb_cursor_get( mcd, &key, &data, MDB_PREV_DUP ); + else + rc = LDAP_BUSY; + } else if ( rc ) { + rc = LDAP_OTHER; + } + op->o_tmpfree( ww->data.mv_data, op->o_tmpmemctx ); + ww->data.mv_data = NULL; + } else if ( isc->scopes[0].mid > 1 ) { /* candidate-based search */ + int i; + for ( i=1; i<=isc->scopes[0].mid; i++ ) { + if ( !isc->scopes[i].mval.mv_data ) + continue; + key.mv_data = &isc->scopes[i].mid; + mdb_cursor_get( mcd, &key, &isc->scopes[i].mval, MDB_SET ); + } + } + return rc; +} + +int +mdb_search( Operation *op, SlapReply *rs ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + ID id, cursor, nsubs, ncand, cscope; + ID lastid = NOID; + ID *candidates, *iscopes, *c0; + ID2 *scopes; + void *stack; + Entry *e = NULL, *base = NULL; + Entry *matched = NULL; + AttributeName *attrs; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + int tentries = 0; + int admincheck = 0; + IdScopes isc; + MDB_cursor *mci, *mcd; + ww_ctx wwctx; + slap_callback cb = { 0 }; + + mdb_op_info opinfo = {{{0}}}, *moi = &opinfo; + MDB_txn *ltid = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_search) "\n" ); + attrs = op->oq_search.rs_attrs; + + manageDSAit = get_manageDSAit( op ); + + rs->sr_err = mdb_opinfo_get( op, mdb, 1, &moi ); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + ltid = moi->moi_txn; + + rs->sr_err = mdb_cursor_open( ltid, mdb->mi_id2entry, &mci ); + if ( rs->sr_err ) { + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + rs->sr_err = mdb_cursor_open( ltid, mdb->mi_dn2id, &mcd ); + if ( rs->sr_err ) { + mdb_cursor_close( mci ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + scopes = scope_chunk_get( op ); + candidates = c0 = search_stack( op ); + iscopes = candidates + MDB_idl_um_size; + stack = iscopes + MDB_idl_db_size; + /* if candidates already in use, alloc a new array */ + if ( c0[0] ) { + candidates = ch_malloc(( MDB_idl_um_size + MDB_idl_db_size ) * sizeof ( ID )); + iscopes = candidates + MDB_idl_um_size; + } + isc.mt = ltid; + isc.mc = mcd; + isc.scopes = scopes; + isc.oscope = op->ors_scope; + isc.sctmp = stack; + + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + MDB_IDL_ZERO(candidates); + } +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = mdb_dn2entry( op, ltid, mcd, &op->o_req_ndn, &e, &nsubs, 1 ); + + switch(rs->sr_err) { + case MDB_NOTFOUND: + matched = e; + e = NULL; + break; + case 0: + break; + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + goto done; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + goto done; + } + + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + if ( matched && is_entry_alias( matched )) { + struct berval stub; + + stub.bv_val = op->o_req_ndn.bv_val; + stub.bv_len = op->o_req_ndn.bv_len - matched->e_nname.bv_len - 1; + e = deref_base( op, rs, matched, &matched, ltid, + candidates, NULL ); + if ( e ) { + build_new_dn( &op->o_req_ndn, &e->e_nname, &stub, + op->o_tmpmemctx ); + mdb_entry_return(op, e); + matched = NULL; + goto dn2entry_retry; + } + } else if ( e && is_entry_alias( e )) { + e = deref_base( op, rs, e, &matched, ltid, + candidates, NULL ); + } + } + + if ( e == NULL ) { + struct berval matched_dn = BER_BVNULL; + + if ( matched != NULL ) { + BerVarray erefs = NULL; + + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, matched, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + ber_dupbv( &matched_dn, &matched->e_name ); + + erefs = is_entry_referral( matched ) + ? get_entry_referrals( op, matched ) + : NULL; + if ( rs->sr_err == MDB_NOTFOUND ) + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = matched_dn.bv_val; + } + + mdb_entry_return(op, matched); + matched = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + } + + } else { + rs->sr_ref = referral_rewrite( default_referral, + NULL, &op->o_req_dn, op->oq_search.rs_scope ); + rs->sr_err = rs->sr_ref != NULL ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT; + } + + send_ldap_result( op, rs ); + + if ( rs->sr_ref ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + if ( !BER_BVISNULL( &matched_dn ) ) { + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + } + goto done; + } + + /* NOTE: __NEW__ "search" access is required + * on searchBase object */ + if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry, + NULL, ACL_SEARCH, NULL, &mask ) ) + { + if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + } + + mdb_entry_return( op,e); + send_ldap_result( op, rs ); + goto done; + } + + if ( !manageDSAit && is_entry_referral( e ) ) { + /* entry is a referral */ + struct berval matched_dn = BER_BVNULL; + BerVarray erefs = NULL; + + ber_dupbv( &matched_dn, &e->e_name ); + erefs = get_entry_referrals( op, e ); + + rs->sr_err = LDAP_REFERRAL; + + mdb_entry_return( op, e ); + e = NULL; + + if ( erefs ) { + rs->sr_ref = referral_rewrite( erefs, &matched_dn, + &op->o_req_dn, op->oq_search.rs_scope ); + ber_bvarray_free( erefs ); + + if ( !rs->sr_ref ) { + rs->sr_text = "bad_referral object"; + } + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) ": entry is referral\n" ); + + rs->sr_matched = matched_dn.bv_val; + send_ldap_result( op, rs ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + ber_memfree( matched_dn.bv_val ); + rs->sr_matched = NULL; + goto done; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + mdb_entry_return( op,e); + send_ldap_result( op, rs ); + goto done; + } + + /* compute it anyway; root does not use it */ + stoptime = op->o_time + op->ors_tlimit; + + base = e; + + e = NULL; + + /* select candidates */ + if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) { + rs->sr_err = base_candidate( op->o_bd, base, candidates ); + scopes[0].mid = 0; + ncand = 1; + } else { + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) { + size_t nkids; + MDB_val key, data; + key.mv_data = &base->e_id; + key.mv_size = sizeof( ID ); + mdb_cursor_get( mcd, &key, &data, MDB_SET ); + mdb_cursor_count( mcd, &nkids ); + nsubs = nkids - 1; + } else if ( !base->e_id ) { + /* we don't maintain nsubs for entryID 0. + * just grab entry count from id2entry stat + */ + MDB_stat ms; + mdb_stat( ltid, mdb->mi_id2entry, &ms ); + nsubs = ms.ms_entries; + } + MDB_IDL_ZERO( candidates ); + scopes[0].mid = 1; + scopes[1].mid = base->e_id; + scopes[1].mval.mv_data = NULL; + rs->sr_err = search_candidates( op, rs, base, + &isc, mci, candidates, stack ); + + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) { +adminlimit: + rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + ncand = MDB_IDL_N( candidates ); + if ( !base->e_id || ncand == NOID ) { + /* grab entry count from id2entry stat + */ + MDB_stat ms; + mdb_stat( ltid, mdb->mi_id2entry, &ms ); + if ( !base->e_id ) + nsubs = ms.ms_entries; + if ( ncand == NOID ) + ncand = ms.ms_entries; + } + } + + /* start cursor at beginning of candidates. + */ + cursor = 0; + + if ( candidates[0] == 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) ": no candidates\n" ); + + goto nochange; + } + + /* if not root and candidates exceed to-be-checked entries, abort */ + if ( op->ors_limit /* isroot == FALSE */ && + op->ors_limit->lms_s_unchecked != -1 && + ncand > (unsigned) op->ors_limit->lms_s_unchecked ) + { + admincheck = 1; + } + + if ( op->ors_limit == NULL /* isroot == TRUE */ || + !op->ors_limit->lms_s_pr_hide ) + { + tentries = ncand; + } + + wwctx.flag = 0; + wwctx.nentries = 0; + /* If we're running in our own read txn */ + if ( moi == &opinfo ) { + cb.sc_writewait = mdb_writewait; + cb.sc_private = &wwctx; + wwctx.txn = ltid; + wwctx.mcd = NULL; + cb.sc_next = op->o_callback; + op->o_callback = &cb; + } + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) { + PagedResultsState *ps = op->o_pagedresults_state; + /* deferred cookie parsing */ + rs->sr_err = parse_paged_cookie( op, rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto done; + } + + cursor = (ID) ps->ps_cookie; + if ( cursor && ps->ps_size == 0 ) { + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = "search abandoned by pagedResult size=0"; + send_ldap_result( op, rs ); + goto done; + } + + if ( admincheck ) + goto adminlimit; + + id = mdb_idl_first( candidates, &cursor ); + if ( id == NOID ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": no paged results candidates\n" ); + send_paged_response( op, rs, &lastid, 0 ); + + rs->sr_err = LDAP_OTHER; + goto done; + } + if ( id == (ID)ps->ps_cookie ) + id = mdb_idl_next( candidates, &cursor ); + nsubs = ncand; /* always bypass scope'd search */ + goto loop_begin; + } + if ( nsubs < ncand ) { + int rc; + /* Do scope-based search */ + if ( admincheck && nsubs > (unsigned) op->ors_limit->lms_s_unchecked ) + goto adminlimit; + + /* if any alias scopes were set, save them */ + if (scopes[0].mid > 1) { + cursor = 1; + for (cscope = 1; cscope <= scopes[0].mid; cscope++) { + /* Ignore the original base */ + if (scopes[cscope].mid == base->e_id) + continue; + iscopes[cursor++] = scopes[cscope].mid; + } + iscopes[0] = scopes[0].mid - 1; + } else { + iscopes[0] = 0; + } + + wwctx.mcd = mcd; + isc.id = base->e_id; + isc.numrdns = 0; + rc = mdb_dn2id_walk( op, &isc ); + if ( rc ) + id = NOID; + else + id = isc.id; + cscope = 0; + } else { + if ( admincheck ) + goto adminlimit; + id = mdb_idl_first( candidates, &cursor ); + } + + while (id != NOID) + { + int scopeok; + MDB_val edata; + +loop_begin: + + /* check for abandon */ + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + send_ldap_result( op, rs ); + goto done; + } + + /* mostly needed by internal searches, + * e.g. related to syncrepl, for whom + * abandon does not get set... */ + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + send_ldap_disconnect( op, rs ); + goto done; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + goto done; + } + + + if ( nsubs < ncand ) { + unsigned i; + /* Is this entry in the candidate list? */ + scopeok = 0; + if (MDB_IDL_IS_RANGE( candidates )) { + if ( id >= MDB_IDL_RANGE_FIRST( candidates ) && + id <= MDB_IDL_RANGE_LAST( candidates )) + scopeok = 1; + } else { + i = mdb_idl_search( candidates, id ); + if (i <= candidates[0] && candidates[i] == id ) + scopeok = 1; + } + if ( scopeok ) + goto scopeok; + goto loop_continue; + } + + /* Does this candidate actually satisfy the search scope? + */ + scopeok = 0; + isc.numrdns = 0; + switch( op->ors_scope ) { + case LDAP_SCOPE_BASE: + /* This is always true, yes? */ + if ( id == base->e_id ) scopeok = 1; + break; + +#ifdef LDAP_SCOPE_CHILDREN + case LDAP_SCOPE_CHILDREN: + if ( id == base->e_id ) break; + /* Fall-thru */ +#endif + case LDAP_SCOPE_SUBTREE: + if ( id == base->e_id ) { + scopeok = 1; + break; + } + /* Fall-thru */ + case LDAP_SCOPE_ONELEVEL: + if ( id == base->e_id ) break; + isc.id = id; + isc.nscope = 0; + rs->sr_err = mdb_idscopes( op, &isc ); + if ( rs->sr_err == MDB_SUCCESS ) { + if ( isc.nscope ) + scopeok = 1; + } else { + if ( rs->sr_err == MDB_NOTFOUND ) + goto notfound; + } + break; + } + + /* Not in scope, ignore it */ + if ( !scopeok ) + { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": %ld scope not okay\n", + (long) id ); + goto loop_continue; + } + +scopeok: + if ( id == base->e_id ) { + e = base; + } else { + + /* get the entry */ + rs->sr_err = mdb_id2edata( op, mci, id, &edata ); + if ( rs->sr_err == MDB_NOTFOUND ) { +notfound: + if( nsubs < ncand ) + goto loop_continue; + + if( !MDB_IDL_IS_RANGE(candidates) ) { + /* only complain for non-range IDLs */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": candidate %ld not found\n", + (long) id ); + } else { + /* get the next ID from the DB */ + rs->sr_err = mdb_get_nextid( mci, &cursor ); + if ( rs->sr_err == MDB_NOTFOUND ) { + break; + } + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in get_nextid"; + send_ldap_result( op, rs ); + goto done; + } + cursor--; + } + + goto loop_continue; + } else if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in mdb_id2edata"; + send_ldap_result( op, rs ); + goto done; + } + + rs->sr_err = mdb_entry_decode( op, ltid, &edata, id, &e ); + if ( rs->sr_err ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error in mdb_entry_decode"; + send_ldap_result( op, rs ); + goto done; + } + e->e_id = id; + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + } + + if ( is_entry_subentry( e ) ) { + if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) { + if(!get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries( op ) && + !get_subentries_visibility( op )) + { + /* only subentries are visible */ + goto loop_continue; + } + + } else if ( get_subentries_visibility( op )) { + /* only subentries are visible */ + goto loop_continue; + } + + /* aliases were already dereferenced in candidate list */ + if ( op->ors_deref & LDAP_DEREF_SEARCHING ) { + /* but if the search base is an alias, and we didn't + * deref it when finding, return it. + */ + if ( is_entry_alias(e) && + ((op->ors_deref & LDAP_DEREF_FINDING) || e != base )) + { + goto loop_continue; + } + } + + if ( !manageDSAit && is_entry_glue( e )) { + goto loop_continue; + } + + if (e != base) { + struct berval pdn, pndn; + char *d, *n; + int i; + + /* child of base, just append RDNs to base->e_name */ + if ( nsubs < ncand || isc.scopes[isc.nscope].mid == base->e_id ) { + pdn = base->e_name; + pndn = base->e_nname; + } else { + mdb_id2name( op, ltid, &isc.mc, scopes[isc.nscope].mid, &pdn, &pndn ); + } + e->e_name.bv_len = pdn.bv_len; + e->e_nname.bv_len = pndn.bv_len; + for (i=0; i<isc.numrdns; i++) { + e->e_name.bv_len += isc.rdns[i].bv_len + 1; + e->e_nname.bv_len += isc.nrdns[i].bv_len + 1; + } + e->e_name.bv_val = op->o_tmpalloc(e->e_name.bv_len + 1, op->o_tmpmemctx); + e->e_nname.bv_val = op->o_tmpalloc(e->e_nname.bv_len + 1, op->o_tmpmemctx); + d = e->e_name.bv_val; + n = e->e_nname.bv_val; + if (nsubs < ncand) { + /* RDNs are in top-down order */ + for (i=isc.numrdns-1; i>=0; i--) { + memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len); + d += isc.rdns[i].bv_len; + *d++ = ','; + memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len); + n += isc.nrdns[i].bv_len; + *n++ = ','; + } + } else { + /* RDNs are in bottom-up order */ + for (i=0; i<isc.numrdns; i++) { + memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len); + d += isc.rdns[i].bv_len; + *d++ = ','; + memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len); + n += isc.nrdns[i].bv_len; + *n++ = ','; + } + } + + if (pdn.bv_len) { + memcpy(d, pdn.bv_val, pdn.bv_len+1); + memcpy(n, pndn.bv_val, pndn.bv_len+1); + } else { + *--d = '\0'; + *--n = '\0'; + e->e_name.bv_len--; + e->e_nname.bv_len--; + } + if (pndn.bv_val != base->e_nname.bv_val) { + op->o_tmpfree(pndn.bv_val, op->o_tmpmemctx); + op->o_tmpfree(pdn.bv_val, op->o_tmpmemctx); + } + } + + /* + * if it's a referral, add it to the list of referrals. only do + * this for non-base searches, and don't check the filter + * explicitly here since it's only a candidate anyway. + */ + if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE + && is_entry_referral( e ) ) + { + BerVarray erefs = get_entry_referrals( op, e ); + rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL, + op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL + ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + + rs->sr_entry = e; + rs->sr_flags = 0; + + send_search_reference( op, rs ); + + if (e != base) + mdb_entry_return( op, e ); + rs->sr_entry = NULL; + e = NULL; + + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + + goto loop_continue; + } + + /* if it matches the filter and scope, send it */ + rs->sr_err = test_filter( op, e, op->oq_search.rs_filter ); + + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* check size limit */ + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) { + if (e != base) + mdb_entry_return( op, e ); + e = NULL; + send_paged_response( op, rs, &lastid, tentries ); + goto done; + } + lastid = id; + } + + if (e) { + /* safe default */ + rs->sr_attrs = op->oq_search.rs_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_ctrls = NULL; + rs->sr_entry = e; + RS_ASSERT( e->e_private != NULL ); + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rs->sr_err = send_search_entry( op, rs ); + rs->sr_attrs = NULL; + rs->sr_entry = NULL; + if (e != base) + mdb_entry_return( op, e ); + e = NULL; + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: /* entry sent ok */ + break; + default: /* entry not sent */ + break; + case LDAP_BUSY: + send_ldap_result( op, rs ); + goto done; + case LDAP_UNAVAILABLE: + case LDAP_SIZELIMIT_EXCEEDED: + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) { + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + + } else { + rs->sr_err = LDAP_OTHER; + } + goto done; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(mdb_search) + ": %ld does not match filter\n", + (long) id ); + } + +loop_continue: + if ( moi == &opinfo && !wwctx.flag && mdb->mi_rtxn_size ) { + wwctx.nentries++; + if ( wwctx.nentries >= mdb->mi_rtxn_size ) { + MDB_envinfo ei; + wwctx.nentries = 0; + mdb_env_info(mdb->mi_dbenv, &ei); + if ( ei.me_last_txnid > mdb_txn_id( ltid )) + mdb_rtxn_snap( op, &wwctx ); + } + } + if ( wwctx.flag ) { + rs->sr_err = mdb_waitfixup( op, &wwctx, mci, mcd, &isc ); + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + goto done; + } + } + + if( e != NULL ) { + if ( e != base ) + mdb_entry_return( op, e ); + RS_ASSERT( rs->sr_entry == NULL ); + e = NULL; + rs->sr_entry = NULL; + } + + if ( nsubs < ncand ) { + int rc = mdb_dn2id_walk( op, &isc ); + if (rc) { + id = NOID; + /* We got to the end of a subtree. If there are any + * alias scopes left, search them too. + */ + while (iscopes[0] && cscope < iscopes[0]) { + cscope++; + isc.id = iscopes[cscope]; + if ( base ) + mdb_entry_return( op, base ); + rs->sr_err = mdb_id2entry(op, mci, isc.id, &base); + if ( !rs->sr_err ) { + mdb_id2name( op, ltid, &isc.mc, isc.id, &base->e_name, &base->e_nname ); + isc.numrdns = 0; + if (isc.oscope == LDAP_SCOPE_ONELEVEL) + isc.oscope = LDAP_SCOPE_BASE; + rc = mdb_dn2id_walk( op, &isc ); + if ( !rc ) { + id = isc.id; + break; + } + } + } + } else + id = isc.id; + } else { + id = mdb_idl_next( candidates, &cursor ); + } + } + +nochange: + rs->sr_ctrls = NULL; + rs->sr_ref = rs->sr_v2ref; + rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL; + rs->sr_rspoid = NULL; + if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) { + send_paged_response( op, rs, NULL, 0 ); + } else { + send_ldap_result( op, rs ); + } + + rs->sr_err = LDAP_SUCCESS; + +done: + if ( cb.sc_private ) { + /* remove our writewait callback */ + slap_callback **scp = &op->o_callback; + while ( *scp ) { + if ( *scp == &cb ) { + *scp = cb.sc_next; + cb.sc_private = NULL; + break; + } + } + } + mdb_cursor_close( mcd ); + mdb_cursor_close( mci ); + if ( moi == &opinfo ) { + mdb_txn_reset( moi->moi_txn ); + LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next ); + } else { + moi->moi_ref--; + } + if( rs->sr_v2ref ) { + ber_bvarray_free( rs->sr_v2ref ); + rs->sr_v2ref = NULL; + } + if (base) + mdb_entry_return( op, base ); + scope_chunk_ret( op, scopes ); + if ( candidates != c0 ) { + ch_free( candidates ); + } else { + MDB_IDL_ZERO( candidates ); + } + + return rs->sr_err; +} + + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ) +{ + Debug(LDAP_DEBUG_ARGS, "base_candidates: base: \"%s\" (0x%08lx)\n", + e->e_nname.bv_val, (long) e->e_id ); + + ids[0] = 1; + ids[1] = e->e_id; + return 0; +} + +/* Look for "objectClass Present" in this filter. + * Also count depth of filter tree while we're at it. + */ +static int oc_filter( + Filter *f, + int cur, + int *max ) +{ + int rc = 0; + + assert( f != NULL ); + + if( cur > *max ) *max = cur; + + switch( f->f_choice ) { + case LDAP_FILTER_PRESENT: + if (f->f_desc == slap_schema.si_ad_objectClass) { + rc = 1; + } + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + cur++; + for ( f=f->f_and; f; f=f->f_next ) { + (void) oc_filter(f, cur, max); + } + break; + + default: + break; + } + return rc; +} + +typedef struct IDLchunk { + unsigned int logn; + unsigned int pad; +} IDLchunk; + +static void search_stack_free( void *key, void *data ) +{ + ber_memfree_x(data, NULL); +} + +static void *search_stack( Operation *op ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + IDLchunk *ic = NULL; + + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack, + (void **)&ic, NULL ); + } else { + ic = mdb->mi_search_stack; + } + + if ( ic && ic->logn != MDB_idl_logn ) { + ber_memfree_x( ic, NULL ); + ic = NULL; + } + + if ( !ic ) { + ic = ch_malloc(( mdb->mi_search_stack_depth + 2 ) * (size_t)MDB_idl_um_size + * sizeof( ID ) + sizeof( IDLchunk ) ); + ic->logn = MDB_idl_logn; + if ( op->o_threadctx ) { + ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack, + ic, search_stack_free, NULL, NULL ); + } else { + mdb->mi_search_stack = ic; + } + ID *idl = (ID *)( ic+1 ); + MDB_IDL_ZERO( idl ); + } + return ic+1; +} + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + IdScopes *isc, + MDB_cursor *mci, + ID *ids, + ID *stack ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + int rc, depth = 1; + Filter *f, rf, xf, nf, sf; + AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT; + AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT; + + /* + * This routine takes as input a filter (user-filter) + * and rewrites it as follows: + * (&(scope=DN)[(objectClass=subentry)] + * (|[(objectClass=referral)](user-filter)) + */ + + Debug(LDAP_DEBUG_TRACE, + "search_candidates: base=\"%s\" (0x%08lx) scope=%d\n", + e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope ); + + f = op->oq_search.rs_filter; + + /* If the user's filter uses objectClass=*, + * these clauses are redundant. + */ + if (!oc_filter(op->oq_search.rs_filter, 1, &depth) + && !get_subentries_visibility(op)) { + if( !get_manageDSAit(op) && !get_domainScope(op) ) { + /* match referral objects */ + struct berval bv_ref = BER_BVC( "referral" ); + rf.f_choice = LDAP_FILTER_EQUALITY; + rf.f_ava = &aa_ref; + rf.f_av_desc = slap_schema.si_ad_objectClass; + rf.f_av_value = bv_ref; + rf.f_next = f; + xf.f_or = &rf; + xf.f_choice = LDAP_FILTER_OR; + xf.f_next = NULL; + f = &xf; + depth++; + } + } + + if( get_subentries_visibility( op ) ) { + struct berval bv_subentry = BER_BVC( "subentry" ); + sf.f_choice = LDAP_FILTER_EQUALITY; + sf.f_ava = &aa_subentry; + sf.f_av_desc = slap_schema.si_ad_objectClass; + sf.f_av_value = bv_subentry; + sf.f_next = f; + nf.f_choice = LDAP_FILTER_AND; + nf.f_and = &sf; + nf.f_next = NULL; + f = &nf; + depth++; + } + + /* Allocate IDL stack, plus 1 more for former tmp */ + if ( depth+1 > mdb->mi_search_stack_depth ) { + stack = ch_malloc( (depth + 1) * MDB_idl_um_size * sizeof( ID ) ); + } + + if( op->ors_deref & LDAP_DEREF_SEARCHING ) { + rc = search_aliases( op, rs, e->e_id, isc, mci, stack ); + } else { + rc = LDAP_SUCCESS; + } + + if ( rc == LDAP_SUCCESS ) { + rc = mdb_filter_candidates( op, isc->mt, f, ids, + stack, stack+MDB_idl_um_size ); + } + + if ( depth+1 > mdb->mi_search_stack_depth ) { + ch_free( stack ); + } + + if( rc ) { + Debug(LDAP_DEBUG_TRACE, + "mdb_search_candidates: failed (rc=%d)\n", + rc ); + + } else { + Debug(LDAP_DEBUG_TRACE, + "mdb_search_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) MDB_IDL_FIRST(ids), + (long) MDB_IDL_LAST(ids) ); + } + + return rc; +} + +static int +parse_paged_cookie( Operation *op, SlapReply *rs ) +{ + int rc = LDAP_SUCCESS; + PagedResultsState *ps = op->o_pagedresults_state; + + /* this function must be invoked only if the pagedResults + * control has been detected, parsed and partially checked + * by the frontend */ + assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ); + + /* cookie decoding/checks deferred to backend... */ + if ( ps->ps_cookieval.bv_len ) { + PagedResultsCookie reqcookie; + if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + } + + AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie )); + + if ( reqcookie > ps->ps_cookie ) { + /* bad cookie */ + rs->sr_text = "paged results cookie is invalid"; + rc = LDAP_PROTOCOL_ERROR; + goto done; + + } else if ( reqcookie < ps->ps_cookie ) { + rs->sr_text = "paged results cookie is invalid or old"; + rc = LDAP_UNWILLING_TO_PERFORM; + goto done; + } + + } else { + /* we're going to use ps_cookie */ + op->o_conn->c_pagedresults_state.ps_cookie = 0; + } + +done:; + + return rc; +} + +static void +send_paged_response( + Operation *op, + SlapReply *rs, + ID *lastid, + int tentries ) +{ + LDAPControl *ctrls[2]; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie respcookie; + struct berval cookie; + + Debug(LDAP_DEBUG_ARGS, + "send_paged_response: lastid=0x%08lx nentries=%d\n", + lastid ? *lastid : 0, rs->sr_nentries ); + + ctrls[1] = NULL; + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( lastid ) { + respcookie = ( PagedResultsCookie )(*lastid); + cookie.bv_len = sizeof( respcookie ); + cookie.bv_val = (char *)&respcookie; + + } else { + respcookie = ( PagedResultsCookie )0; + BER_BVSTR( &cookie, "" ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = respcookie; + op->o_conn->c_pagedresults_state.ps_count = + ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + /* return size of 0 -- no estimate */ + ber_printf( ber, "{iO}", 0, &cookie ); + + ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx ); + if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) { + goto done; + } + + ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrls[0]->ldctl_iscritical = 0; + + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + +done: + (void) ber_free_buf( ber ); +} diff --git a/servers/slapd/back-mdb/tools.c b/servers/slapd/back-mdb/tools.c new file mode 100644 index 0000000..ddf6a29 --- /dev/null +++ b/servers/slapd/back-mdb/tools.c @@ -0,0 +1,1712 @@ +/* tools.c - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2011-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#define AVL_INTERNAL +#include "back-mdb.h" +#include "idl.h" + +#ifdef MDB_TOOL_IDL_CACHING +static int mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn ); + +#define IDBLOCK 1024 + +typedef struct mdb_tool_idl_cache_entry { + struct mdb_tool_idl_cache_entry *next; + ID ids[IDBLOCK]; +} mdb_tool_idl_cache_entry; + +typedef struct mdb_tool_idl_cache { + struct berval kstr; + mdb_tool_idl_cache_entry *head, *tail; + ID first, last; + int count; + short offset; + short flags; +} mdb_tool_idl_cache; +#define WAS_FOUND 0x01 +#define WAS_RANGE 0x02 + +#define MDB_TOOL_IDL_FLUSH(be, txn) mdb_tool_idl_flush(be, txn) +#else +#define MDB_TOOL_IDL_FLUSH(be, txn) +#endif /* MDB_TOOL_IDL_CACHING */ + +MDB_txn *mdb_tool_txn = NULL; + +static MDB_txn *txi = NULL; +static MDB_cursor *cursor = NULL, *idcursor = NULL; +static MDB_cursor *mcp = NULL, *mcd = NULL; +static MDB_val key, data; +static ID previd = NOID; + +typedef struct dn_id { + ID id; + struct berval dn; +} dn_id; + +#define HOLE_SIZE 4096 +static dn_id hbuf[HOLE_SIZE], *holes = hbuf; +static unsigned nhmax = HOLE_SIZE; +static unsigned nholes; + +static struct berval *tool_base; +static int tool_scope; +static Filter *tool_filter; +static Entry *tool_next_entry; + +static ID mdb_tool_ix_id; +static BackendDB *mdb_tool_ix_be; +static MDB_txn *mdb_tool_ix_txn; +static int mdb_tool_index_tcount, mdb_tool_threads; +static IndexRec *mdb_tool_index_rec; +static AttrIxInfo **mdb_tool_axinfo; +static struct mdb_info *mdb_tool_info; +static ldap_pvt_thread_mutex_t mdb_tool_index_mutex; +static ldap_pvt_thread_cond_t mdb_tool_index_cond_main; +static ldap_pvt_thread_cond_t mdb_tool_index_cond_work; +static void * mdb_tool_index_task( void *ctx, void *ptr ); + +static int mdb_writes, mdb_writes_per_commit; + +/* Number of ops per commit in Quick mode. + * Batching speeds writes overall, but too large a + * batch will fail with MDB_TXN_FULL. + */ +#ifndef MDB_WRITES_PER_COMMIT +#define MDB_WRITES_PER_COMMIT 500 +#endif + +static int +mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ); + +int mdb_tool_entry_open( + BackendDB *be, int mode ) +{ + /* In Quick mode, commit once per 500 entries */ + mdb_writes = 0; + if ( slapMode & SLAP_TOOL_QUICK ) + mdb_writes_per_commit = MDB_WRITES_PER_COMMIT; + else + mdb_writes_per_commit = 1; + +#ifdef MDB_TOOL_IDL_CACHING /* threaded indexing has no performance advantage */ + /* Set up for threaded slapindex */ + if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) { + if ( !mdb_tool_info ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + ldap_pvt_thread_mutex_init( &mdb_tool_index_mutex ); + ldap_pvt_thread_cond_init( &mdb_tool_index_cond_main ); + ldap_pvt_thread_cond_init( &mdb_tool_index_cond_work ); + if ( mdb->mi_nattrs ) { + int i; + mdb_tool_threads = slap_tool_thread_max - 1; + if ( mdb_tool_threads > 1 ) { + mdb_tool_index_rec = ch_calloc( mdb->mi_nattrs, sizeof( IndexRec )); + mdb_tool_axinfo = ch_calloc( mdb_tool_threads, sizeof( AttrIxInfo* ) + + sizeof( AttrIxInfo )); + mdb_tool_axinfo[0] = (AttrIxInfo *)(mdb_tool_axinfo + mdb_tool_threads); + for (i=1; i<mdb_tool_threads; i++) + mdb_tool_axinfo[i] = mdb_tool_axinfo[i-1]+1; + mdb_tool_index_tcount = mdb_tool_threads - 1; + mdb_tool_ix_be = be; + for (i=1; i<mdb_tool_threads; i++) { + int *ptr = ch_malloc( sizeof( int )); + *ptr = i; + ldap_pvt_thread_pool_submit( &connection_pool, + mdb_tool_index_task, ptr ); + } + mdb_tool_info = mdb; + } + } + } + } +#endif + + return 0; +} + +int mdb_tool_entry_close( + BackendDB *be ) +{ +#ifdef MDB_TOOL_IDL_CACHING + if ( mdb_tool_info ) { + int i; + slapd_shutdown = 1; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + + /* There might still be some threads starting */ + while ( mdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + + mdb_tool_index_tcount = mdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); + + /* Make sure all threads are stopped */ + while ( mdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + + mdb_tool_info = NULL; + slapd_shutdown = 0; + ch_free( mdb_tool_index_rec ); + mdb_tool_index_tcount = mdb_tool_threads - 1; + if (mdb_tool_txn) + MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn ); + for (i=0; i<mdb_tool_threads; i++) { + mdb_tool_idl_cache *ic; + mdb_tool_idl_cache_entry *ice; + while ((ic = mdb_tool_axinfo[i]->ai_clist)) { + mdb_tool_axinfo[i]->ai_clist = ic->head; + free(ic); + } + while ((ice = mdb_tool_axinfo[i]->ai_flist)) { + mdb_tool_axinfo[i]->ai_flist = ice->next; + free(ice); + } + } + } +#endif + + if( idcursor ) { + mdb_cursor_close( idcursor ); + idcursor = NULL; + } + if( cursor ) { + mdb_cursor_close( cursor ); + cursor = NULL; + } + { + struct mdb_info *mdb = be->be_private; + if ( mdb ) { + int i; + for (i=0; i<mdb->mi_nattrs; i++) + mdb->mi_attrs[i]->ai_cursor = NULL; + } + } + if( mdb_tool_txn ) { + int rc; + if (( rc = mdb_txn_commit( mdb_tool_txn ))) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_close) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + return -1; + } + mdb_tool_txn = NULL; + } + if( txi ) { + int rc; + if (( rc = mdb_txn_commit( txi ))) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_close) ": database %s: " + "txn_commit failed: %s (%d)\n", + be->be_suffix[0].bv_val, mdb_strerror(rc), rc ); + return -1; + } + txi = NULL; + } + + if( nholes ) { + unsigned i; + fprintf( stderr, "Error, entries missing!\n"); + for (i=0; i<nholes; i++) { + fprintf(stderr, " entry %ld: %s\n", + holes[i].id, holes[i].dn.bv_val); + } + nholes = 0; + return -1; + } + + return 0; +} + +ID +mdb_tool_entry_first_x( + BackendDB *be, + struct berval *base, + int scope, + Filter *f ) +{ + tool_base = base; + tool_scope = scope; + tool_filter = f; + + return mdb_tool_entry_next( be ); +} + +ID mdb_tool_entry_next( + BackendDB *be ) +{ + int rc; + ID id; + struct mdb_info *mdb; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + mdb = (struct mdb_info *) be->be_private; + assert( mdb != NULL ); + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn ); + if ( rc ) + return NOID; + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor ); + if ( rc ) { + mdb_txn_abort( mdb_tool_txn ); + return NOID; + } + } + +next:; + rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT ); + + if( rc ) { + return NOID; + } + + previd = *(ID *)key.mv_data; + id = previd; + + if ( !data.mv_size ) + goto next; + + if ( tool_filter || tool_base ) { + static Operation op = {0}; + static Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if ( tool_next_entry ) { + mdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + } + + rc = mdb_tool_entry_get_int( be, id, &tool_next_entry ); + if ( rc == LDAP_NO_SUCH_OBJECT ) { + goto next; + } + + assert( tool_next_entry != NULL ); + + if ( tool_filter && test_filter( NULL, tool_next_entry, tool_filter ) != LDAP_COMPARE_TRUE ) + { + mdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } + } + + return id; +} + +ID mdb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + ID id; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + mdb = (struct mdb_info *) be->be_private; + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, (slapMode & SLAP_TOOL_READONLY) != 0 ? + MDB_RDONLY : 0, &mdb_tool_txn ); + if ( rc ) + return NOID; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = mdb_dn2id( &op, mdb_tool_txn, NULL, dn, &id, NULL, NULL, NULL ); + if ( rc == MDB_NOTFOUND ) + return NOID; + + return id; +} + +static int +mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ) +{ + Operation op = {0}; + Opheader ohdr = {0}; + + Entry *e = NULL; + struct berval dn = BER_BVNULL, ndn = BER_BVNULL; + int rc; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + if ( ( tool_filter || tool_base ) && id == previd && tool_next_entry != NULL ) { + *ep = tool_next_entry; + tool_next_entry = NULL; + return LDAP_SUCCESS; + } + + if ( id != previd ) { + key.mv_size = sizeof(ID); + key.mv_data = &id; + rc = mdb_cursor_get( cursor, &key, &data, MDB_SET ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + if ( !data.mv_size ) { + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + if ( slapMode & SLAP_TOOL_READONLY ) { + rc = mdb_id2name( &op, mdb_tool_txn, &idcursor, id, &dn, &ndn ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + if ( tool_base != NULL ) { + if ( !dnIsSuffixScope( &ndn, tool_base, tool_scope ) ) { + ch_free( dn.bv_val ); + ch_free( ndn.bv_val ); + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + } + } + rc = mdb_entry_decode( &op, mdb_tool_txn, &data, id, &e ); + e->e_id = id; + if ( !BER_BVISNULL( &dn )) { + e->e_name = dn; + e->e_nname = ndn; + } else { + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + } + +done: + if ( e != NULL ) { + *ep = e; + } + + return rc; +} + +Entry* +mdb_tool_entry_get( BackendDB *be, ID id ) +{ + Entry *e = NULL; + int rc; + + if ( !mdb_tool_txn ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, + (slapMode & SLAP_TOOL_READONLY) ? MDB_RDONLY : 0, &mdb_tool_txn ); + if ( rc ) + return NULL; + } + if ( !cursor ) { + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor ); + if ( rc ) { + mdb_txn_abort( mdb_tool_txn ); + mdb_tool_txn = NULL; + return NULL; + } + } + (void)mdb_tool_entry_get_int( be, id, &e ); + return e; +} + +static int mdb_tool_next_id( + Operation *op, + MDB_txn *tid, + Entry *e, + struct berval *text, + int hole ) +{ + struct berval dn = e->e_name; + struct berval ndn = e->e_nname; + struct berval pdn, npdn, nmatched; + ID id, pid = 0; + int rc; + + if (ndn.bv_len == 0) { + e->e_id = 0; + return 0; + } + + rc = mdb_dn2id( op, tid, mcp, &ndn, &id, NULL, NULL, &nmatched ); + if ( rc == MDB_NOTFOUND ) { + if ( !be_issuffix( op->o_bd, &ndn ) ) { + ID eid = e->e_id; + dnParent( &ndn, &npdn ); + if ( nmatched.bv_len != npdn.bv_len ) { + dnParent( &dn, &pdn ); + e->e_name = pdn; + e->e_nname = npdn; + rc = mdb_tool_next_id( op, tid, e, text, 1 ); + e->e_name = dn; + e->e_nname = ndn; + if ( rc ) { + return rc; + } + /* If parent didn't exist, it was created just now + * and its ID is now in e->e_id. Make sure the current + * entry gets added under the new parent ID. + */ + if ( eid != e->e_id ) { + pid = e->e_id; + } + } else { + pid = id; + } + } + rc = mdb_next_id( op->o_bd, idcursor, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val ); + return rc; + } + rc = mdb_dn2id_add( op, mcp, mcd, pid, 1, 1, e ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dn2id_add failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val ); + } else if ( hole ) { + MDB_val key, data; + if ( nholes == nhmax - 1 ) { + if ( holes == hbuf ) { + holes = ch_malloc( nhmax * sizeof(dn_id) * 2 ); + AC_MEMCPY( holes, hbuf, sizeof(hbuf) ); + } else { + holes = ch_realloc( holes, nhmax * sizeof(dn_id) * 2 ); + } + nhmax *= 2; + } + ber_dupbv( &holes[nholes].dn, &ndn ); + holes[nholes++].id = e->e_id; + key.mv_size = sizeof(ID); + key.mv_data = &e->e_id; + data.mv_size = 0; + data.mv_data = NULL; + rc = mdb_cursor_put( idcursor, &key, &data, MDB_NOOVERWRITE ); + if ( rc == MDB_KEYEXIST ) + rc = 0; + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dummy id2entry add failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> mdb_tool_next_id: %s\n", text->bv_val ); + } + } + } else if ( !hole ) { + unsigned i, j; + + e->e_id = id; + + for ( i=0; i<nholes; i++) { + if ( holes[i].id == e->e_id ) { + free(holes[i].dn.bv_val); + for (j=i;j<nholes;j++) holes[j] = holes[j+1]; + holes[j].id = 0; + nholes--; + break; + } else if ( holes[i].id > e->e_id ) { + break; + } + } + } + return rc; +} + +static int +mdb_tool_index_add( + Operation *op, + MDB_txn *txn, + Entry *e ) +{ + struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private; + + if ( !mdb->mi_nattrs ) + return 0; + + if ( mdb_tool_threads > 1 ) { + IndexRec *ir; + int i, rc; + Attribute *a; + + ir = mdb_tool_index_rec; + for (i=0; i<mdb->mi_nattrs; i++) + ir[i].ir_attrs = NULL; + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + rc = mdb_index_recset( mdb, a, a->a_desc->ad_type, + &a->a_desc->ad_tags, ir ); + if ( rc ) + return rc; + } + for (i=0; i<mdb->mi_nattrs; i++) { + if ( !ir[i].ir_ai ) + break; + rc = mdb_cursor_open( txn, ir[i].ir_ai->ai_dbi, + &ir[i].ir_ai->ai_cursor ); + if ( rc ) + return rc; + } + mdb_tool_ix_id = e->e_id; + mdb_tool_ix_txn = txn; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + /* Wait for all threads to be ready */ + while ( mdb_tool_index_tcount ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + } + + for ( i=1; i<mdb_tool_threads; i++ ) + mdb_tool_index_rec[i].ir_i = LDAP_BUSY; + mdb_tool_index_tcount = mdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work ); + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + + return mdb_index_recrun( op, txn, mdb, ir, e->e_id, 0 ); + } else + { + return mdb_index_entry_add( op, txn, e ); + } +} + +static int +mdb_tool_index_finish() +{ + int i, rc = 0; + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + for ( i=1; i<mdb_tool_threads; i++ ) { + if ( mdb_tool_index_rec[i].ir_i == LDAP_BUSY ) { + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, + &mdb_tool_index_mutex ); + i--; + continue; + } + if ( mdb_tool_index_rec[i].ir_i ) { + rc = mdb_tool_index_rec[i].ir_i; + break; + } + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + return rc; +} + +ID mdb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn ); + + mdb = (struct mdb_info *) be->be_private; + + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + return NOID; + } + } + if ( !idcursor ) { + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &idcursor ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + return NOID; + } + if ( !mdb->mi_nextid ) { + ID dummy; + mdb_next_id( be, idcursor, &dummy ); + } + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcp ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + return NOID; + } + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcd ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + return NOID; + } + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* add dn2id indices */ + rc = mdb_tool_next_id( &op, mdb_tool_txn, e, text, 0 ); + if( rc != 0 ) { + goto done; + } + + if ( mdb_tool_threads > 1 ) { + LDAP_SLIST_INSERT_HEAD( &op.o_extra, &mdb_tool_axinfo[0]->ai_oe, oe_next ); + } + rc = mdb_tool_index_add( &op, mdb_tool_txn, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "index_entry_add failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + goto done; + } + + + /* id2entry index */ + rc = mdb_id2entry_add( &op, mdb_tool_txn, idcursor, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + goto done; + } + + if( mdb->mi_nattrs && mdb_tool_threads > 1 ) + rc = mdb_tool_index_finish(); + +done: + if( rc == 0 ) { + mdb_writes++; + if ( mdb_writes >= mdb_writes_per_commit ) { + unsigned i; + MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn ); + rc = mdb_txn_commit( mdb_tool_txn ); + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb->mi_attrs[i]->ai_cursor = NULL; + mdb_writes = 0; + mdb_tool_txn = NULL; + idcursor = NULL; + if( rc != 0 ) { + mdb->mi_numads = 0; + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + e->e_id = NOID; + } + } + + } else { + unsigned i; + mdb_txn_abort( mdb_tool_txn ); + mdb_tool_txn = NULL; + idcursor = NULL; + for ( i=0; i<mdb->mi_nattrs; i++ ) + mdb->mi_attrs[i]->ai_cursor = NULL; + mdb_writes = 0; + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n", + text->bv_val ); + e->e_id = NOID; + } + + return e->e_id; +} + +static int mdb_dn2id_upgrade( BackendDB *be ); + +int mdb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct mdb_info *mi = (struct mdb_info *) be->be_private; + int rc; + Entry *e; + Operation op = {0}; + Opheader ohdr = {0}; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n", + (long) id ); + assert( tool_base == NULL ); + assert( tool_filter == NULL ); + + /* Special: do a dn2id upgrade */ + if ( adv && adv[0] == slap_schema.si_ad_entryDN ) { + /* short-circuit tool_entry_next() */ + mdb_cursor_get( cursor, &key, &data, MDB_LAST ); + return mdb_dn2id_upgrade( be ); + } + + /* No indexes configured, nothing to do. Could return an + * error here to shortcut things. + */ + if (!mi->mi_attrs) { + return 0; + } + + /* Check for explicit list of attrs to index */ + if ( adv ) { + int i, j, n; + + if ( mi->mi_attrs[0]->ai_desc != adv[0] ) { + /* count */ + for ( n = 0; adv[n]; n++ ) ; + + /* insertion sort */ + for ( i = 0; i < n; i++ ) { + AttributeDescription *ad = adv[i]; + for ( j = i-1; j>=0; j--) { + if ( SLAP_PTRCMP( adv[j], ad ) <= 0 ) break; + adv[j+1] = adv[j]; + } + adv[j+1] = ad; + } + } + + for ( i = 0; adv[i]; i++ ) { + if ( mi->mi_attrs[i]->ai_desc != adv[i] ) { + for ( j = i+1; j < mi->mi_nattrs; j++ ) { + if ( mi->mi_attrs[j]->ai_desc == adv[i] ) { + AttrInfo *ai = mi->mi_attrs[i]; + mi->mi_attrs[i] = mi->mi_attrs[j]; + mi->mi_attrs[j] = ai; + break; + } + } + if ( j == mi->mi_nattrs ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": no index configured for %s\n", + adv[i]->ad_cname.bv_val ); + return -1; + } + } + } + mi->mi_nattrs = i; + } + + e = mdb_tool_entry_get( be, id ); + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": could not locate id=%ld\n", + (long) id ); + return -1; + } + + if ( !txi ) { + rc = mdb_txn_begin( mi->mi_dbenv, NULL, 0, &txi ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) ": " + "txn_begin failed: %s (%d)\n", + mdb_strerror(rc), rc ); + goto done; + } + } + + if ( slapMode & SLAP_TRUNCATE_MODE ) { + int i; + for ( i=0; i < mi->mi_nattrs; i++ ) { + rc = mdb_drop( txi, mi->mi_attrs[i]->ai_dbi, 0 ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(mdb_tool_entry_reindex) + ": (Truncate) mdb_drop(%s) failed: %s (%d)\n", + mi->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val, + mdb_strerror(rc), rc ); + return -1; + } + } + slapMode ^= SLAP_TRUNCATE_MODE; + } + + /* + * just (re)add them for now + * Use truncate mode to empty/reset index databases + */ + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n", + (long) id ); + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = mdb_tool_index_add( &op, txi, e ); + +done: + if( rc == 0 ) { + mdb_writes++; + if ( mdb_writes >= mdb_writes_per_commit ) { + MDB_val key; + unsigned i; + MDB_TOOL_IDL_FLUSH( be, txi ); + rc = mdb_txn_commit( txi ); + mdb_writes = 0; + for ( i=0; i<mi->mi_nattrs; i++ ) + mi->mi_attrs[i]->ai_cursor = NULL; + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) + ": txn_commit failed: %s (%d)\n", + mdb_strerror(rc), rc ); + e->e_id = NOID; + } + mdb_cursor_close( cursor ); + txi = NULL; + /* Must close the read txn to allow old pages to be reclaimed. */ + mdb_txn_abort( mdb_tool_txn ); + /* and then reopen it so that tool_entry_next still works. */ + mdb_txn_begin( mi->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn ); + mdb_cursor_open( mdb_tool_txn, mi->mi_id2entry, &cursor ); + key.mv_data = &id; + key.mv_size = sizeof(ID); + mdb_cursor_get( cursor, &key, NULL, MDB_SET ); + } + + } else { + unsigned i; + mdb_writes = 0; + mdb_cursor_close( cursor ); + cursor = NULL; + mdb_txn_abort( txi ); + for ( i=0; i<mi->mi_nattrs; i++ ) + mi->mi_attrs[i]->ai_cursor = NULL; + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_reindex) + ": txn_aborted! err=%d\n", + rc ); + e->e_id = NOID; + txi = NULL; + } + mdb_entry_release( &op, e, 0 ); + + return rc; +} + +ID mdb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + assert ( e->e_id != NOID ); + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn ); + + mdb = (struct mdb_info *) be->be_private; + + if( cursor ) { + mdb_cursor_close( cursor ); + cursor = NULL; + } + if ( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val ); + return NOID; + } + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* id2entry index */ + rc = mdb_id2entry_update( &op, mdb_tool_txn, NULL, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_update failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val ); + goto done; + } + +done: + if( rc == 0 ) { + rc = mdb_txn_commit( mdb_tool_txn ); + if( rc != 0 ) { + mdb->mi_numads = 0; + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": " + "%s\n", text->bv_val ); + e->e_id = NOID; + } + + } else { + mdb_txn_abort( mdb_tool_txn ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n", + text->bv_val ); + e->e_id = NOID; + } + mdb_tool_txn = NULL; + + return e->e_id; +} + +int mdb_tool_entry_delete( + BackendDB *be, + struct berval *ndn, + struct berval *text ) +{ + int rc; + struct mdb_info *mdb; + Operation op = {0}; + Opheader ohdr = {0}; + Entry *e; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + assert( text != NULL ); + assert( text->bv_val != NULL ); + assert( text->bv_val[0] == '\0' ); /* overconservative? */ + + assert ( ndn != NULL ); + assert ( ndn->bv_val != NULL ); + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) "( %s )\n", + ndn->bv_val ); + + mdb = (struct mdb_info *) be->be_private; + + assert( cursor == NULL ); + if( cursor ) { + mdb_cursor_close( cursor ); + cursor = NULL; + } + if( !mdb_tool_txn ) { + rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + return LDAP_OTHER; + } + } + + rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &cursor ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "cursor_open failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + return LDAP_OTHER; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = mdb_dn2entry( &op, mdb_tool_txn, cursor, ndn, &e, NULL, 0 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "dn2entry failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + goto done; + } + + /* check that we wouldn't orphan any children */ + rc = mdb_dn2id_children( &op, mdb_tool_txn, e ); + if( rc != MDB_NOTFOUND ) { + switch( rc ) { + case 0: + snprintf( text->bv_val, text->bv_len, + "delete failed:" + " subordinate objects must be deleted first"); + break; + default: + snprintf( text->bv_val, text->bv_len, + "has_children failed: %s (%d)", + mdb_strerror(rc), rc ); + break; + } + rc = -1; + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + goto done; + } + + /* delete from dn2id */ + rc = mdb_dn2id_delete( &op, cursor, e->e_id, 1 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "dn2id_delete failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + goto done; + } + + /* deindex values */ + rc = mdb_index_entry_del( &op, mdb_tool_txn, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "entry_delete failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + goto done; + } + + /* do the deletion */ + rc = mdb_id2entry_delete( be, mdb_tool_txn, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_update failed: err=%d", rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + goto done; + } + +done: + /* free entry */ + if( e != NULL ) { + mdb_entry_return( &op, e ); + } + + if( rc == 0 ) { + rc = mdb_txn_commit( mdb_tool_txn ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": " + "%s\n", text->bv_val ); + } + + } else { + mdb_txn_abort( mdb_tool_txn ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + mdb_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n", + text->bv_val ); + } + mdb_tool_txn = NULL; + cursor = NULL; + + return rc; +} + +static void * +mdb_tool_index_task( void *ctx, void *ptr ) +{ + int base = *(int *)ptr; + Operation op = {0}; + Opheader ohdr = {0}; + AttrIxInfo ai = {0}, *aio; + + free( ptr ); + op.o_hdr = &ohdr; + op.o_bd = mdb_tool_ix_be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + aio = mdb_tool_axinfo[base]; + mdb_tool_axinfo[base] = &ai; + LDAP_SLIST_INSERT_HEAD( &op.o_extra, &ai.ai_oe, oe_next ); + while ( 1 ) { + ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex ); + mdb_tool_index_tcount--; + if ( !mdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); + ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_work, + &mdb_tool_index_mutex ); + if ( slapd_shutdown ) { + mdb_tool_index_tcount--; + if ( !mdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main ); + *aio = ai; + mdb_tool_axinfo[base] = aio; + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + break; + } + ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex ); + mdb_tool_index_rec[base].ir_i = mdb_index_recrun( &op, + mdb_tool_ix_txn, + mdb_tool_info, mdb_tool_index_rec, mdb_tool_ix_id, base ); + } + + return NULL; +} + +#ifdef MDB_TOOL_IDL_CACHING +static int +mdb_tool_idl_cmp( const void *v1, const void *v2 ) +{ + const mdb_tool_idl_cache *c1 = v1, *c2 = v2; + int rc; + + if (( rc = c1->kstr.bv_len - c2->kstr.bv_len )) return rc; + return memcmp( c1->kstr.bv_val, c2->kstr.bv_val, c1->kstr.bv_len ); +} + +static int +mdb_tool_idl_flush_one( MDB_cursor *mc, AttrIxInfo *ai, mdb_tool_idl_cache *ic ) +{ + mdb_tool_idl_cache_entry *ice; + MDB_val key, data[2]; + int i, rc; + ID id, nid; + + /* Freshly allocated, ignore it */ + if ( !ic->head && ic->count <= MDB_idl_db_size ) { + return 0; + } + + key.mv_data = ic->kstr.bv_val; + key.mv_size = ic->kstr.bv_len; + + if ( ic->count > MDB_idl_db_size ) { + while ( ic->flags & WAS_FOUND ) { + rc = mdb_cursor_get( mc, &key, data, MDB_SET ); + if ( rc ) { + /* FIXME: find out why this happens */ + ic->flags = 0; + break; + } + if ( ic->flags & WAS_RANGE ) { + /* Skip lo */ + rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP ); + + /* Get hi */ + rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP ); + + /* Store range hi */ + data[0].mv_data = &ic->last; + rc = mdb_cursor_put( mc, &key, data, MDB_CURRENT ); + } else { + /* Delete old data, replace with range */ + ic->first = *(ID *)data[0].mv_data; + mdb_cursor_del( mc, MDB_NODUPDATA ); + } + break; + } + if ( !(ic->flags & WAS_RANGE)) { + /* range, didn't exist before */ + nid = 0; + data[0].mv_size = sizeof(ID); + data[0].mv_data = &nid; + rc = mdb_cursor_put( mc, &key, data, 0 ); + if ( rc == 0 ) { + data[0].mv_data = &ic->first; + rc = mdb_cursor_put( mc, &key, data, 0 ); + if ( rc == 0 ) { + data[0].mv_data = &ic->last; + rc = mdb_cursor_put( mc, &key, data, 0 ); + } + } + if ( rc ) { + rc = -1; + } + } + } else { + /* Normal write */ + int n; + + data[0].mv_size = sizeof(ID); + rc = 0; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) { + int end; + if ( ice->next ) { + end = IDBLOCK; + } else { + end = (ic->count-ic->offset) & (IDBLOCK-1); + if ( !end ) + end = IDBLOCK; + } + data[1].mv_size = end; + data[0].mv_data = ice->ids; + rc = mdb_cursor_put( mc, &key, data, MDB_APPENDDUP|MDB_MULTIPLE ); + if ( rc ) { + rc = -1; + break; + } + } + if ( ic->head ) { + ic->tail->next = ai->ai_flist; + ai->ai_flist = ic->head; + } + } + ic->head = ai->ai_clist; + ai->ai_clist = ic; + return rc; +} + +static int +mdb_tool_idl_flush_db( MDB_txn *txn, AttrInfo *ai, AttrIxInfo *ax ) +{ + MDB_cursor *mc; + Avlnode *root; + int rc; + + mdb_cursor_open( txn, ai->ai_dbi, &mc ); + root = ldap_tavl_end( ai->ai_root, TAVL_DIR_LEFT ); + do { + rc = mdb_tool_idl_flush_one( mc, ax, root->avl_data ); + if ( rc != -1 ) + rc = 0; + } while ((root = ldap_tavl_next(root, TAVL_DIR_RIGHT))); + mdb_cursor_close( mc ); + + return rc; +} + +static int +mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn ) +{ + struct mdb_info *mdb = (struct mdb_info *) be->be_private; + int rc = 0; + unsigned int i, dbi; + + for ( i=0; i < mdb->mi_nattrs; i++ ) { + if ( !mdb->mi_attrs[i]->ai_root ) continue; + rc = mdb_tool_idl_flush_db( txn, mdb->mi_attrs[i], mdb_tool_axinfo[i % mdb_tool_threads] ); + ldap_tavl_free(mdb->mi_attrs[i]->ai_root, NULL); + mdb->mi_attrs[i]->ai_root = NULL; + if ( rc ) + break; + } + return rc; +} + +int mdb_tool_idl_add( + BackendDB *be, + MDB_cursor *mc, + struct berval *keys, + ID id ) +{ + MDB_dbi dbi; + mdb_tool_idl_cache *ic, itmp; + mdb_tool_idl_cache_entry *ice; + int i, rc, lcount; + AttrIxInfo *ax = (AttrIxInfo *)mc; + AttrInfo *ai = (AttrInfo *)ax->ai_ai; + mc = ai->ai_cursor; + + dbi = ai->ai_dbi; + for (i=0; keys[i].bv_val; i++) { + itmp.kstr = keys[i]; + ic = ldap_tavl_find( ai->ai_root, &itmp, mdb_tool_idl_cmp ); + + /* No entry yet, create one */ + if ( !ic ) { + MDB_val key, data; + ID nid; + int rc; + + if ( ax->ai_clist ) { + ic = ax->ai_clist; + ax->ai_clist = ic->head; + } else { + ic = ch_malloc( sizeof( mdb_tool_idl_cache ) + itmp.kstr.bv_len + 4 ); + } + ic->kstr.bv_len = itmp.kstr.bv_len; + ic->kstr.bv_val = (char *)(ic+1); + memcpy( ic->kstr.bv_val, itmp.kstr.bv_val, ic->kstr.bv_len ); + ic->head = ic->tail = NULL; + ic->last = 0; + ic->count = 0; + ic->offset = 0; + ic->flags = 0; + ldap_tavl_insert( &ai->ai_root, ic, mdb_tool_idl_cmp, + ldap_avl_dup_error ); + + /* load existing key count here */ + key.mv_size = keys[i].bv_len; + key.mv_data = keys[i].bv_val; + rc = mdb_cursor_get( mc, &key, &data, MDB_SET ); + if ( rc == 0 ) { + ic->flags |= WAS_FOUND; + nid = *(ID *)data.mv_data; + if ( nid == 0 ) { + ic->count = MDB_idl_db_size+1; + ic->flags |= WAS_RANGE; + } else { + size_t count; + + mdb_cursor_count( mc, &count ); + ic->count = count; + ic->first = nid; + ic->offset = count & (IDBLOCK-1); + } + } + } + /* are we a range already? */ + if ( ic->count > MDB_idl_db_size ) { + ic->last = id; + continue; + /* Are we at the limit, and converting to a range? */ + } else if ( ic->count == MDB_idl_db_size ) { + if ( ic->head ) { + ic->tail->next = ax->ai_flist; + ax->ai_flist = ic->head; + } + ic->head = ic->tail = NULL; + ic->last = id; + ic->count++; + continue; + } + /* No free block, create that too */ + lcount = (ic->count-ic->offset) & (IDBLOCK-1); + if ( !ic->tail || lcount == 0) { + if ( ax->ai_flist ) { + ice = ax->ai_flist; + ax->ai_flist = ice->next; + } else { + ice = ch_malloc( sizeof( mdb_tool_idl_cache_entry )); + } + ice->next = NULL; + if ( !ic->head ) { + ic->head = ice; + } else { + ic->tail->next = ice; + } + ic->tail = ice; + if ( lcount ) + ice->ids[lcount-1] = 0; + if ( !ic->count ) + ic->first = id; + } + ice = ic->tail; + if (!lcount || ice->ids[lcount-1] != id) { + ice->ids[lcount] = id; + ic->count++; + } + } + + return 0; +} +#endif /* MDB_TOOL_IDL_CACHING */ + +/* Upgrade from pre 2.4.34 dn2id format */ + +#include <ac/unistd.h> +#include <lutil_meter.h> + +#define STACKSIZ 2048 + +typedef struct rec { + ID id; + size_t len; + char rdn[512]; +} rec; + +static int +mdb_dn2id_upgrade( BackendDB *be ) { + struct mdb_info *mi = (struct mdb_info *) be->be_private; + MDB_txn *mt; + MDB_cursor *mc = NULL; + MDB_val key, data; + int rc, writes=0, depth=0; + int enable_meter = 0; + ID id = 0, *num, count = 0; + rec *stack; + lutil_meter_t meter; + + if (!(mi->mi_flags & MDB_NEED_UPGRADE)) { + Debug( LDAP_DEBUG_ANY, "database %s: No upgrade needed.\n", + be->be_suffix[0].bv_val ); + return 0; + } + + { + MDB_stat st; + + mdb_stat(mdb_cursor_txn(cursor), mi->mi_dbis[MDB_ID2ENTRY], &st); + if (!st.ms_entries) { + /* Empty DB, nothing to upgrade? */ + return 0; + } + if (isatty(2)) + enable_meter = !lutil_meter_open(&meter, + &lutil_meter_text_display, + &lutil_meter_linear_estimator, + st.ms_entries); + } + + num = ch_malloc(STACKSIZ * (sizeof(ID) + sizeof(rec))); + stack = (rec *)(num + STACKSIZ); + + rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + + key.mv_size = sizeof(ID); + /* post-order depth-first update */ + for(;;) { + size_t dkids; + unsigned char *ptr; + + /* visit */ + key.mv_data = &id; + stack[depth].id = id; + rc = mdb_cursor_get(mc, &key, &data, MDB_SET); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + num[depth] = 1; + + rc = mdb_cursor_count(mc, &dkids); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_count failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + if (dkids > 1) { + rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP); +down: + ptr = (unsigned char *)data.mv_data + data.mv_size - sizeof(ID); + memcpy(&id, ptr, sizeof(ID)); + depth++; + memcpy(stack[depth].rdn, data.mv_data, data.mv_size); + stack[depth].len = data.mv_size; + continue; + } + + + /* pop: write updated count, advance to next node */ +pop: + /* update superior counts */ + if (depth) + num[depth-1] += num[depth]; + + key.mv_data = &id; + id = stack[depth-1].id; + data.mv_data = stack[depth].rdn; + data.mv_size = stack[depth].len; + rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(BOTH) failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + data.mv_data = stack[depth].rdn; + ptr = (unsigned char *)data.mv_data + data.mv_size; + memcpy(ptr, &num[depth], sizeof(ID)); + data.mv_size += sizeof(ID); + rc = mdb_cursor_del(mc, 0); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_del failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + rc = mdb_cursor_put(mc, &key, &data, 0); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_put failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + count++; +#if 1 + if (enable_meter) + lutil_meter_update(&meter, count, 0); +#else + { + int len; + ptr = data.mv_data; + len = (ptr[0] & 0x7f) << 8 | ptr[1]; + printf("ID: %zu, %zu, %.*s\n", stack[depth].id, num[depth], len, ptr+2); + } +#endif + writes++; + if (writes == 1000) { + mdb_cursor_close(mc); + rc = mdb_txn_commit(mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin(2) failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open(2) failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH); + if (rc) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(2) failed, %s (%d)\n", + mdb_strerror(rc), rc ); + goto leave; + } + writes = 0; + } + depth--; + + rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP); + if (rc == 0) + goto down; + rc = 0; + if (depth) + goto pop; + else + break; + } +leave: + mdb_cursor_close(mc); + if (mt) { + int r2; + r2 = mdb_txn_commit(mt); + if (r2) { + Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit(2) failed, %s (%d)\n", + mdb_strerror(r2), r2 ); + if (!rc) + rc = r2; + } + } + ch_free(num); + if (enable_meter) { + lutil_meter_update(&meter, count, 1); + lutil_meter_close(&meter); + } + return rc; +} |