summaryrefslogtreecommitdiffstats
path: root/servers/slapd/back-mdb
diff options
context:
space:
mode:
Diffstat (limited to 'servers/slapd/back-mdb')
-rw-r--r--servers/slapd/back-mdb/Makefile.in62
-rw-r--r--servers/slapd/back-mdb/add.c419
-rw-r--r--servers/slapd/back-mdb/attr.c824
-rw-r--r--servers/slapd/back-mdb/back-mdb.h207
-rw-r--r--servers/slapd/back-mdb/bind.c156
-rw-r--r--servers/slapd/back-mdb/compare.c142
-rw-r--r--servers/slapd/back-mdb/config.c828
-rw-r--r--servers/slapd/back-mdb/delete.c436
-rw-r--r--servers/slapd/back-mdb/dn2entry.c79
-rw-r--r--servers/slapd/back-mdb/dn2id.c981
-rw-r--r--servers/slapd/back-mdb/extended.c54
-rw-r--r--servers/slapd/back-mdb/filterindex.c1173
-rw-r--r--servers/slapd/back-mdb/id2entry.c1151
-rw-r--r--servers/slapd/back-mdb/idl.c1293
-rw-r--r--servers/slapd/back-mdb/idl.h118
-rw-r--r--servers/slapd/back-mdb/index.c577
-rw-r--r--servers/slapd/back-mdb/init.c508
-rw-r--r--servers/slapd/back-mdb/key.c72
-rw-r--r--servers/slapd/back-mdb/modify.c843
-rw-r--r--servers/slapd/back-mdb/modrdn.c624
-rw-r--r--servers/slapd/back-mdb/monitor.c807
-rw-r--r--servers/slapd/back-mdb/nextid.c53
-rw-r--r--servers/slapd/back-mdb/operational.c121
-rw-r--r--servers/slapd/back-mdb/proto-mdb.h411
-rw-r--r--servers/slapd/back-mdb/referral.c151
-rw-r--r--servers/slapd/back-mdb/search.c1543
-rw-r--r--servers/slapd/back-mdb/tools.c1712
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;
+}