]> git.sur5r.net Git - openldap/commitdiff
Import back-mdb
authorHoward Chu <hyc@openldap.org>
Thu, 1 Sep 2011 23:52:53 +0000 (16:52 -0700)
committerHoward Chu <hyc@openldap.org>
Fri, 2 Sep 2011 00:00:26 +0000 (17:00 -0700)
28 files changed:
doc/man/man5/slapd-mdb.5 [new file with mode: 0644]
servers/slapd/back-mdb/Makefile.in [new file with mode: 0644]
servers/slapd/back-mdb/add.c [new file with mode: 0644]
servers/slapd/back-mdb/attr.c [new file with mode: 0644]
servers/slapd/back-mdb/back-mdb.h [new file with mode: 0644]
servers/slapd/back-mdb/bind.c [new file with mode: 0644]
servers/slapd/back-mdb/compare.c [new file with mode: 0644]
servers/slapd/back-mdb/config.c [new file with mode: 0644]
servers/slapd/back-mdb/delete.c [new file with mode: 0644]
servers/slapd/back-mdb/dn2entry.c [new file with mode: 0644]
servers/slapd/back-mdb/dn2id.c [new file with mode: 0644]
servers/slapd/back-mdb/extended.c [new file with mode: 0644]
servers/slapd/back-mdb/filterindex.c [new file with mode: 0644]
servers/slapd/back-mdb/id2entry.c [new file with mode: 0644]
servers/slapd/back-mdb/idl.c [new file with mode: 0644]
servers/slapd/back-mdb/idl.h [new file with mode: 0644]
servers/slapd/back-mdb/index.c [new file with mode: 0644]
servers/slapd/back-mdb/init.c [new file with mode: 0644]
servers/slapd/back-mdb/key.c [new file with mode: 0644]
servers/slapd/back-mdb/modify.c [new file with mode: 0644]
servers/slapd/back-mdb/modrdn.c [new file with mode: 0644]
servers/slapd/back-mdb/monitor.c [new file with mode: 0644]
servers/slapd/back-mdb/nextid.c [new file with mode: 0644]
servers/slapd/back-mdb/operational.c [new file with mode: 0644]
servers/slapd/back-mdb/proto-mdb.h [new file with mode: 0644]
servers/slapd/back-mdb/referral.c [new file with mode: 0644]
servers/slapd/back-mdb/search.c [new file with mode: 0644]
servers/slapd/back-mdb/tools.c [new file with mode: 0644]

diff --git a/doc/man/man5/slapd-mdb.5 b/doc/man/man5/slapd-mdb.5
new file mode 100644 (file)
index 0000000..9c723d3
--- /dev/null
@@ -0,0 +1,145 @@
+.TH SLAPD-MDB 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2011 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.\" $OpenLDAP$
+.SH NAME
+slapd\-mdb \- Memory-Mapped DB backend to slapd
+.SH SYNOPSIS
+.B ETCDIR/slapd.conf
+.SH DESCRIPTION
+The \fBmdb\fP backend to
+.BR slapd (8)
+uses OpenLDAP's own Memory-Mapped DB (MDB) library to store data.
+It relies completely on the underlying operating system for memory
+management and does no caching of its own.
+.LP
+The \fBmdb\fP backend is similar to the \fBhdb\fP backend in that
+it uses a hierarchical database layout which
+supports subtree renames. It is both more space-efficient and more
+execution-efficient than the \fBbdb\fP backend, while being overall
+much simpler to manage.
+.SH CONFIGURATION
+These
+.B slapd.conf
+options apply to the \fBmdb\fP backend database.
+That is, they must follow a "database mdb" line and
+come before any subsequent "backend" or "database" lines.
+Other database options are described in the
+.BR slapd.conf (5)
+manual page.
+.TP
+.BI checkpoint \ <kbyte>\ <min>
+Specify the frequency for flushing the database disk buffers.
+This setting is only needed if the \fBdbnosync\fP option is used.
+The checkpoint will occur if either \fI<kbyte>\fP data has been written or
+\fI<min>\fP minutes have passed since the last checkpoint.
+Both arguments default to zero, in which case they are ignored. When
+the \fI<min>\fP argument is non-zero, an internal task will run every 
+\fI<min>\fP minutes to perform the checkpoint.
+Note: currently the \fI<kbyte>\fP setting is unimplemented.
+.TP
+.B dbnosync
+Specify that on-disk database contents should not be immediately
+synchronized with in memory changes.
+Enabling this option may improve performance at the expense of data
+security. By default, a full data flush/sync is performed when each
+transaction is committed.
+.TP
+.BI directory \ <directory>
+Specify the directory where the MDB files containing this database and
+associated indexes live.
+A separate directory must be specified for each database.
+The default is
+.BR LOCALSTATEDIR/openldap\-data .
+.TP
+\fBindex \fR{\fI<attrlist>\fR|\fBdefault\fR} [\fBpres\fR,\fBeq\fR,\fBapprox\fR,\fBsub\fR,\fI<special>\fR]
+Specify the indexes to maintain for the given attribute (or
+list of attributes).
+Some attributes only support a subset of indexes.
+If only an \fI<attr>\fP is given, the indices specified for \fBdefault\fR
+are maintained.
+Note that setting a default does not imply that all attributes will be
+indexed. Also, for best performance, an
+.B eq
+index should always be configured for the
+.B objectClass
+attribute.
+
+A number of special index parameters may be specified.
+The index type
+.B sub
+can be decomposed into
+.BR subinitial ,
+.BR subany ,\ and
+.B subfinal
+indices.
+The special type
+.B nolang
+may be specified to disallow use of this index by language subtypes.
+The special type
+.B nosubtypes
+may be specified to disallow use of this index by named subtypes.
+Note: changing \fBindex\fP settings in 
+.BR slapd.conf (5)
+requires rebuilding indices, see
+.BR slapindex (8);
+changing \fBindex\fP settings
+dynamically by LDAPModifying "cn=config" automatically causes rebuilding
+of the indices online in a background task.
+.TP
+.BI maxreaders \ <integer>
+Specify the maximum number of threads that may have concurrent read access
+to the database. Tools such as slapcat count as a single thread,
+in addition to threads in any active slapd processes. The
+default is 126.
+.TP
+.BI maxsize \ <bytes>
+Specify the maximum size of the database in bytes. A memory map of this
+size is allocated at startup time and the database will not be allowed
+to grow beyond this size. The default is 10485760 bytes. This setting
+may be changed upward if the configured limit needs to be increased.
+
+Note: It is important to set this to as large a value as possible,
+(relative to anticipated growth of the actual data over time) since
+growing the size later may not be practical when the system is under
+heavy load.
+.TP
+.BI mode \ <integer>
+Specify the file protection mode that newly created database 
+files should have.
+The default is 0600.
+.TP
+.BI searchstack \ <depth>
+Specify the depth of the stack used for search filter evaluation.
+Search filters are evaluated on a stack to accommodate nested AND / OR
+clauses. An individual stack is assigned to each server thread.
+The depth of the stack determines how complex a filter can be
+evaluated without requiring any additional memory allocation. Filters that
+are nested deeper than the search stack depth will cause a separate
+stack to be allocated for that particular search operation. These
+allocations can have a major negative impact on server performance,
+but specifying too much stack will also consume a great deal of memory.
+Each search stack uses 512K bytes per level. The default stack depth
+is 16, thus 8MB per thread is used.
+.SH ACCESS CONTROL
+The 
+.B mdb
+backend honors access control semantics as indicated in
+.BR slapd.access (5).
+.SH FILES
+.TP
+.B ETCDIR/slapd.conf
+default 
+.B slapd 
+configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd\-config (5),
+.BR slapd (8),
+.BR slapadd (8),
+.BR slapcat (8),
+.BR slapindex (8),
+OpenLDAP MDB documentation.
+.SH ACKNOWLEDGEMENTS
+.so ../Project
+Written by Howard Chu.
diff --git a/servers/slapd/back-mdb/Makefile.in b/servers/slapd/back-mdb/Makefile.in
new file mode 100644 (file)
index 0000000..93cb911
--- /dev/null
@@ -0,0 +1,61 @@
+# Makefile.in for back-mdb
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2011 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 referral.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 referral.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
+
+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_R_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$(srcdir)/$(LDAP_LIBDIR)/libmdb
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+       @touch $@
+
+mdb.lo:        $(srcdir)/$(LDAP_LIBDIR)/libmdb/mdb.c
+       $(LTCOMPILE_MOD) $<
+
+midl.lo:       $(srcdir)/$(LDAP_LIBDIR)/libmdb/midl.c
+       $(LTCOMPILE_MOD) $<
+
+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 (file)
index 0000000..7822b9f
--- /dev/null
@@ -0,0 +1,438 @@
+/* add.c - ldap mdb back-end add routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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;
+       ID eid = NOID, pid = 0;
+       mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
+       int subentry;
+
+       int             success;
+
+       LDAPControl **postread_ctrl = NULL;
+       LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+       int num_ctrls = 0;
+
+#ifdef LDAP_X_TXN
+       int settle = 0;
+#endif
+
+       Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_add) ": %s\n",
+               op->ora_e->e_name.bv_val, 0, 0);
+
+#ifdef LDAP_X_TXN
+       if( op->o_txnSpec ) {
+               /* acquire connection lock */
+               ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+               if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
+                       rs->sr_text = "invalid transaction identifier";
+                       rs->sr_err = LDAP_X_TXN_ID_INVALID;
+                       goto txnReturn;
+               } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) {
+                       settle=1;
+                       goto txnReturn;
+               }
+
+               if( op->o_conn->c_txn_backend == NULL ) {
+                       op->o_conn->c_txn_backend = op->o_bd;
+
+               } else if( op->o_conn->c_txn_backend != op->o_bd ) {
+                       rs->sr_text = "transaction cannot span multiple database contexts";
+                       rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
+                       goto txnReturn;
+               }
+
+               /* insert operation into transaction */
+
+               rs->sr_text = "transaction specified";
+               rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY;
+
+txnReturn:
+               /* release connection lock */
+               ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+               if( !settle ) {
+                       send_ldap_result( op, rs );
+                       return rs->sr_err;
+               }
+       }
+#endif
+
+       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, 0 );
+               goto return_results;
+       }
+
+       /* 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, 0 );
+               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 );
+
+       /* 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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "internal error";
+               goto return_results;
+       }
+
+       txn = moi->moi_txn;
+
+       /*
+        * 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 );
+       }
+
+       /* get entry or parent */
+       rs->sr_err = mdb_dn2entry( op, txn, &op->ora_e->e_nname, &p, 1 );
+       switch( rs->sr_err ) {
+       case 0:
+               rs->sr_err = LDAP_ALREADY_EXISTS;
+               mdb_entry_return( 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( p );
+               p = NULL;
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_add) ": parent "
+                       "does not exist\n", 0, 0, 0 );
+
+               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( p );
+               p = NULL;
+
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_add) ": no write access to parent\n",
+                       0, 0, 0 );
+               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( p );
+                       p = NULL;
+                       /* parent is a subentry, don't allow add */
+                       Debug( LDAP_DEBUG_TRACE,
+                               LDAP_XSTRING(mdb_add) ": parent is subentry\n",
+                               0, 0, 0 );
+                       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( p );
+                       p = NULL;
+                       /* parent is an alias, don't allow add */
+                       Debug( LDAP_DEBUG_TRACE,
+                               LDAP_XSTRING(mdb_add) ": parent is alias\n",
+                               0, 0, 0 );
+                       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( p );
+                       p = NULL;
+                       Debug( LDAP_DEBUG_TRACE,
+                               LDAP_XSTRING(mdb_add) ": parent is referral\n",
+                               0, 0, 0 );
+
+                       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( 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",
+                       0, 0, 0 );
+               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",
+                       0, 0, 0 );
+               rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+               rs->sr_text = "no write access to attribute";
+               goto return_results;;
+       }
+
+       if ( eid == NOID ) {
+               rs->sr_err = mdb_next_id( op->o_bd, txn, &eid );
+               if( rs->sr_err != 0 ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                               LDAP_XSTRING(mdb_add) ": next_id failed (%d)\n",
+                               rs->sr_err, 0, 0 );
+                       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, txn, pid, op->ora_e );
+       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, 0 );
+
+               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",
+                       0, 0, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "index generation failed";
+               goto return_results;
+       }
+
+       /* id2entry index */
+       rs->sr_err = mdb_id2entry_add( op, txn, op->ora_e );
+       if ( rs->sr_err != 0 ) {
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_add) ": id2entry_add failed\n",
+                       0, 0, 0 );
+               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", 0, 0, 0 );
+                       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;
+               }
+
+               if (( rs->sr_err = mdb_txn_commit( txn )) != 0 ) {
+                       rs->sr_text = "txn_commit failed";
+                       Debug( LDAP_DEBUG_TRACE,
+                               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;
+               }
+               txn = NULL;
+       }
+
+       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_txn_abort( txn );
+               }
+               if ( opinfo.moi_oe.oe_key ) {
+                       LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+               }
+       }
+
+       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 (file)
index 0000000..8207d50
--- /dev/null
@@ -0,0 +1,522 @@
+/* attr.c - backend routines for dealing with attributes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 "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;
+       int i, flags;
+       int rc;
+
+       txn = tx0;
+       if ( txn == NULL ) {
+               rc = mdb_txn_begin( mdb->mi_dbenv, 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, 0, 0 );
+                       return rc;
+               }
+       } 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;
+               rc = mdb_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_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, 0, 0 );
+                       break;
+               }
+       }
+
+       /* 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, 0, 0 );
+                       }
+               } else {
+                       mdb_txn_abort( txn );
+               }
+       }
+
+       return rc;
+}
+
+void
+mdb_attr_dbs_close(
+       struct mdb_info *mdb,
+       MDB_txn *txn
+)
+{
+       int i;
+       for ( i=0; i<mdb->mi_nattrs; i++ )
+               if ( mdb->mi_attrs[i]->ai_dbi )
+                       mdb_close( txn, mdb->mi_attrs[i]->ai_dbi );
+}
+
+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 );
+                       }
+                       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 done;
+               }
+
+               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 done;
+               }
+
+               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 done;
+               }
+
+               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 done;
+               }
+
+               Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n",
+                       ad->ad_cname.bv_val, mask, 0 ); 
+
+               a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) );
+
+#ifdef LDAP_COMP_MATCH
+               a->ai_cr = NULL;
+#endif
+               a->ai_desc = ad;
+               a->ai_dbi = 0;
+
+               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
+                                */
+                               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 done;
+                               }
+                               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;
+                                       goto done;
+                               }
+                       }
+               }
+#endif
+               rc = ainfo_insert( mdb, a );
+               if( rc ) {
+                       if ( mdb->mi_flags & MDB_IS_OPEN ) {
+                               AttrInfo *b = mdb_attr_mask( mdb, ad );
+                               /* 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++ )
+               mdb_attr_index_unparser( mdb->mi_attrs[i], bva );
+}
+
+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 ) {
+                       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--;
+               }
+       }
+}
diff --git a/servers/slapd/back-mdb/back-mdb.h b/servers/slapd/back-mdb/back-mdb.h
new file mode 100644 (file)
index 0000000..a9c8de8
--- /dev/null
@@ -0,0 +1,162 @@
+/* back-mdb.h - mdb back-end header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 "mdb.h"
+
+LDAP_BEGIN_DECL
+
+#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_NDB                        3
+
+/* 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
+
+/* Default to 10MB max */
+#define DEFAULT_MAPSIZE        (10*1048576)
+
+#ifdef LDAP_DEVEL
+#define MDB_MONITOR_IDX
+#endif /* LDAP_DEVEL */
+
+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 */
+       /* The DB_ENV can be tuned via DB_CONFIG */
+       char            *mi_dbenv_home;
+       u_int32_t       mi_dbenv_flags;
+       int                     mi_dbenv_mode;
+
+       size_t          mi_mapsize;
+
+       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;
+
+       int                     mi_txn_cp;
+       u_int32_t       mi_txn_cp_min;
+       u_int32_t       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
+
+       MDB_dbi mi_dbis[MDB_NDB];
+};
+
+#define mi_id2entry    mi_dbis[MDB_ID2ENTRY]
+#define mi_dn2id       mi_dbis[MDB_DN2ID]
+#define mi_ad2id       mi_dbis[MDB_AD2ID]
+
+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
+
+/* Copy an ID "src" to pointer "dst" in big-endian byte order */
+#define MDB_ID2DISK( src, dst )        \
+       do { int i0; ID tmp; unsigned char *_p; \
+               tmp = (src); _p = (unsigned char *)(dst);       \
+               for ( i0=sizeof(ID)-1; i0>=0; i0-- ) {  \
+                       _p[i0] = tmp & 0xff; tmp >>= 8; \
+               } \
+       } while(0)
+
+/* Copy a pointer "src" to a pointer "dst" from big-endian to native order */
+#define MDB_DISK2ID( src, dst ) \
+       do { unsigned i0; ID tmp = 0; unsigned char *_p;        \
+               _p = (unsigned char *)(src);    \
+               for ( i0=0; i0<sizeof(ID); i0++ ) {     \
+                       tmp <<= 8; tmp |= *_p++;        \
+               } *(dst) = tmp; \
+       } while (0)
+
+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
+       int ai_idx;     /* position in AI array */
+       MDB_dbi ai_dbi;
+} AttrInfo;
+
+/* 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;
+
+typedef struct IndexRec {
+       AttrInfo *ai;
+       AttrList *attrs;
+} IndexRec;
+
+#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 (file)
index 0000000..fe34954
--- /dev/null
@@ -0,0 +1,156 @@
+/* bind.c - mdb backend bind routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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, 0, 0);
+
+       /* 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, &op->o_req_ndn, &e, 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", 0,
+                       0, 0 );
+               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", 0, 0, 0 );
+               rs->sr_err = LDAP_INVALID_CREDENTIALS;
+               goto done;
+       }
+
+       if ( is_entry_referral( e ) ) {
+               Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0,
+                       0, 0 );
+               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 );
+       }
+       /* free entry and reader lock */
+       if( e != NULL ) {
+               mdb_entry_return( 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 (file)
index 0000000..9bb05dc
--- /dev/null
@@ -0,0 +1,140 @@
+/* compare.c - mdb backend compare routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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, &op->o_req_ndn, &e, 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( 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", 0, 0, 0 );
+
+               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 );
+       }
+       /* free entry */
+       if ( e != NULL ) {
+               mdb_entry_return( 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 (file)
index 0000000..c6cb3cb
--- /dev/null
@@ -0,0 +1,588 @@
+/* config.c - mdb backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 "config.h"
+
+#include "lutil.h"
+#include "ldap_rq.h"
+
+static ConfigDriver mdb_cf_gen;
+
+enum {
+       MDB_CHKPT = 1,
+       MDB_DIRECTORY,
+       MDB_DBNOSYNC,
+       MDB_INDEX,
+       MDB_MAXREADERS,
+       MDB_MAXSIZE,
+       MDB_MODE,
+       MDB_SSTACK,
+};
+
+static ConfigTable mdbcfg[] = {
+       { "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 caseIgnoreMatch "
+                       "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' "
+                       "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' "
+                       "SYNTAX OMsBoolean SINGLE-VALUE )", 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 },
+       { "maxreaders", "num", 2, 2, 0, ARG_INT|ARG_MAGIC|MDB_MAXREADERS,
+               mdb_cf_gen, "( OLcfgDbAt:12.1 NAME 'olcDbMaxReaders' "
+               "DESC 'Maximum number of threads that may access the DB concurrently' "
+               "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' "
+               "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' "
+               "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+       { "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' "
+               "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+       { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+               NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs mdbocs[] = {
+       {
+               "( OLcfgDbOc:12.1 "
+               "NAME 'olcMdbConfig' "
+               "DESC 'MDB backend configuration' "
+               "SUP olcDatabaseConfig "
+               "MUST olcDbDirectory "
+               "MAY ( olcDbCheckpoint $ "
+               "olcDbNoSync $ olcDbIndex $ olcDbMaxReaders $ olcDbMaxsize $ "
+               "olcDbMode $ olcDbSearchStack ) )",
+                       Cft_Database, mdbcfg },
+       { NULL, 0, NULL }
+};
+
+/* 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, 0 );
+       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_data = &id;
+       key.mv_size = sizeof(ID);
+
+       while ( 1 ) {
+               if ( slapd_shutdown )
+                       break;
+
+               rc = mdb_txn_begin( mdb->mi_dbenv, 0, &txn );
+               if ( rc )
+                       break;
+               if ( getnext ) {
+                       getnext = 0;
+                       rc = mdb_cursor_open( txn, mdb->mi_id2entry, &curs );
+                       if ( rc ) {
+                               mdb_txn_abort( txn );
+                               break;
+                       }
+                       rc = mdb_cursor_get( curs, &key, &data, MDB_SET_RANGE );
+                       memcpy( &id, key.mv_data, sizeof( id ));
+                       mdb_cursor_close( curs );
+                       if ( rc ) {
+                               mdb_txn_abort( txn );
+                               if ( rc == MDB_NOTFOUND )
+                                       rc = 0;
+                               break;
+                       }
+               }
+
+               rc = mdb_id2entry( op, txn, id, &e );
+               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 );
+               if ( rc == 0 ) {
+                       rc = mdb_txn_commit( txn );
+                       txn = NULL;
+               }
+               if ( 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, 0, 0 );
+                       rc = LDAP_OTHER;
+               }
+       }
+
+       if ( 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_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;
+               }
+               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;
+                       c->cleanup = 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 );
+                       break;
+               case MDB_INDEX:
+                       if ( c->valx == -1 ) {
+                               int i;
+
+                               /* delete all (FIXME) */
+                               for ( i = 0; i < mdb->mi_nattrs; i++ ) {
+                                       mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING;
+                               }
+                               mdb->mi_flags |= MDB_DEL_INDEX;
+                               c->cleanup = 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;
+                                               c->cleanup = mdb_cf_cleanup;
+                                       }
+
+                                       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: {
+               long    l;
+               mdb->mi_txn_cp = 1;
+               if ( lutil_atolx( &l, c->argv[1], 0 ) != 0 ) {
+                       fprintf( stderr, "%s: "
+                               "invalid kbyte \"%s\" in \"checkpoint\".\n",
+                               c->log, c->argv[1] );
+                       return 1;
+               }
+               mdb->mi_txn_cp_kbyte = l;
+               if ( lutil_atolx( &l, 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_min = l;
+               /* 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 ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid path: %s",
+                               c->log, strerror( errno ));
+                       Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 );
+                       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_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;
+               c->cleanup = mdb_cf_cleanup;
+               mdb->mi_flags |= MDB_OPEN_INDEX;
+               if (( mdb->mi_flags & MDB_IS_OPEN ) && !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;
+               break;
+
+       case MDB_MAXSIZE:
+               mdb->mi_mapsize = c->value_ulong;
+               if ( mdb->mi_flags & MDB_IS_OPEN )
+                       mdb->mi_flags |= MDB_RE_OPEN;
+               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 (file)
index 0000000..01c31f1
--- /dev/null
@@ -0,0 +1,470 @@
+/* delete.c - mdb backend delete routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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_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;
+
+#ifdef LDAP_X_TXN
+       int settle = 0;
+#endif
+
+       Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_delete) ": %s\n",
+               op->o_req_dn.bv_val, 0, 0 );
+
+#ifdef LDAP_X_TXN
+       if( op->o_txnSpec ) {
+               /* acquire connection lock */
+               ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+               if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
+                       rs->sr_text = "invalid transaction identifier";
+                       rs->sr_err = LDAP_X_TXN_ID_INVALID;
+                       goto txnReturn;
+               } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) {
+                       settle=1;
+                       goto txnReturn;
+               }
+
+               if( op->o_conn->c_txn_backend == NULL ) {
+                       op->o_conn->c_txn_backend = op->o_bd;
+
+               } else if( op->o_conn->c_txn_backend != op->o_bd ) {
+                       rs->sr_text = "transaction cannot span multiple database contexts";
+                       rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
+                       goto txnReturn;
+               }
+
+               /* insert operation into transaction */
+
+               rs->sr_text = "transaction specified";
+               rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY;
+
+txnReturn:
+               /* release connection lock */
+               ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+               if( !settle ) {
+                       send_ldap_result( op, rs );
+                       return rs->sr_err;
+               }
+       }
+#endif
+
+       ctrls[num_ctrls] = 0;
+
+       /* 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 );
+       }
+
+       /* 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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "internal error";
+               goto return_results;
+       }
+
+       txn = moi->moi_txn;
+
+       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, &pdn, &p, 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, 0, 0);
+
+               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( 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, &op->o_req_ndn, &e, 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, 0, 0);
+
+               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( 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", 0, 0, 0 );
+                       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",
+                                               0, 0, 0 );
+                                       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", 0, 0, 0 );
+                               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", 0, 0, 0 );
+               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",
+                       0, 0, 0 );
+
+               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", 0, 0, 0 );
+                       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, 0, 0);
+                       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, 0 );
+                       rs->sr_err = LDAP_OTHER;
+                       rs->sr_text = "internal error";
+               }
+               goto return_results;
+       }
+
+       /* delete from dn2id */
+       rs->sr_err = mdb_dn2id_delete( op, txn, p->e_id, e );
+       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, 0 );
+               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, 0 );
+               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, 0 );
+               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, 0 );
+                               rs->sr_err = LDAP_OTHER;
+                               rs->sr_text = "internal error";
+                               goto return_results;
+                       }
+                       parent_is_leaf = 1;
+               }
+               mdb_entry_return( 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_TRACE,
+                       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( p );
+       }
+
+       /* free entry */
+       if( e != NULL ) {
+               mdb_entry_return( 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 );
+               }
+       }
+
+       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 (file)
index 0000000..3aa8167
--- /dev/null
@@ -0,0 +1,64 @@
+/* dn2entry.c - routines to deal with the dn2id / id2entry glue */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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,
+       struct berval *dn,
+       Entry **e,
+       int matched )
+{
+       int rc, rc2;
+       ID id = NOID;
+       struct berval mbv, nmbv;
+
+       Debug(LDAP_DEBUG_TRACE, "mdb_dn2entry(\"%s\")\n",
+               dn->bv_val, 0, 0 );
+
+       *e = NULL;
+
+       rc = mdb_dn2id( op, tid, dn, &id, &mbv, &nmbv );
+       if ( rc ) {
+               if ( matched )
+                       rc2 = mdb_id2entry( op, tid, id, e );
+
+       } else {
+               rc = mdb_id2entry( op, tid, id, e );
+       }
+       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 );
+       }
+
+       return rc;
+}
diff --git a/servers/slapd/back-mdb/dn2id.c b/servers/slapd/back-mdb/dn2id.c
new file mode 100644 (file)
index 0000000..c0ae178
--- /dev/null
@@ -0,0 +1,1083 @@
+/* dn2id.c - routines to deal with the dn2id index */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 accomodate
+ * RDNs up to length 32767, but that's fine since full DNs are already
+ * restricted to 8192.
+ *
+ * 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 */
+} 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] << 8) | un->nrdnlen[1];
+       return strncmp( un->nrdn, cn->nrdn, nrlen );
+}
+
+#if 0
+/* This function constructs a full DN for a given entry.
+ */
+int mdb_fix_dn(
+       Entry *e,
+       int checkit )
+{
+       EntryInfo *ei;
+       int rlen = 0, nrlen = 0;
+       char *ptr, *nptr;
+       int max = 0;
+
+       if ( !e->e_id )
+               return 0;
+
+       /* count length of all DN components */
+       for ( ei = BEI(e); ei && ei->bei_id; ei=ei->bei_parent ) {
+               rlen += ei->bei_rdn.bv_len + 1;
+               nrlen += ei->bei_nrdn.bv_len + 1;
+               if (ei->bei_modrdns > max) max = ei->bei_modrdns;
+       }
+
+       /* See if the entry DN was invalidated by a subtree rename */
+       if ( checkit ) {
+               if ( BEI(e)->bei_modrdns >= max ) {
+                       return 0;
+               }
+               /* We found a mismatch, tell the caller to lock it */
+               if ( checkit == 1 ) {
+                       return 1;
+               }
+               /* checkit == 2. do the fix. */
+               free( e->e_name.bv_val );
+               free( e->e_nname.bv_val );
+       }
+
+       e->e_name.bv_len = rlen - 1;
+       e->e_nname.bv_len = nrlen - 1;
+       e->e_name.bv_val = ch_malloc(rlen);
+       e->e_nname.bv_val = ch_malloc(nrlen);
+       ptr = e->e_name.bv_val;
+       nptr = e->e_nname.bv_val;
+       for ( ei = BEI(e); ei && ei->bei_id; ei=ei->bei_parent ) {
+               ptr = lutil_strcopy(ptr, ei->bei_rdn.bv_val);
+               nptr = lutil_strcopy(nptr, ei->bei_nrdn.bv_val);
+               if ( ei->bei_parent ) {
+                       *ptr++ = ',';
+                       *nptr++ = ',';
+               }
+       }
+       BEI(e)->bei_modrdns = max;
+       if ( ptr > e->e_name.bv_val ) ptr[-1] = '\0';
+       if ( nptr > e->e_nname.bv_val ) nptr[-1] = '\0';
+
+       return 0;
+}
+#endif
+
+/* 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_txn *txn,
+       ID pid,
+       Entry           *e )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       MDB_dbi dbi = mdb->mi_dn2id;
+       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, 0 );
+
+       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, 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 ));
+
+       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_put( txn, dbi, &key, &data, MDB_NODUPDATA );
+       }
+
+       data.mv_data = d;
+       data.mv_size = sizeof(diskNode) + rlen + nrlen;
+
+       rc = mdb_put( txn, dbi, &key, &data, MDB_NODUPDATA );
+
+       if (rc == 0) {
+               nid = e->e_id;
+               memcpy( ptr, &pid, sizeof( ID ));
+               d->nrdnlen[0] ^= 0x80;
+
+               rc = mdb_put( txn, dbi, &key, &data, MDB_NODUPDATA );
+       }
+
+       op->o_tmpfree( d, op->o_tmpmemctx );
+       Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 );
+
+       return rc;
+}
+
+int
+mdb_dn2id_delete(
+       Operation       *op,
+       MDB_txn *txn,
+       ID pid,
+       Entry   *e )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       MDB_dbi dbi = mdb->mi_dn2id;
+       MDB_val key, data;
+       diskNode *d;
+       int rc, nrlen;
+       ID      nid;
+
+       Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_delete 0x%lx: \"%s\"\n",
+               e->e_id, e->e_ndn, 0 );
+
+       key.mv_size = sizeof(ID);
+       key.mv_data = &nid;
+       nid = pid;
+
+       nrlen = dn_rdnlen( op->o_bd, &e->e_nname );
+       data.mv_size = sizeof(diskNode) + nrlen - sizeof(ID) - 1;
+
+       d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+       d->nrdnlen[1] = nrlen & 0xff;
+       d->nrdnlen[0] = (nrlen >> 8) | 0x80;
+       memcpy( d->nrdn, e->e_nname.bv_val, nrlen );
+       d->nrdn[nrlen] = '\0';
+       data.mv_data = d;
+
+       /* Delete our ID from the parent's list */
+       rc = mdb_del( txn, dbi, &key, &data );
+
+       /* 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 ) {
+               nid = e->e_id;
+               d->nrdnlen[0] ^= 0x80;
+               rc = mdb_del( txn, dbi, &key, &data );
+       }
+
+       op->o_tmpfree( d, op->o_tmpmemctx );
+
+       Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_delete 0x%lx: %d\n", e->e_id, rc, 0 );
+       return rc;
+}
+
+/* return last found ID in *id if no match */
+int
+mdb_dn2id(
+       Operation       *op,
+       MDB_txn *txn,
+       struct berval   *in,
+       ID      *id,
+       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, 0, 0 );
+
+       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);
+
+       rc = mdb_cursor_open( txn, dbi, &cursor );
+       if ( rc ) return rc;
+
+       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 - 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;
+                       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; 
+       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, 0 );
+       } else {
+               Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: got id=0x%lx\n",
+                       nid, 0, 0 );
+       }
+
+       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, 0, 0 );
+
+       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 ) return rc;
+
+       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 ) {
+                       mdb_cursor_close( cursor );
+                       break;
+               }
+               ptr = (char *) data.mv_data + data.mv_size - 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;
+               }
+       }
+
+done:
+       if( rc != 0 ) {
+               Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2sups: get failed: %s (%d)\n",
+                       mdb_strerror( rc ), rc, 0 );
+       }
+
+       return rc;
+}
+
+#if 0
+int
+mdb_dn2id_parent(
+       Operation *op,
+       DB_TXN *txn,
+       EntryInfo *ei,
+       ID *idp )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       DB *db = mdb->bi_dn2id->bdi_db;
+       DBT             key, data;
+       DBC     *cursor;
+       int             rc = 0;
+       diskNode *d;
+       char    *ptr;
+       ID      nid;
+
+       DBTzero(&key);
+       key.size = sizeof(ID);
+       key.data = &nid;
+       key.ulen = sizeof(ID);
+       key.flags = DB_DBT_USERMEM;
+       MDB_ID2DISK( ei->bei_id, &nid );
+
+       DBTzero(&data);
+       data.flags = DB_DBT_USERMEM;
+
+       rc = db->cursor( db, txn, &cursor, mdb->bi_db_opflags );
+       if ( rc ) return rc;
+
+       data.ulen = sizeof(diskNode) + (SLAP_LDAPDN_MAXLEN * 2);
+       d = op->o_tmpalloc( data.ulen, op->o_tmpmemctx );
+       data.data = d;
+
+       rc = cursor->c_get( cursor, &key, &data, DB_SET );
+       if ( rc == 0 ) {
+               if (d->nrdnlen[0] & 0x80) {
+                       rc = LDAP_OTHER;
+               } else {
+                       db_recno_t dkids;
+                       ptr = (char *) data.data + data.size - sizeof(ID);
+                       MDB_DISK2ID( ptr, idp );
+                       ei->bei_nrdn.bv_len = (d->nrdnlen[0] << 8) | d->nrdnlen[1];
+                       ber_str2bv( d->nrdn, ei->bei_nrdn.bv_len, 1, &ei->bei_nrdn );
+                       ei->bei_rdn.bv_len = data.size - sizeof(diskNode) -
+                               ei->bei_nrdn.bv_len;
+                       ptr = d->nrdn + ei->bei_nrdn.bv_len + 1;
+                       ber_str2bv( ptr, ei->bei_rdn.bv_len, 1, &ei->bei_rdn );
+                       /* How many children does this node have? */
+                       cursor->c_count( cursor, &dkids, 0 );
+                       ei->bei_dkids = dkids;
+               }
+       }
+       cursor->c_close( cursor );
+       op->o_tmpfree( d, op->o_tmpmemctx );
+       return rc;
+}
+#endif
+
+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 ) {
+               unsigned long 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, ci0, idc = 0;
+       char    *ptr;
+       int             rc;
+
+       key.mv_size = sizeof(ID);
+
+       MDB_IDL_ZERO( res );
+
+       rc = mdb_cursor_open( txn, dbi, &cursor );
+       if ( rc ) return rc;
+
+       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) {
+               id = ida;
+               while (id) {
+                       key.mv_data = &id;
+                       rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+                       if ( rc ) {
+                               /* not found, move on to next */
+                               if (idc) {
+                                       if (ci0 != cid)
+                                               ids[ci0] = ids[cid];
+                                       ci0++;
+                               }
+                               break;
+                       }
+                       ptr = data.mv_data;
+                       ptr += data.mv_size - sizeof(ID);
+                       memcpy( &id, ptr, sizeof(ID) );
+                       if ( id == base ) {
+                               res[0]++;
+                               res[res[0]] = ida;
+                               if (idc)
+                                       idc--;
+                               break;
+                       } else {
+                               if (idc) {
+                                       if (ci0 != cid)
+                                               ids[ci0] = ids[cid];
+                                       ci0++;
+                               }
+                       }
+                       if ( op->ors_scope == LDAP_SCOPE_ONELEVEL )
+                               break;
+               }
+               ida = mdb_idl_next( ids, &cid );
+       }
+       if (!MDB_IDL_IS_RANGE( ids ))
+               ids[0] = idc;
+
+       mdb_cursor_close( cursor );
+       return rc;
+}
+
+/* See if base is a child of any of the scopes
+ */
+int
+mdb_idscopes(
+       Operation *op,
+       MDB_txn *txn,
+       MDB_cursor **cursp,
+       ID base,
+       ID *scopes )
+{
+       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 id;
+       char    *ptr;
+       int             rc;
+       unsigned int x;
+
+       key.mv_size = sizeof(ID);
+
+       if ( !*cursp ) {
+               rc = mdb_cursor_open( txn, dbi, cursp );
+               if ( rc ) return rc;
+       }
+       cursor = *cursp;
+
+       id = base;
+       while (id) {
+               key.mv_data = &id;
+               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) );
+               x = mdb_idl_search( scopes, id );
+               if ( scopes[x] == id )
+                       return MDB_SUCCESS;
+               if ( op->ors_scope == LDAP_SCOPE_ONELEVEL )
+                       break;
+       }
+       return MDB_NOTFOUND;
+}
+
+#if 0
+/* mdb_dn2idl:
+ * We can't just use mdb_idl_fetch_key because
+ * 1 - our data items are longer than just an entry ID
+ * 2 - our data items are sorted alphabetically by nrdn, not by ID.
+ *
+ * We descend the tree recursively, so we define this cookie
+ * to hold our necessary state information. The mdb_dn2idl_internal
+ * function uses this cookie when calling itself.
+ */
+
+struct dn2id_cookie {
+       struct mdb_info *mdb;
+       Operation *op;
+       DB_TXN *txn;
+       EntryInfo *ei;
+       ID *ids;
+       ID *tmp;
+       ID *buf;
+       DB *db;
+       DBC *dbc;
+       DBT key;
+       DBT data;
+       ID dbuf;
+       ID id;
+       ID nid;
+       int rc;
+       int depth;
+       char need_sort;
+       char prefix;
+};
+
+static int
+apply_func(
+       void *data,
+       void *arg )
+{
+       EntryInfo *ei = data;
+       ID *idl = arg;
+
+       mdb_idl_append_one( idl, ei->bei_id );
+       return 0;
+}
+
+static int
+mdb_dn2idl_internal(
+       struct dn2id_cookie *cx
+)
+{
+       MDB_IDL_ZERO( cx->tmp );
+
+       if ( cx->mdb->bi_idl_cache_size ) {
+               char *ptr = ((char *)&cx->id)-1;
+
+               cx->key.data = ptr;
+               cx->key.size = sizeof(ID)+1;
+               if ( cx->prefix == DN_SUBTREE_PREFIX ) {
+                       ID *ids = cx->depth ? cx->tmp : cx->ids;
+                       *ptr = cx->prefix;
+                       cx->rc = mdb_idl_cache_get(cx->mdb, cx->db, &cx->key, ids);
+                       if ( cx->rc == LDAP_SUCCESS ) {
+                               if ( cx->depth ) {
+                                       mdb_idl_append( cx->ids, cx->tmp );
+                                       cx->need_sort = 1;
+                               }
+                               return cx->rc;
+                       }
+               }
+               *ptr = DN_ONE_PREFIX;
+               cx->rc = mdb_idl_cache_get(cx->mdb, cx->db, &cx->key, cx->tmp);
+               if ( cx->rc == LDAP_SUCCESS ) {
+                       goto gotit;
+               }
+               if ( cx->rc == DB_NOTFOUND ) {
+                       return cx->rc;
+               }
+       }
+
+       mdb_cache_entryinfo_lock( cx->ei );
+
+       /* If number of kids in the cache differs from on-disk, load
+        * up all the kids from the database
+        */
+       if ( cx->ei->bei_ckids+1 != cx->ei->bei_dkids ) {
+               EntryInfo ei;
+               db_recno_t dkids = cx->ei->bei_dkids;
+               ei.bei_parent = cx->ei;
+
+               /* Only one thread should load the cache */
+               while ( cx->ei->bei_state & CACHE_ENTRY_ONELEVEL ) {
+                       mdb_cache_entryinfo_unlock( cx->ei );
+                       ldap_pvt_thread_yield();
+                       mdb_cache_entryinfo_lock( cx->ei );
+                       if ( cx->ei->bei_ckids+1 == cx->ei->bei_dkids ) {
+                               goto synced;
+                       }
+               }
+
+               cx->ei->bei_state |= CACHE_ENTRY_ONELEVEL;
+
+               mdb_cache_entryinfo_unlock( cx->ei );
+
+               cx->rc = cx->db->cursor( cx->db, NULL, &cx->dbc,
+                       cx->mdb->bi_db_opflags );
+               if ( cx->rc )
+                       goto done_one;
+
+               cx->data.data = &cx->dbuf;
+               cx->data.ulen = sizeof(ID);
+               cx->data.dlen = sizeof(ID);
+               cx->data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
+
+               /* The first item holds the parent ID. Ignore it. */
+               cx->key.data = &cx->nid;
+               cx->key.size = sizeof(ID);
+               cx->rc = cx->dbc->c_get( cx->dbc, &cx->key, &cx->data, DB_SET );
+               if ( cx->rc ) {
+                       cx->dbc->c_close( cx->dbc );
+                       goto done_one;
+               }
+
+               /* If the on-disk count is zero we've never checked it.
+                * Count it now.
+                */
+               if ( !dkids ) {
+                       cx->dbc->c_count( cx->dbc, &dkids, 0 );
+                       cx->ei->bei_dkids = dkids;
+               }
+
+               cx->data.data = cx->buf;
+               cx->data.ulen = MDB_IDL_UM_SIZE * sizeof(ID);
+               cx->data.flags = DB_DBT_USERMEM;
+
+               if ( dkids > 1 ) {
+                       /* Fetch the rest of the IDs in a loop... */
+                       while ( (cx->rc = cx->dbc->c_get( cx->dbc, &cx->key, &cx->data,
+                               DB_MULTIPLE | DB_NEXT_DUP )) == 0 ) {
+                               u_int8_t *j;
+                               size_t len;
+                               void *ptr;
+                               DB_MULTIPLE_INIT( ptr, &cx->data );
+                               while (ptr) {
+                                       DB_MULTIPLE_NEXT( ptr, &cx->data, j, len );
+                                       if (j) {
+                                               EntryInfo *ei2;
+                                               diskNode *d = (diskNode *)j;
+                                               short nrlen;
+
+                                               MDB_DISK2ID( j + len - sizeof(ID), &ei.bei_id );
+                                               nrlen = ((d->nrdnlen[0] ^ 0x80) << 8) | d->nrdnlen[1];
+                                               ei.bei_nrdn.bv_len = nrlen;
+                                               /* nrdn/rdn are set in-place.
+                                                * mdb_cache_load will copy them as needed
+                                                */
+                                               ei.bei_nrdn.bv_val = d->nrdn;
+                                               ei.bei_rdn.bv_len = len - sizeof(diskNode)
+                                                       - ei.bei_nrdn.bv_len;
+                                               ei.bei_rdn.bv_val = d->nrdn + ei.bei_nrdn.bv_len + 1;
+                                               mdb_idl_append_one( cx->tmp, ei.bei_id );
+                                               mdb_cache_load( cx->mdb, &ei, &ei2 );
+                                       }
+                               }
+                       }
+               }
+
+               cx->rc = cx->dbc->c_close( cx->dbc );
+done_one:
+               mdb_cache_entryinfo_lock( cx->ei );
+               cx->ei->bei_state &= ~CACHE_ENTRY_ONELEVEL;
+               mdb_cache_entryinfo_unlock( cx->ei );
+               if ( cx->rc )
+                       return cx->rc;
+
+       } else {
+               /* The in-memory cache is in sync with the on-disk data.
+                * do we have any kids?
+                */
+synced:
+               cx->rc = 0;
+               if ( cx->ei->bei_ckids > 0 ) {
+                       /* Walk the kids tree; order is irrelevant since mdb_idl_sort
+                        * will sort it later.
+                        */
+                       avl_apply( cx->ei->bei_kids, apply_func,
+                               cx->tmp, -1, AVL_POSTORDER );
+               }
+               mdb_cache_entryinfo_unlock( cx->ei );
+       }
+
+       if ( !MDB_IDL_IS_RANGE( cx->tmp ) && cx->tmp[0] > 3 )
+               mdb_idl_sort( cx->tmp, cx->buf );
+       if ( cx->mdb->bi_idl_cache_max_size && !MDB_IDL_IS_ZERO( cx->tmp )) {
+               char *ptr = ((char *)&cx->id)-1;
+               cx->key.data = ptr;
+               cx->key.size = sizeof(ID)+1;
+               *ptr = DN_ONE_PREFIX;
+               mdb_idl_cache_put( cx->mdb, cx->db, &cx->key, cx->tmp, cx->rc );
+       }
+
+gotit:
+       if ( !MDB_IDL_IS_ZERO( cx->tmp )) {
+               if ( cx->prefix == DN_SUBTREE_PREFIX ) {
+                       mdb_idl_append( cx->ids, cx->tmp );
+                       cx->need_sort = 1;
+                       if ( !(cx->ei->bei_state & CACHE_ENTRY_NO_GRANDKIDS)) {
+                               ID *save, idcurs;
+                               EntryInfo *ei = cx->ei;
+                               int nokids = 1;
+                               save = cx->op->o_tmpalloc( MDB_IDL_SIZEOF( cx->tmp ),
+                                       cx->op->o_tmpmemctx );
+                               MDB_IDL_CPY( save, cx->tmp );
+
+                               idcurs = 0;
+                               cx->depth++;
+                               for ( cx->id = mdb_idl_first( save, &idcurs );
+                                       cx->id != NOID;
+                                       cx->id = mdb_idl_next( save, &idcurs )) {
+                                       EntryInfo *ei2;
+                                       cx->ei = NULL;
+                                       if ( mdb_cache_find_id( cx->op, cx->txn, cx->id, &cx->ei,
+                                               ID_NOENTRY, NULL ))
+                                               continue;
+                                       if ( cx->ei ) {
+                                               ei2 = cx->ei;
+                                               if ( !( ei2->bei_state & CACHE_ENTRY_NO_KIDS )) {
+                                                       MDB_ID2DISK( cx->id, &cx->nid );
+                                                       mdb_dn2idl_internal( cx );
+                                                       if ( !MDB_IDL_IS_ZERO( cx->tmp ))
+                                                               nokids = 0;
+                                               }
+                                               mdb_cache_entryinfo_lock( ei2 );
+                                               ei2->bei_finders--;
+                                               mdb_cache_entryinfo_unlock( ei2 );
+                                       }
+                               }
+                               cx->depth--;
+                               cx->op->o_tmpfree( save, cx->op->o_tmpmemctx );
+                               if ( nokids ) {
+                                       mdb_cache_entryinfo_lock( ei );
+                                       ei->bei_state |= CACHE_ENTRY_NO_GRANDKIDS;
+                                       mdb_cache_entryinfo_unlock( ei );
+                               }
+                       }
+                       /* Make sure caller knows it had kids! */
+                       cx->tmp[0]=1;
+
+                       cx->rc = 0;
+               } else {
+                       MDB_IDL_CPY( cx->ids, cx->tmp );
+               }
+       }
+       return cx->rc;
+}
+
+int
+mdb_dn2idl(
+       Operation       *op,
+       DB_TXN *txn,
+       struct berval *ndn,
+       EntryInfo       *ei,
+       ID *ids,
+       ID *stack )
+{
+       struct mdb_info *mdb = (struct mdb_info *)op->o_bd->be_private;
+       struct dn2id_cookie cx;
+
+       Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2idl(\"%s\")\n",
+               ndn->bv_val, 0, 0 );
+
+#ifndef MDB_MULTIPLE_SUFFIXES
+       if ( op->ors_scope != LDAP_SCOPE_ONELEVEL && 
+               ( ei->bei_id == 0 ||
+               ( ei->bei_parent->bei_id == 0 && op->o_bd->be_suffix[0].bv_len )))
+       {
+               MDB_IDL_ALL( mdb, ids );
+               return 0;
+       }
+#endif
+
+       cx.id = ei->bei_id;
+       MDB_ID2DISK( cx.id, &cx.nid );
+       cx.ei = ei;
+       cx.mdb = mdb;
+       cx.db = cx.mdb->bi_dn2id->bdi_db;
+       cx.prefix = (op->ors_scope == LDAP_SCOPE_ONELEVEL) ?
+               DN_ONE_PREFIX : DN_SUBTREE_PREFIX;
+       cx.ids = ids;
+       cx.tmp = stack;
+       cx.buf = stack + MDB_IDL_UM_SIZE;
+       cx.op = op;
+       cx.txn = txn;
+       cx.need_sort = 0;
+       cx.depth = 0;
+
+       if ( cx.prefix == DN_SUBTREE_PREFIX ) {
+               ids[0] = 1;
+               ids[1] = cx.id;
+       } else {
+               MDB_IDL_ZERO( ids );
+       }
+       if ( cx.ei->bei_state & CACHE_ENTRY_NO_KIDS )
+               return LDAP_SUCCESS;
+
+       DBTzero(&cx.key);
+       cx.key.ulen = sizeof(ID);
+       cx.key.size = sizeof(ID);
+       cx.key.flags = DB_DBT_USERMEM;
+
+       DBTzero(&cx.data);
+
+       mdb_dn2idl_internal(&cx);
+       if ( cx.need_sort ) {
+               char *ptr = ((char *)&cx.id)-1;
+               if ( !MDB_IDL_IS_RANGE( cx.ids ) && cx.ids[0] > 3 ) 
+                       mdb_idl_sort( cx.ids, cx.tmp );
+               cx.key.data = ptr;
+               cx.key.size = sizeof(ID)+1;
+               *ptr = cx.prefix;
+               cx.id = ei->bei_id;
+               if ( cx.mdb->bi_idl_cache_max_size )
+                       mdb_idl_cache_put( cx.mdb, cx.db, &cx.key, cx.ids, cx.rc );
+       }
+
+       if ( cx.rc == DB_NOTFOUND )
+               cx.rc = LDAP_SUCCESS;
+
+       return cx.rc;
+}
+#endif
diff --git a/servers/slapd/back-mdb/extended.c b/servers/slapd/back-mdb/extended.c
new file mode 100644 (file)
index 0000000..57a0571
--- /dev/null
@@ -0,0 +1,54 @@
+/* extended.c - mdb backend extended routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 (file)
index 0000000..3e3a87b
--- /dev/null
@@ -0,0 +1,1144 @@
+/* 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-2011 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", 0, 0, 0 );
+
+       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", 0, 0, 0 );
+               rc = presence_candidates( op, rtxn, f->f_desc, ids );
+               break;
+
+       case LDAP_FILTER_EQUALITY:
+               Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n", 0, 0, 0 );
+#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", 0, 0, 0 );
+               rc = approx_candidates( op, rtxn, f->f_ava, ids, tmp );
+               break;
+
+       case LDAP_FILTER_SUBSTRINGS:
+               Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n", 0, 0, 0 );
+               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", 0, 0, 0 );
+               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", 0, 0, 0 );
+               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", 0, 0, 0 );
+               MDB_IDL_ALL( ids );
+               break;
+
+       case LDAP_FILTER_AND:
+               Debug( LDAP_DEBUG_FILTER, "\tAND\n", 0, 0, 0 );
+               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", 0, 0, 0 );
+               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", 0, 0, 0 );
+                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, 0, 0 );
+               /* Must not return NULL, otherwise extended filters break */
+               MDB_IDL_ALL( 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, 0, 0 );
+       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, 0, 0 );
+       }
+
+       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", 0, 0, 0 );
+       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", 0, 0, 0 );
+               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, &mra->ma_value, &id, 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, 0, 0 );
+       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, 0, 0 );
+       }
+
+       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, 0, 0 );
+
+       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_TRACE,
+                       "<= mdb_presence_candidates: (%s) not indexed\n",
+                       desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               return 0;
+       }
+
+       if( prefix.bv_val == NULL ) {
+               Debug( LDAP_DEBUG_TRACE,
+                       "<= mdb_presence_candidates: (%s) no prefix\n",
+                       desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               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, 0, 0 );
+
+       if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
+               ID id;
+               rc = mdb_dn2id( op, rtxn, &ava->aa_value, &id, NULL, NULL );
+               if ( rc == LDAP_SUCCESS ) {
+                       /* exactly one ID can match */
+                       ids[0] = 1;
+                       ids[1] = id;
+               }
+               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_ANY,
+                       "<= mdb_equality_candidates: (%s) not indexed\n", 
+                       ava->aa_desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               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, 0, 0 );
+               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, 0 );
+                       break;
+               }
+
+               if( MDB_IDL_IS_ZERO( tmp ) ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                               "<= mdb_equality_candidates: (%s) NULL\n", 
+                               ava->aa_desc->ad_cname.bv_val, 0, 0 );
+                       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, 0, 0 );
+
+       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_ANY,
+                       "<= mdb_approx_candidates: (%s) not indexed\n",
+                       ava->aa_desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               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, 0 );
+               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, 0 );
+                       break;
+               }
+
+               if( MDB_IDL_IS_ZERO( tmp ) ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                               "<= mdb_approx_candidates: (%s) NULL\n",
+                               ava->aa_desc->ad_cname.bv_val, 0, 0 );
+                       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, 0, 0 );
+
+       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_ANY,
+                       "<= mdb_substring_candidates: (%s) not indexed\n",
+                       sub->sa_desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               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, 0 );
+               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, 0 );
+               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, 0 );
+                       break;
+               }
+
+               if( MDB_IDL_IS_ZERO( tmp ) ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                               "<= mdb_substring_candidates: (%s) NULL\n",
+                               sub->sa_desc->ad_cname.bv_val, 0, 0 );
+                       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, 0, 0 );
+
+       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_ANY,
+                       "<= mdb_inequality_candidates: (%s) not indexed\n", 
+                       ava->aa_desc->ad_cname.bv_val, 0, 0 );
+               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, 0 );
+               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, 0, 0 );
+               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, 0 );
+                       break;
+               }
+
+               if( MDB_IDL_IS_ZERO( tmp ) ) {
+                       Debug( LDAP_DEBUG_TRACE,
+                              "<= mdb_inequality_candidates: (%s) NULL\n", 
+                              ava->aa_desc->ad_cname.bv_val, 0, 0 );
+                       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 (file)
index 0000000..511fda9
--- /dev/null
@@ -0,0 +1,425 @@
+/* id2entry.c - routines to deal with the id2entry database */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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"
+
+static int mdb_id2entry_put(
+       Operation *op,
+       MDB_txn *tid,
+       Entry *e,
+       int flag )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       MDB_dbi dbi = mdb->mi_id2entry;
+       MDB_val key, data;
+       struct berval bv;
+       int rc;
+       struct berval odn, ondn;
+
+       /* We only store rdns, and they go in the dn2id database. */
+
+       odn = e->e_name; ondn = e->e_nname;
+
+       e->e_name = slap_empty_bv;
+       e->e_nname = slap_empty_bv;
+
+       key.mv_data = &e->e_id;
+       key.mv_size = sizeof(ID);
+
+       rc = entry_encode( e, &bv );
+       e->e_name = odn; e->e_nname = ondn;
+       if( rc != LDAP_SUCCESS ) {
+               return -1;
+       }
+
+       data.mv_size = bv.bv_len;
+       data.mv_data = bv.bv_val;
+
+       rc = mdb_put( tid, dbi, &key, &data, flag );
+
+       op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+       return rc;
+}
+
+/*
+ * This routine adds (or updates) an entry on disk.
+ * The cache should be already be updated.
+ */
+
+
+int mdb_id2entry_add(
+       Operation *op,
+       MDB_txn *tid,
+       Entry *e )
+{
+       return mdb_id2entry_put(op, tid, e, MDB_NOOVERWRITE);
+}
+
+int mdb_id2entry_update(
+       Operation *op,
+       MDB_txn *tid,
+       Entry *e )
+{
+       return mdb_id2entry_put(op, tid, e, 0);
+}
+
+int mdb_id2entry(
+       Operation *op,
+       MDB_txn *tid,
+       ID id,
+       Entry **e )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       MDB_dbi dbi = mdb->mi_id2entry;
+       MDB_val key, data;
+       EntryHeader eh;
+       int rc = 0;
+
+       *e = NULL;
+
+       key.mv_data = &id;
+       key.mv_size = sizeof(ID);
+
+       /* fetch it */
+       rc = mdb_get( tid, dbi, &key, &data );
+       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 = entry_alloc();
+
+                       r->e_id = 0;
+                       attr_merge_one( r, slap_schema.si_ad_objectClass, &gluebv, NULL );
+                       attr_merge_one( r, slap_schema.si_ad_structuralObjectClass, &gluebv, NULL );
+                       r->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END;
+                       *e = r;
+                       return MDB_SUCCESS;
+               }
+       }
+       if ( rc ) return rc;
+
+       eh.bv.bv_val = data.mv_data;
+       eh.bv.bv_len = data.mv_size;
+       rc = entry_header( &eh );
+       if ( rc ) return rc;
+
+       if ( eh.nvals ) {
+               eh.bv.bv_len = eh.nvals * sizeof( struct berval );
+               eh.bv.bv_val = ch_malloc( eh.bv.bv_len );
+               rc = entry_decode(&eh, e);
+       } else {
+               *e = entry_alloc();
+       }
+
+       if( rc == 0 ) {
+               (*e)->e_id = id;
+               (*e)->e_name.bv_val = NULL;
+               (*e)->e_nname.bv_val = NULL;
+       } else {
+               ch_free( eh.bv.bv_val );
+       }
+
+       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;
+       int rc;
+
+       key.mv_data = &e->e_id;
+       key.mv_size = sizeof(ID);
+
+       /* delete from database */
+       rc = mdb_del( tid, dbi, &key, NULL );
+
+       return rc;
+}
+
+int mdb_entry_return(
+       Entry *e
+)
+{
+       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;
+       int rc;
+       /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE,
+                       SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */
+       mdb_entry_return ( e );
+       if ( slapMode == SLAP_SERVER_MODE ) {
+               OpExtra *oex;
+               LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+                       if ( oex->oe_key == mdb ) {
+                               moi = (mdb_op_info *)oex;
+                               /* If it was setup by entry_get we should probably free it */
+                               if ( moi->moi_flag & 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;
+                       }
+               }
+       }
+       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, 0, 0 ); 
+       Debug( LDAP_DEBUG_ARGS,
+               "=> mdb_entry_get: oc: \"%s\", at: \"%s\"\n",
+               oc ? oc->soc_cname.bv_val : "(null)", at_name, 0);
+
+       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, ndn, &e, 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, 0, 0 ); 
+               rc = LDAP_NO_SUCH_OBJECT;
+               goto return_results;
+       }
+       
+       Debug( LDAP_DEBUG_ACL,
+               "=> mdb_entry_get: found entry: \"%s\"\n",
+               ndn->bv_val, 0, 0 ); 
+
+       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, 0, 0 ); 
+               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, 0, 0 ); 
+               rc = LDAP_NO_SUCH_ATTRIBUTE;
+               goto return_results;
+       }
+
+return_results:
+       if( rc != LDAP_SUCCESS ) {
+               /* free entry */
+               if ( e )
+                       mdb_entry_return( e );
+
+               if (moi->moi_ref == 1) {
+                       LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+                       mdb_txn_reset( txn );
+                       op->o_tmpfree( moi, op->o_tmpmemctx );
+               }
+       } else {
+               *ent = e;
+       }
+
+       Debug( LDAP_DEBUG_TRACE,
+               "mdb_entry_get: rc=%d\n",
+               rc, 0, 0 ); 
+       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 );
+       }
+}
+
+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 ) {
+                       rc = mdb_txn_begin( mdb->mi_dbenv, 0, &moi->moi_txn );
+                       if (rc) {
+                               Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+                                       mdb_strerror(rc), rc, 0 );
+                       }
+                       return rc;
+               }
+               return 0;
+       }
+
+       /* OK, this is a reader */
+       if ( !moi->moi_txn ) {
+               if ( !ctx ) {
+                       /* Shouldn't happen unless we're single-threaded */
+                       rc = mdb_txn_begin( mdb->mi_dbenv, MDB_RDONLY, &moi->moi_txn );
+                       if (rc) {
+                               Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+                                       mdb_strerror(rc), rc, 0 );
+                       }
+                       return rc;
+               }
+               if ( ldap_pvt_thread_pool_getkey( ctx, mdb->mi_dbenv, &data, NULL ) ) {
+                       rc = mdb_txn_begin( mdb->mi_dbenv, MDB_RDONLY, &moi->moi_txn );
+                       if (rc) {
+                               Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+                                       mdb_strerror(rc), rc, 0 );
+                               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, 0, 0 );
+                               return rc;
+                       }
+               } else {
+                       moi->moi_txn = data;
+                       renew = 1;
+               }
+               moi->moi_flag |= MOI_READER;
+       }
+       if ( moi->moi_ref < 1 ) {
+               moi->moi_ref = 0;
+       }
+       if ( renew ) {
+               mdb_txn_renew( moi->moi_txn );
+       }
+       moi->moi_ref++;
+       if ( *moip != moi )
+               *moip = moi;
+
+       return 0;
+}
diff --git a/servers/slapd/back-mdb/idl.c b/servers/slapd/back-mdb/idl.c
new file mode 100644 (file)
index 0000000..cc18123
--- /dev/null
@@ -0,0 +1,1166 @@
+/* idl.c - ldap id list handling routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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"
+
+#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 ? 1 : 0 ) )
+
+#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], 0, 0 );
+
+               for( i=1; i<=ids[0]; i++ ) {
+                       if( i % 16 == 1 ) {
+                               Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 );
+                       }
+                       Debug( LDAP_DEBUG_ANY, "  %02lx", (long) ids[i], 0, 0 );
+               }
+
+               Debug( LDAP_DEBUG_ANY, "\n", 0, 0, 0 );
+       }
+
+       idl_check( ids );
+}
+#endif /* IDL_DEBUG > 1 */
+#endif /* IDL_DEBUG > 0 */
+
+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 postion greater than id
+        */
+       unsigned base = 0;
+       unsigned cursor = 0;
+       int val = 0;
+       unsigned n = ids[0];
+
+#if IDL_DEBUG > 0
+       idl_check( ids );
+#endif
+
+       while( 0 < n ) {
+               int pivot = n >> 1;
+               cursor = base + pivot;
+               val = IDL_CMP( id, ids[cursor + 1] );
+
+               if( val < 0 ) {
+                       n = pivot;
+
+               } else if ( val > 0 ) {
+                       base = cursor + 1;
+                       n -= pivot + 1;
+
+               } else {
+                       return cursor + 1;
+               }
+       }
+       
+       if( val > 0 ) {
+               return cursor + 2;
+       } else {
+               return cursor + 1;
+       }
+
+#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, 0 );
+       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_FIRST(ids) && id <= MDB_IDL_LAST(ids))
+                       return -1;
+               if (id < MDB_IDL_FIRST(ids))
+                       ids[1] = id;
+               else if (id > MDB_IDL_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, 0 );
+       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(
+       MDB_val         *key,
+       char            *buf )
+{
+       if ( key->mv_size == 4 /* LUTIL_HASH_BYTES */ ) {
+               unsigned char *c = key->mv_data;
+               sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] );
+               return buf;
+       } else {
+               return key->mv_data;
+       }
+}
+
+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( key, keybuf ), 0, 0 );
+
+       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, 0 );
+                       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], 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, 0 );
+               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, 0 );
+               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, 0 );
+               return -1;
+       }
+
+       return rc;
+}
+
+int
+mdb_idl_insert_key(
+       BackendDB       *be,
+       MDB_txn         *txn,
+       MDB_dbi         dbi,
+       MDB_val         *key,
+       ID                      id )
+{
+       MDB_cursor *cursor;
+       MDB_val data;
+       ID lo, hi, *i;
+       char *err;
+       int     rc;
+
+       {
+               char buf[16];
+               Debug( LDAP_DEBUG_ARGS,
+                       "mdb_idl_insert_key: %lx %s\n", 
+                       (long) id, mdb_show_key( key, buf ), 0 );
+       }
+
+       assert( id != NOID );
+
+       rc = mdb_cursor_open( txn, dbi, &cursor );
+       if ( rc != 0 ) {
+               Debug( LDAP_DEBUG_ANY, "=> mdb_idl_insert_key: "
+                       "cursor failed: %s (%d)\n", mdb_strerror(rc), rc, 0 );
+               return rc;
+       }
+       /* Fetch the first data item for this key, to see if it
+        * exists and if it's a range.
+        */
+       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 */
+                       unsigned long 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 */
+                               MDB_val key2;
+
+                               lo = *i;
+                               rc = mdb_cursor_get( cursor, &key2, &data, MDB_NEXT_NODUP );
+                               if ( rc != 0 && rc != MDB_NOTFOUND ) {
+                                       err = "c_get next_nodup";
+                                       goto fail;
+                               }
+                               if ( rc == MDB_NOTFOUND ) {
+                                       rc = mdb_cursor_get( cursor, key, &data, MDB_LAST );
+                                       if ( rc != 0 ) {
+                                               err = "c_get last";
+                                               goto fail;
+                                       }
+                               } else {
+                                       rc = mdb_cursor_get( cursor, key, &data, MDB_PREV );
+                                       if ( rc != 0 ) {
+                                               err = "c_get prev";
+                                               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_del( txn, dbi, key, NULL );
+                               if ( rc != 0 ) {
+                                       err = "mdb_del";
+                                       goto fail;
+                               }
+                               /* Store the range */
+                               data.mv_size = sizeof(ID);
+                               data.mv_data = &id;
+                               id = 0;
+                               rc = mdb_put( txn, dbi, key, &data, 0 );
+                               if ( rc != 0 ) {
+                                       err = "mdb_put range";
+                                       goto fail;
+                               }
+                               id = lo;
+                               rc = mdb_put( txn, dbi, key, &data, 0 );
+                               if ( rc != 0 ) {
+                                       err = "mdb_put lo";
+                                       goto fail;
+                               }
+                               id = hi;
+                               rc = mdb_put( txn, dbi, key, &data, 0 );
+                               if ( rc != 0 ) {
+                                       err = "mdb_put hi";
+                                       goto fail;
+                               }
+                       } else {
+                       /* There's room, just store it */
+                               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 ) {
+                               if ( id < lo )
+                                       data.mv_data = &lo;
+                               else
+                                       data.mv_data = &hi;
+                               data.mv_size = sizeof(ID);
+                               /* Delete the current lo/hi */
+                               rc = mdb_del( txn, dbi, key, &data );
+                               if ( rc != 0 ) {
+                                       err = "mdb_del lo/hi";
+                                       goto fail;
+                               }
+                               goto put1;
+                       }
+               }
+       } else if ( rc == MDB_NOTFOUND ) {
+put1:  data.mv_data = &id;
+               data.mv_size = sizeof(ID);
+               rc = mdb_put( txn, dbi, key, &data, MDB_NODUPDATA );
+               /* Don't worry if it's already there */
+               if ( rc != 0 && rc != MDB_KEYEXIST ) {
+                       err = "mdb_put id";
+                       goto fail;
+               }
+       } else {
+               /* initial c_get failed, nothing was done */
+fail:
+               Debug( LDAP_DEBUG_ANY, "=> mdb_idl_insert_key: "
+                       "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc );
+       }
+       mdb_cursor_close( cursor );
+       return rc;
+}
+
+int
+mdb_idl_delete_key(
+       BackendDB       *be,
+       MDB_txn         *txn,
+       MDB_dbi         dbi,
+       MDB_val         *key,
+       ID                      id )
+{
+       int     rc;
+       MDB_val data;
+       MDB_cursor *cursor;
+       ID lo, hi, tmp, *i;
+       char *err;
+
+       {
+               char buf[16];
+               Debug( LDAP_DEBUG_ARGS,
+                       "mdb_idl_delete_key: %lx %s\n", 
+                       (long) id, mdb_show_key( key, buf ), 0 );
+       }
+       assert( id != NOID );
+
+       rc = mdb_cursor_open( txn, dbi, &cursor );
+       if ( rc != 0 ) {
+               Debug( LDAP_DEBUG_ANY, "=> mdb_idl_delete_key: "
+                       "cursor failed: %s (%d)\n", mdb_strerror(rc), rc, 0 );
+               return rc;
+       }
+       /* Fetch the first data item for this key, to see if it
+        * exists and if it's a range.
+        */
+       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_del( txn, dbi, key, &data );
+                       if ( rc != 0 ) {
+                               err = "mdb_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... */
+                                       rc = mdb_del( txn, dbi, key, NULL );
+                                       if ( rc != 0 ) {
+                                               err = "mdb_del";
+                                               goto fail;
+                                       }
+                               } else {
+                                       data.mv_size = sizeof(ID);
+                                       if ( id == lo )
+                                               data.mv_data = &lo;
+                                       else
+                                               data.mv_data = &hi;
+                                       rc = mdb_del( txn, dbi, key, &data );
+                                       if ( rc != 0 ) {
+                                               err = "c_del";
+                                               goto fail;
+                                       }
+                                       if ( id == lo )
+                                               data.mv_data = &lo2;
+                                       else
+                                               data.mv_data = &hi2;
+                                       rc = mdb_put( txn, dbi, key, &data, 0 );
+                                       if ( rc != 0 ) {
+                                               err = "mdb_put lo/hi";
+                                               goto fail;
+                                       }
+                               }
+                       }
+               }
+       } else {
+               /* initial c_get failed, nothing was done */
+fail:
+               if ( rc != MDB_NOTFOUND ) {
+                       Debug( LDAP_DEBUG_ANY, "=> mdb_idl_delete_key: "
+                               "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc );
+               }
+       }
+       mdb_cursor_close( cursor );
+       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 idmin to idmax is contiguous, just
+        * turn it into a range.
+        */
+       if ( MDB_IDL_IS_RANGE( b )
+               && MDB_IDL_FIRST( b ) <= MDB_IDL_FIRST( a )
+               && MDB_IDL_LAST( b ) >= MDB_IDL_LAST( a ) ) {
+               if (idmax - idmin + 1 == a[0])
+               {
+                       a[0] = NOID;
+                       a[1] = idmin;
+                       a[2] = idmax;
+               }
+               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_FIRST(ids) && id <= MDB_IDL_LAST(ids))
+                       return -1;
+               if (id < MDB_IDL_FIRST(ids))
+                       ids[1] = id;
+               else if (id > MDB_IDL_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;
+       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-1) {
+                               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 mbackes@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 occurences 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 */
diff --git a/servers/slapd/back-mdb/idl.h b/servers/slapd/back-mdb/idl.h
new file mode 100644 (file)
index 0000000..21de4d8
--- /dev/null
@@ -0,0 +1,74 @@
+/* idl.h - ldap mdb back-end ID list header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 */
+#define MDB_IDL_DB_SIZE                (1<<MDB_IDL_LOGN)
+#define MDB_IDL_UM_SIZE                (1<<(MDB_IDL_LOGN+1))
+#define MDB_IDL_UM_SIZEOF      (MDB_IDL_UM_SIZE * sizeof(ID))
+
+#define MDB_IDL_DB_MAX         (MDB_IDL_DB_SIZE-1)
+
+#define MDB_IDL_UM_MAX         (MDB_IDL_UM_SIZE-1)
+
+#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_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] )
+
+LDAP_BEGIN_DECL
+LDAP_END_DECL
+
+#endif
diff --git a/servers/slapd/back-mdb/index.c b/servers/slapd/back-mdb/index.c
new file mode 100644 (file)
index 0000000..1505733
--- /dev/null
@@ -0,0 +1,555 @@
+/* index.c - routines for dealing with attribute indexes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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};
+static struct berval presence_key = BER_BVC(presence_keyval);
+
+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;
+                       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,
+       MDB_dbi dbi,
+       AttributeDescription *ad,
+       struct berval *atname,
+       BerVarray vals,
+       ID id,
+       int opid,
+       slap_mask_t mask )
+{
+       int rc, i;
+       struct berval *keys;
+
+       assert( mask != 0 );
+
+       if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) {
+               rc = mdb_key_change( op->o_bd, txn, dbi, &presence_key, id, opid );
+               if( rc ) {
+                       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 ) {
+                       for( i=0; keys[i].bv_val != NULL; i++ ) {
+                               rc = mdb_key_change( op->o_bd, txn, dbi, &keys[i], id, opid );
+                               if( rc ) {
+                                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+                                       goto done;
+                               }
+                       }
+                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+               }
+               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 ) {
+                       for( i=0; keys[i].bv_val != NULL; i++ ) {
+                               rc = mdb_key_change( op->o_bd, txn, dbi, &keys[i], id, opid );
+                               if( rc ) {
+                                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+                                       goto done;
+                               }
+                       }
+                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+               }
+
+               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 ) {
+                       for( i=0; keys[i].bv_val != NULL; i++ ) {
+                               rc = mdb_key_change( op->o_bd, txn, dbi, &keys[i], id, opid );
+                               if( rc ) {
+                                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+                                       goto done;
+                               }
+                       }
+                       ber_bvarray_free_x( keys, op->o_tmpmemctx );
+               }
+
+               rc = LDAP_SUCCESS;
+       }
+
+done:
+       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 ) {
+#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->ai_dbi, 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->ai_dbi, 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 ) {
+                               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->ai_dbi, 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].ai = mdb->mi_attrs[slot];
+                       al = ch_malloc( sizeof( AttrList ));
+                       al->attr = a;
+                       al->next = ir[slot].attrs;
+                       ir[slot].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].ai = mdb->mi_attrs[slot];
+                               al = ch_malloc( sizeof( AttrList ));
+                               al->attr = a;
+                               al->next = ir[slot].attrs;
+                               ir[slot].attrs = al;
+                       }
+               }
+       }
+       return LDAP_SUCCESS;
+}
+
+/* Apply the indices for the recset */
+int mdb_index_recrun(
+       Operation *op,
+       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) {
+               ir = ir0 + i;
+               if ( !ir->ai ) continue;
+               while (( al = ir->attrs )) {
+                       ir->attrs = al->next;
+                       rc = indexer( op, NULL, ir->ai->ai_dbi, ir->ai->ai_desc,
+                               &ir->ai->ai_desc->ad_type->sat_cname,
+                               al->attr->a_nvals, id, SLAP_INDEX_ADD_OP,
+                               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 );
+
+       /* 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 );
+
+       return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/back-mdb/init.c b/servers/slapd/back-mdb/init.c
new file mode 100644 (file)
index 0000000..4e174e2
--- /dev/null
@@ -0,0 +1,445 @@
+/* init.c - initialize mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 "config.h"
+
+static const struct berval mdmi_databases[] = {
+       BER_BVC("ad2i"),
+       BER_BVC("dn2i"),
+       BER_BVC("id2e"),
+       BER_BVNULL
+};
+
+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",
+               0, 0, 0 );
+
+       /* 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;
+
+       be->be_private = mdb;
+       be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+#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;
+       u_int32_t flags;
+       char *dbhome;
+       MDB_txn *txn;
+
+       if ( be->be_suffix == NULL ) {
+               Debug( LDAP_DEBUG_ANY,
+                       LDAP_XSTRING(mdb_db_open) ": need suffix.\n",
+                       1, 0, 0 );
+               return -1;
+       }
+
+       Debug( LDAP_DEBUG_ARGS,
+               LDAP_XSTRING(mdb_db_open) ": \"%s\"\n",
+               be->be_suffix[0].bv_val, 0, 0 );
+
+       /* Check existence of dbenv_home. Any error means trouble */
+       rc = stat( mdb->mi_dbenv_home, &stat1 );
+       if( rc != 0 ) {
+               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, 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, 0);
+
+       flags = mdb->mi_dbenv_flags;
+
+       if ( slapMode & SLAP_TOOL_QUICK )
+               flags |= MDB_NOSYNC;
+
+       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, err %d. "
+                       "Restore from backup!\n",
+                       be->be_suffix[0].bv_val, rc, 0 );
+               goto fail;
+       }
+
+       rc = mdb_txn_begin( mdb->mi_dbenv, 0, &txn );
+       if ( rc ) {
+               Debug( LDAP_DEBUG_ANY,
+                       LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened, err %d. "
+                       "Restore from backup!\n",
+                       be->be_suffix[0].bv_val, rc, 0 );
+               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 ( !(slapMode & SLAP_TOOL_READONLY) )
+                               flags |= MDB_CREATE;
+               }
+
+               rc = mdb_open( txn,
+                       mdmi_databases[i].bv_val,
+                       flags,
+                       &mdb->mi_dbis[i] );
+
+               if ( rc != 0 ) {
+                       snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+                               "mdb_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, 0, 0 );
+                       goto fail;
+               }
+
+               if ( i == MDB_DN2ID )
+                       mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_dup_compare );
+
+       }
+
+       rc = mdb_attr_dbs_open( be, txn, cr );
+       if ( rc ) {
+               mdb_txn_abort( txn );
+               goto fail;
+       }
+
+       rc = mdb_txn_commit(txn);
+       if ( rc != 0 ) {
+               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;
+       MDB_txn *txn;
+
+       /* 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;
+                       rc = mdb_txn_begin( mdb->mi_dbenv, MDB_RDONLY, &txn );
+
+                       mdb_attr_dbs_close( mdb, txn );
+                       for ( i=0; i<MDB_NDB; i++ )
+                               mdb_close( txn, mdb->mi_dbis[i] );
+
+                       mdb_txn_abort( txn );
+
+                       /* force a sync */
+                       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,
+#ifdef LDAP_X_TXN
+               LDAP_CONTROL_X_TXN_SPEC,
+#endif
+               NULL
+       };
+
+       /* initialize the underlying database system */
+       Debug( LDAP_DEBUG_TRACE,
+               LDAP_XSTRING(mdb_back_initialize) ": initialize " 
+               MDB_UCTYPE " backend\n", 0, 0, 0 );
+
+       bi->bi_flags |=
+               SLAP_BFLAG_INCREMENT |
+               SLAP_BFLAG_SUBENTRIES |
+               SLAP_BFLAG_ALIASES |
+               SLAP_BFLAG_REFERRALS;
+
+       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, 0, 0 );
+                       return -1;
+               }
+
+               Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_back_initialize)
+                       ": %s\n", version, 0, 0 );
+       }
+
+       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_extended = mdb_extended;
+
+#if 0  /* FIXME: Redundant, why does this exist? */
+       bi->bi_chk_referrals = mdb_referrals;
+#endif
+       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_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 (file)
index 0000000..e333920
--- /dev/null
@@ -0,0 +1,94 @@
+/* index.c - routines for dealing with attribute indexes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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;
+
+       Debug( LDAP_DEBUG_TRACE, "=> key_read\n", 0, 0, 0 );
+
+       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, 0, 0 );
+       } else {
+               Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read %ld candidates\n",
+                       (long) MDB_IDL_N(ids), 0, 0 );
+       }
+
+       return rc;
+}
+
+/* Add or remove stuff from index files */
+int
+mdb_key_change(
+       Backend *be,
+       MDB_txn *txn,
+       MDB_dbi dbi,
+       struct berval *k,
+       ID id,
+       int op
+)
+{
+       int     rc;
+       MDB_val key;
+
+       Debug( LDAP_DEBUG_TRACE, "=> key_change(%s,%lx)\n",
+               op == SLAP_INDEX_ADD_OP ? "ADD":"DELETE", (long) id, 0 );
+
+       key.mv_size = k->bv_len;
+       key.mv_data = k->bv_val;
+
+       if (op == SLAP_INDEX_ADD_OP) {
+               /* Add values */
+               rc = mdb_idl_insert_key( be, txn, dbi, &key, id );
+               if ( rc == MDB_KEYEXIST ) rc = 0;
+       } else {
+               /* Delete values */
+               rc = mdb_idl_delete_key( be, txn, dbi, &key, id );
+               if ( rc == MDB_NOTFOUND ) rc = 0;
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "<= key_change %d\n", rc, 0, 0 );
+
+       return rc;
+}
diff --git a/servers/slapd/back-mdb/modify.c b/servers/slapd/back-mdb/modify.c
new file mode 100644 (file)
index 0000000..c577ea9
--- /dev/null
@@ -0,0 +1,693 @@
+/* modify.c - mdb backend modify routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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
+};
+
+static void
+mdb_modify_idxflags(
+       Operation *op,
+       AttributeDescription *desc,
+       int got_delete,
+       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 ( got_delete ) {
+                       Attribute       *ap;
+                       struct berval   ix2;
+
+                       ap = attr_find( oldattrs, desc );
+                       if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL;
+
+                       /* 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;
+                       }
+
+               } else {
+                       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 )
+{
+       int rc, err;
+       Modification    *mod;
+       Modifications   *ml;
+       Attribute       *save_attrs;
+       Attribute       *ap;
+       int                     glue_attr_delete = 0;
+       int                     got_delete;
+
+       Debug( LDAP_DEBUG_TRACE, "mdb_modify_internal: 0x%08lx: %s\n",
+               e->e_id, e->e_dn, 0);
+
+       if ( !acl_check_modlist( op, e, modlist )) {
+               return LDAP_INSUFFICIENT_ACCESS;
+       }
+
+       /* save_attrs will be disposed of by mdb_cache_modify */
+       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;
+               got_delete = 0;
+
+               switch ( mod->sm_op ) {
+               case LDAP_MOD_ADD:
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: add %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       err = modify_add_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, 0);
+                       }
+                       break;
+
+               case LDAP_MOD_DELETE:
+                       if ( glue_attr_delete ) {
+                               err = LDAP_SUCCESS;
+                               break;
+                       }
+
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: delete %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       err = modify_delete_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, 0);
+                       } else {
+                               got_delete = 1;
+                       }
+                       break;
+
+               case LDAP_MOD_REPLACE:
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: replace %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       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, 0);
+                       } else {
+                               got_delete = 1;
+                       }
+                       break;
+
+               case LDAP_MOD_INCREMENT:
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: increment %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       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, 0);
+                       } else {
+                               got_delete = 1;
+                       }
+                       break;
+
+               case SLAP_MOD_SOFTADD:
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: softadd %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       /* Avoid problems in index_add_mods()
+                        * We need to add index if necessary.
+                        */
+                       mod->sm_op = LDAP_MOD_ADD;
+
+                       err = modify_add_values( e, mod, get_permissiveModify(op),
+                               text, textbuf, textlen );
+
+                       mod->sm_op = SLAP_MOD_SOFTADD;
+
+                       if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+                               err = LDAP_SUCCESS;
+                       }
+
+                       if( err != LDAP_SUCCESS ) {
+                               Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+                                       err, *text, 0);
+                       }
+                       break;
+
+               case SLAP_MOD_SOFTDEL:
+                       Debug(LDAP_DEBUG_ARGS,
+                               "mdb_modify_internal: softdel %s\n",
+                               mod->sm_desc->ad_cname.bv_val, 0, 0);
+                       /* Avoid problems in index_delete_mods()
+                        * We need to add index if necessary.
+                        */
+                       mod->sm_op = LDAP_MOD_DELETE;
+
+                       err = modify_delete_values( e, mod, get_permissiveModify(op),
+                               text, textbuf, textlen );
+
+                       mod->sm_op = SLAP_MOD_SOFTDEL;
+
+                       if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+                               err = LDAP_SUCCESS;
+                       }
+
+                       if( err != LDAP_SUCCESS ) {
+                               Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+                                       err, *text, 0);
+                       }
+                       break;
+
+               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, 0, 0);
+                       /* Avoid problems in index_add_mods()
+                        * We need to add index if necessary.
+                        */
+                       mod->sm_op = LDAP_MOD_ADD;
+
+                       err = modify_add_values( e, mod, get_permissiveModify(op),
+                               text, textbuf, textlen );
+
+                       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, 0);
+                       }
+                       break;
+
+               default:
+                       Debug(LDAP_DEBUG_ANY, "mdb_modify_internal: invalid op %d\n",
+                               mod->sm_op, 0, 0);
+                       *text = "Invalid modify operation";
+                       err = LDAP_OTHER;
+                       Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+                               err, *text, 0);
+               }
+
+               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, got_delete, 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, 0, 0 );
+               }
+
+               /* 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,
+                               1, 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;
+                               vals = op->o_tmpalloc( (ap->a_numvals + 1) *
+                                       sizeof(struct berval), op->o_tmpmemctx );
+                               j = 0;
+                               for ( i=0; i < ap->a_numvals; i++ ) {
+                                       rc = attr_valfind( a2, SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+                                               &ap->a_nvals[i], NULL, op->o_tmpmemctx );
+                                       /* Save deleted values */
+                                       if ( rc == LDAP_NO_SUCH_ATTRIBUTE )
+                                               vals[j++] = ap->a_nvals[i];
+                               }
+                               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, 0 );
+                                       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;
+                       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, 0 );
+                               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;
+
+#ifdef LDAP_X_TXN
+       int settle = 0;
+#endif
+
+       Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(mdb_modify) ": %s\n",
+               op->o_req_dn.bv_val, 0, 0 );
+
+#ifdef LDAP_X_TXN
+       if( op->o_txnSpec ) {
+               /* acquire connection lock */
+               ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+               if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
+                       rs->sr_text = "invalid transaction identifier";
+                       rs->sr_err = LDAP_X_TXN_ID_INVALID;
+                       goto txnReturn;
+               } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) {
+                       settle=1;
+                       goto txnReturn;
+               }
+
+               if( op->o_conn->c_txn_backend == NULL ) {
+                       op->o_conn->c_txn_backend = op->o_bd;
+
+               } else if( op->o_conn->c_txn_backend != op->o_bd ) {
+                       rs->sr_text = "transaction cannot span multiple database contexts";
+                       rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
+                       goto txnReturn;
+               }
+
+               /* insert operation into transaction */
+
+               rs->sr_text = "transaction specified";
+               rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY;
+
+txnReturn:
+               /* release connection lock */
+               ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+               if( !settle ) {
+                       send_ldap_result( op, rs );
+                       return rs->sr_err;
+               }
+       }
+#endif
+
+       ctrls[num_ctrls] = NULL;
+
+       /* 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 );
+       }
+
+       /* 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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "internal error";
+               goto return_results;
+       }
+
+       txn = moi->moi_txn;
+
+       /* get entry or ancestor */
+       rs->sr_err = mdb_dn2entry( op, txn, &op->o_req_ndn, &e, 1 );
+
+       if ( rs->sr_err != 0 ) {
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_modify) ": dn2entry failed (%d)\n",
+                       rs->sr_err, 0, 0 );
+               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( 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",
+                       0, 0, 0 );
+
+               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", 0, 0, 0 );
+                       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, 0, 0 );
+               /* 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, &dummy );
+       if ( rs->sr_err != 0 ) {
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_modify) ": id2entry update failed " "(%d)\n",
+                       rs->sr_err, 0, 0 );
+               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", 0, 0, 0 );
+                       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_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_TRACE,
+                       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_txn_abort( txn );
+               }
+               if ( opinfo.moi_oe.oe_key ) {
+                       LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+               }
+       }
+
+       if( e != NULL ) {
+               mdb_entry_return( 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 (file)
index 0000000..b628c25
--- /dev/null
@@ -0,0 +1,655 @@
+/* modrdn.c - mdb backend modrdn routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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;
+       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;
+       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;
+
+#ifdef LDAP_X_TXN
+       int settle = 0;
+#endif
+
+       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" );
+
+#ifdef LDAP_X_TXN
+       if( op->o_txnSpec ) {
+               /* acquire connection lock */
+               ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+               if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
+                       rs->sr_text = "invalid transaction identifier";
+                       rs->sr_err = LDAP_X_TXN_ID_INVALID;
+                       goto txnReturn;
+               } else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) {
+                       settle=1;
+                       goto txnReturn;
+               }
+
+               if( op->o_conn->c_txn_backend == NULL ) {
+                       op->o_conn->c_txn_backend = op->o_bd;
+
+               } else if( op->o_conn->c_txn_backend != op->o_bd ) {
+                       rs->sr_text = "transaction cannot span multiple database contexts";
+                       rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
+                       goto txnReturn;
+               }
+
+               /* insert operation into transaction */
+
+               rs->sr_text = "transaction specified";
+               rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY;
+
+txnReturn:
+               /* release connection lock */
+               ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+               if( !settle ) {
+                       send_ldap_result( op, rs );
+                       return rs->sr_err;
+               }
+       }
+#endif
+
+       ctrls[num_ctrls] = NULL;
+
+       slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+       /* 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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "internal error";
+               goto return_results;
+       }
+
+       txn = moi->moi_txn;
+
+       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_dn2entry( op, txn, &p_ndn, &p, 0 );
+       switch( rs->sr_err ) {
+       case MDB_NOTFOUND:
+               Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn)
+                       ": parent does not exist\n", 0, 0, 0);
+               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", 0,
+                       0, 0 );
+               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, 0, 0 );
+
+       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, 0, 0 );
+
+       /* get entry */
+       rs->sr_err = mdb_dn2entry( op, txn, &op->o_req_ndn, &e, 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( 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", 0,
+                       0, 0 );
+               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, 0, 0 );
+
+               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, 0, 0 );
+
+               /*  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, 0 );
+                       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, np_ndn, &np, 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, 0, 0);
+                               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",
+                                       0, 0, 0 );
+                               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, 0 );
+
+                       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",
+                                       0, 0, 0 );
+                               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",
+                                       0, 0, 0 );
+                               rs->sr_text = "new superior is a referral";
+                               rs->sr_err = LDAP_OTHER;
+                               goto return_results;
+                       }
+                       new_parent_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",
+                                               0, 0, 0 );
+                                       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",
+                       0, 0, 0 );
+
+               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, 0, 0 );
+
+       /* Shortcut the search */
+       rs->sr_err = mdb_dn2id ( op, txn, &new_ndn, &nid, 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;
+       }
+
+       assert( op->orr_modlist != NULL );
+
+       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", 0, 0, 0 );
+                       if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+                               /* FIXME: is it correct to abort
+                                * operation if control fails? */
+                               goto return_results;
+                       }
+               }
+       }
+
+       /* delete old DN */
+       rs->sr_err = mdb_dn2id_delete( op, txn, p->e_id, e );
+       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, 0 );
+               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, txn, np ? np->e_id : p->e_id, &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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "DN index add failed";
+               goto return_results;
+       }
+
+       dummy.e_attrs = e->e_attrs;
+
+       /* 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, 0 );
+               if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+               goto return_results;
+       }
+
+       /* id2entry index */
+       rs->sr_err = mdb_id2entry_update( op, txn, &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, 0 );
+               rs->sr_err = LDAP_OTHER;
+               rs->sr_text = "entry update failed";
+               goto return_results;
+       }
+
+       if ( p_ndn.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_modrdn)
+                                       ": has_children failed: %s (%d)\n",
+                                       mdb_strerror(rs->sr_err), rs->sr_err, 0 );
+                               rs->sr_err = LDAP_OTHER;
+                               rs->sr_text = "internal error";
+                               goto return_results;
+                       }
+                       parent_is_leaf = 1;
+               }
+               mdb_entry_return( 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", 0, 0, 0 );
+                       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;
+                       /* Only free attrs if they were dup'd.  */
+                       if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = 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_TRACE,
+                       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 ( 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
+
+       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( np );
+       }
+
+       if( p != NULL ) {
+               /* free parent */
+               mdb_entry_return( p );
+       }
+
+       /* free entry */
+       if( e != NULL ) {
+               mdb_entry_return( 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 );
+               }
+       }
+
+       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 (file)
index 0000000..728bd01
--- /dev/null
@@ -0,0 +1,656 @@
+/* monitor.c - monitor mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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 "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_olmMDBNotIndexed;
+#endif /* MDB_MONITOR_IDX */
+
+/*
+ * 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.1
+ *
+ * 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.1
+ */
+
+static struct {
+       char                    *name;
+       char                    *oid;
+}              s_oid[] = {
+       { "olmMDBAttributes",                   "olmDatabaseAttributes:1" },
+       { "olmMDBObjectClasses",                "olmDatabaseObjectClasses:1" },
+
+       { NULL }
+};
+
+static struct {
+       char                    *desc;
+       AttributeDescription    **ad;
+}              s_at[] = {
+       { "( olmMDBAttributes:4 "
+               "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
+       { "( olmMDBAttributes:5 "
+               "NAME ( 'olmMDBNotIndexed' ) "
+               "DESC 'Missing indexes resulting from candidate selection' "
+               "SUP monitoredInfo "
+               "NO-USER-MODIFICATION "
+               "USAGE dSAOperation )",
+               &ad_olmMDBNotIndexed },
+#endif /* MDB_MONITOR_IDX */
+
+       { 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
+                       "$ olmMDBNotIndexed "
+#endif /* MDB_MONITOR_IDX */
+                       ") )",
+               &oc_olmMDBDatabase },
+
+       { NULL }
+};
+
+static int
+mdb_monitor_update(
+       Operation       *op,
+       SlapReply       *rs,
+       Entry           *e,
+       void            *priv )
+{
+       struct mdb_info         *mdb = (struct mdb_info *) priv;
+
+#ifdef MDB_MONITOR_IDX
+       mdb_monitor_idx_entry_add( mdb, e );
+#endif /* MDB_MONITOR_IDX */
+
+       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, 0 );
+                       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, 0, 0 );
+                       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, 0, 0 );
+                       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 )
+{
+       struct mdb_info         *mdb = (struct mdb_info *) be->be_private;
+
+       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;
+       struct berval dummy = BER_BVC("");
+
+       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_ANY, LDAP_XSTRING(mdb_monitor_db_open)
+                               ": monitoring disabled; "
+                               "configure monitor database to enable\n",
+                               0, 0, 0 );
+               }
+
+               return 0;
+       }
+
+       /* alloc as many as required (plus 1 for objectClass) */
+       a = attrs_alloc( 1 + 1 );
+       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, 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,
+                       &dummy, 0, &dummy );
+       }
+
+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 ) {
+                       mbe = mi->bi_extra;
+                       mbe->unregister_entry_callback( &mdb->mi_monitor.mdm_ndn,
+                               (monitor_callback_t *)mdb->mi_monitor.mdm_cb,
+                               NULL, 0, NULL );
+               }
+
+               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 );
+       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 *)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 ( 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_olmMDBNotIndexed );
+
+       ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex );
+
+       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_olmMDBNotIndexed );
+                       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 (file)
index 0000000..1d7b389
--- /dev/null
@@ -0,0 +1,59 @@
+/* init.c - initialize mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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_txn *tid, ID *out )
+{
+       struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+       int rc;
+       ID id = 0;
+       MDB_val key;
+       MDB_cursor *cursor;
+
+       /* Get a read cursor */
+       rc = mdb_cursor_open( tid, mdb->mi_id2entry, &cursor );
+
+       if (rc == 0) {
+               rc = mdb_cursor_get(cursor, &key, NULL, MDB_LAST);
+               mdb_cursor_close(cursor);
+       }
+
+       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, 0 );
+               goto done;
+       }
+
+done:
+       return rc;
+}
diff --git a/servers/slapd/back-mdb/operational.c b/servers/slapd/back-mdb/operational.c
new file mode 100644 (file)
index 0000000..28f6e7b
--- /dev/null
@@ -0,0 +1,119 @@
+/* operational.c - mdb backend operational attributes function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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, 0 );
+               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 );
+       }
+       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 (file)
index 0000000..2c05d6b
--- /dev/null
@@ -0,0 +1,376 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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, MDB_txn *txn );
+
+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 ));
+
+void mdb_attr_info_free( AttrInfo *ai );
+
+/*
+ * config.c
+ */
+
+int mdb_back_init_cf( BackendInfo *bi );
+
+/*
+ * dn2entry.c
+ */
+
+int mdb_dn2entry LDAP_P(( Operation *op, MDB_txn *tid,
+       struct berval *dn, Entry **e, int matched ));
+
+/*
+ * dn2id.c
+ */
+
+int mdb_dn2id(
+       Operation *op,
+       MDB_txn *txn,
+       struct berval *ndn,
+       ID *id,
+       struct berval *matched,
+       struct berval *nmatched );
+
+int mdb_dn2id_add(
+       Operation *op,
+       MDB_txn *tid,
+       ID pid,
+       Entry *e );
+
+int mdb_dn2id_delete(
+       Operation *op,
+       MDB_txn *tid,
+       ID pid,
+       Entry *e );
+
+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 );
+
+int mdb_idscopes(
+       Operation *op,
+       MDB_txn *txn,
+       MDB_cursor **cursp,
+       ID base,
+       ID *scopes );
+
+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
+ */
+
+int mdb_id2entry_add(
+       Operation *op,
+       MDB_txn *tid,
+       Entry *e );
+
+int mdb_id2entry_update(
+       Operation *op,
+       MDB_txn *tid,
+       Entry *e );
+
+int mdb_id2entry_delete(
+       BackendDB *be,
+       MDB_txn *tid,
+       Entry *e);
+
+int mdb_id2entry(
+       Operation *op,
+       MDB_txn *tid,
+       ID id,
+       Entry **e);
+
+void mdb_entry_free ( Entry *e );
+int mdb_entry_return( Entry *e );
+BI_entry_release_rw mdb_entry_release;
+BI_entry_get_rw mdb_entry_get;
+
+void mdb_reader_flush( MDB_env *env );
+int mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moi );
+
+/*
+ * 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 );
+
+int mdb_idl_insert_key(
+       BackendDB *be,
+       MDB_txn *txn,
+       MDB_dbi dbi,
+       MDB_val *key,
+       ID id );
+
+int mdb_idl_delete_key(
+       BackendDB *be,
+       MDB_txn *txn,
+       MDB_dbi dbi,
+       MDB_val *key,
+       ID id );
+
+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,
+       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 );
+
+extern int
+mdb_key_change(
+    Backend     *be,
+       MDB_txn *txn,
+    MDB_dbi dbi,
+    struct berval *k,
+    ID id,
+    int        op );
+       
+/*
+ * nextid.c
+ */
+
+int mdb_next_id( BackendDB *be, MDB_txn *tid, 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;
+
+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 (file)
index 0000000..be74c48
--- /dev/null
@@ -0,0 +1,149 @@
+/* referral.c - MDB backend referral handler */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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, 0 );
+               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( 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 );
+       }
+       if ( e )
+               mdb_entry_return( e );
+       return rc;
+}
diff --git a/servers/slapd/back-mdb/search.c b/servers/slapd/back-mdb/search.c
new file mode 100644 (file)
index 0000000..2009f00
--- /dev/null
@@ -0,0 +1,1112 @@
+/* search.c - search operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2011 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,
+       MDB_txn *txn,
+       ID      *ids,
+       ID      *scopes );
+
+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, &ndn, &e, 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( *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,
+       Entry *e,
+       MDB_txn *txn,
+       ID *scopes,
+       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;
+       int first = 1;
+
+       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, txn, &af, aliases,
+               curscop, visited );
+       if (rs->sr_err != LDAP_SUCCESS) {
+               return rs->sr_err;
+       }
+       oldsubs[0] = 1;
+       oldsubs[1] = e->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, txn, e->e_id, aliases, curscop );
+
+               if (first) {
+                       first = 0;
+               } else {
+                       mdb_entry_return( e );
+               }
+
+               /* 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, txn, 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 (a);
+                               continue;
+                       }
+
+                       /* Actually dereference the alias */
+                       MDB_IDL_ZERO(tmp);
+                       a = deref_base( op, rs, a, &matched, txn,
+                               tmp, visited );
+                       if (a) {
+                               /* If the target was not already in our current scopes,
+                                * make note of it in the newsubs list.
+                                */
+                               if (mdb_idl_insert(scopes, a->e_id) == 0) {
+                                       mdb_idl_insert(newsubs, a->e_id);
+                               }
+                               mdb_entry_return( 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( matched );
+                               rs->sr_text = NULL;
+                       }
+               }
+               /* 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.
+                */
+               rs->sr_err = mdb_id2entry(op, txn, ido, &e);
+               if ( rs->sr_err != LDAP_SUCCESS ) {
+                       goto nextido;
+               }
+       }
+       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(struct mdb_info *mdb, MDB_txn *txn, ID *cursor)
+{
+       MDB_cursor *curs;
+       MDB_val key;
+       ID id;
+       int rc;
+
+       id = *cursor + 1;
+       rc = mdb_cursor_open( txn, mdb->mi_id2entry, &curs );
+       if ( rc )
+               return rc;
+       key.mv_data = &id;
+       key.mv_size = sizeof(ID);
+       rc = mdb_cursor_get( curs, &key, NULL, MDB_SET_RANGE );
+       mdb_cursor_close( curs );
+       if ( rc )
+               return rc;
+       memcpy( cursor, key.mv_data, sizeof(ID));
+       return 0;
+}
+
+int
+mdb_search( Operation *op, SlapReply *rs )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       ID              id, cursor;
+       ID              lastid = NOID;
+       ID              candidates[MDB_IDL_UM_SIZE];
+       ID              scopes[MDB_IDL_DB_SIZE];
+       Entry           *e = NULL, base;
+       Entry           *matched = NULL;
+       AttributeName   *attrs;
+       struct berval   realbase = BER_BVNULL;
+       slap_mask_t     mask;
+       time_t          stoptime;
+       int             manageDSAit;
+       int             tentries = 0;
+
+       mdb_op_info     opinfo = {0}, *moi = &opinfo;
+       MDB_txn                 *ltid = NULL;
+       MDB_cursor      *idcursor = NULL;
+
+       Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_search) "\n", 0, 0, 0);
+       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;
+
+       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, &op->o_req_ndn, &e, 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 (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 (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(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( 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",
+                       0, 0, 0 );
+
+               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(e);
+               send_ldap_result( op, rs );
+               goto done;
+       }
+
+       /* compute it anyway; root does not use it */
+       stoptime = op->o_time + op->ors_tlimit;
+
+       /* need normalized dn below */
+       ber_dupbv( &realbase, &e->e_nname );
+
+       /* Copy info to base, must free entry before accessing the database
+        * in search_candidates, to avoid deadlocks.
+        */
+       base.e_private = e->e_private;
+       base.e_nname = realbase;
+       base.e_id = e->e_id;
+
+       mdb_entry_return(e);
+       e = NULL;
+
+       /* select candidates */
+       if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) {
+               rs->sr_err = base_candidate( op->o_bd, &base, candidates );
+
+       } else {
+               MDB_IDL_ZERO( candidates );
+               MDB_IDL_ZERO( scopes );
+               mdb_idl_insert( scopes, base.e_id );
+               rs->sr_err = search_candidates( op, rs, &base,
+                       ltid, candidates, scopes );
+       }
+
+       /* start cursor at beginning of candidates.
+        */
+       cursor = 0;
+
+       if ( candidates[0] == 0 ) {
+               Debug( LDAP_DEBUG_TRACE,
+                       LDAP_XSTRING(mdb_search) ": no candidates\n",
+                       0, 0, 0 );
+
+               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 &&
+               MDB_IDL_N(candidates) > (unsigned) op->ors_limit->lms_s_unchecked )
+       {
+               rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+               send_ldap_result( op, rs );
+               rs->sr_err = LDAP_SUCCESS;
+               goto done;
+       }
+
+       if ( op->ors_limit == NULL      /* isroot == TRUE */ ||
+               !op->ors_limit->lms_s_pr_hide )
+       {
+               tentries = MDB_IDL_N(candidates);
+       }
+
+       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;
+               }
+               id = mdb_idl_first( candidates, &cursor );
+               if ( id == NOID ) {
+                       Debug( LDAP_DEBUG_TRACE, 
+                               LDAP_XSTRING(mdb_search)
+                               ": no paged results candidates\n",
+                               0, 0, 0 );
+                       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 );
+               goto loop_begin;
+       }
+
+       for ( id = mdb_idl_first( candidates, &cursor );
+                 id != NOID ; id = mdb_idl_next( candidates, &cursor ) )
+       {
+               int scopeok;
+
+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;
+               }
+
+               /* get the entry */
+               rs->sr_err = mdb_id2entry( op, ltid, id, &e );
+
+               if (rs->sr_err == LDAP_BUSY) {
+                       rs->sr_text = "ldap server busy";
+                       send_ldap_result( op, rs );
+                       goto done;
+
+               } else if ( rs->sr_err == LDAP_OTHER ) {
+                       rs->sr_text = "internal error";
+                       send_ldap_result( op, rs );
+                       goto done;
+               }
+
+               if ( e == NULL ) {
+                       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, 0, 0 );
+                       } else {
+                               /* get the next ID from the DB */
+                               rs->sr_err = mdb_get_nextid( mdb, ltid, &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;
+               }
+
+               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;
+               }
+
+               /* Does this candidate actually satisfy the search scope?
+                */
+               scopeok = 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 ( mdb_idscopes( op, ltid, &idcursor, id, scopes ) == MDB_SUCCESS ) scopeok = 1;
+                       break;
+               }
+
+               /* 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) ||
+                                       !bvmatch(&e->e_nname, &op->o_req_ndn)))
+                       {
+                               goto loop_continue;
+                       }
+               }
+
+               /* Not in scope, ignore it */
+               if ( !scopeok )
+               {
+                       Debug( LDAP_DEBUG_TRACE,
+                               LDAP_XSTRING(mdb_search)
+                               ": %ld scope not okay\n",
+                               (long) id, 0, 0 );
+                       goto loop_continue;
+               }
+
+               if ( !manageDSAit && is_entry_glue( e )) {
+                       goto loop_continue;
+               }
+
+               mdb_id2name( op, ltid, &idcursor, e->e_id,
+                       &e->e_name, &e->e_nname );
+
+               /*
+                * 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 );
+
+                       mdb_entry_return( 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 ) {
+                                       mdb_entry_return( 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;
+                               mdb_entry_return( e );
+                               e = NULL;
+
+                               switch ( rs->sr_err ) {
+                               case LDAP_SUCCESS:      /* entry sent ok */
+                                       break;
+                               default:                /* entry not sent */
+                                       break;
+                               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, 0, 0 );
+               }
+
+loop_continue:
+               if( e != NULL ) {
+                       /* free reader lock */
+                       mdb_entry_return( e );
+                       RS_ASSERT( rs->sr_entry == NULL );
+                       e = NULL;
+                       rs->sr_entry = NULL;
+               }
+       }
+
+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 ( moi == &opinfo ) {
+               mdb_txn_reset( moi->moi_txn );
+               LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+       }
+       if( idcursor )
+               mdb_cursor_close( idcursor );
+       if( rs->sr_v2ref ) {
+               ber_bvarray_free( rs->sr_v2ref );
+               rs->sr_v2ref = NULL;
+       }
+       if( realbase.bv_val ) ch_free( realbase.bv_val );
+
+       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, 0);
+
+       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;
+}
+
+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;
+       void *ret = NULL;
+
+       if ( op->o_threadctx ) {
+               ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack,
+                       &ret, NULL );
+       } else {
+               ret = mdb->mi_search_stack;
+       }
+
+       if ( !ret ) {
+               ret = ch_malloc( mdb->mi_search_stack_depth * MDB_IDL_UM_SIZE
+                       * sizeof( ID ) );
+               if ( op->o_threadctx ) {
+                       ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack,
+                               ret, search_stack_free, NULL, NULL );
+               } else {
+                       mdb->mi_search_stack = ret;
+               }
+       }
+       return ret;
+}
+
+static int search_candidates(
+       Operation *op,
+       SlapReply *rs,
+       Entry *e,
+       MDB_txn *txn,
+       ID      *ids,
+       ID      *scopes )
+{
+       struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+       int rc, depth = 1;
+       Filter          *f, rf, xf, nf, sf;
+       ID              *stack;
+       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 ) );
+       } else {
+               stack = search_stack( op );
+       }
+
+       if( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+               rc = search_aliases( op, rs, e, txn, scopes, stack );
+       } else {
+               rc = LDAP_SUCCESS;
+       }
+
+       if ( rc == LDAP_SUCCESS ) {
+               rc = mdb_filter_candidates( op, txn, 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, NULL, NULL );
+
+       } 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, NULL );
+
+       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 (file)
index 0000000..1e95a48
--- /dev/null
@@ -0,0 +1,875 @@
+/* tools.c - tools for slap tools */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2011 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"
+
+static MDB_txn *txn = NULL, *txi = NULL;
+static MDB_cursor *cursor = NULL, *idcursor = NULL;
+static MDB_val key, data;
+static EntryHeader eh;
+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;
+
+#if 0
+static ID mdb_tool_ix_id;
+static Operation *mdb_tool_ix_op;
+static int *mdb_tool_index_threads, mdb_tool_index_tcount;
+static void *mdb_tool_index_rec;
+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 );
+#endif
+
+static int     mdb_writes, mdb_writes_per_commit;
+
+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 1000 entries */
+       mdb_writes = 0;
+       if ( slapMode & SLAP_TOOL_QUICK )
+               mdb_writes_per_commit = 1000;
+       else
+               mdb_writes_per_commit = 1;
+
+#if 0
+       /* Set up for threaded slapindex */
+       if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) {
+               if ( !mdb_tool_info ) {
+                       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->bi_nattrs ) {
+                               int i;
+                               mdb_tool_index_threads = ch_malloc( slap_tool_thread_max * sizeof( int ));
+                               mdb_tool_index_rec = ch_malloc( mdb->bi_nattrs * sizeof( IndexRec ));
+                               mdb_tool_index_tcount = slap_tool_thread_max - 1;
+                               for (i=1; i<slap_tool_thread_max; 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 )
+{
+#if 0
+       if ( mdb_tool_info ) {
+               slapd_shutdown = 1;
+               ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+
+               /* There might still be some threads starting */
+               while ( mdb_tool_index_tcount ) {
+                       ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main,
+                                       &mdb_tool_index_mutex );
+               }
+
+               mdb_tool_index_tcount = slap_tool_thread_max - 1;
+               ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work );
+
+               /* Make sure all threads are stopped */
+               while ( mdb_tool_index_tcount ) {
+                       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_threads );
+               ch_free( mdb_tool_index_rec );
+               mdb_tool_index_tcount = slap_tool_thread_max - 1;
+       }
+#endif
+
+       if( idcursor ) {
+               mdb_cursor_close( idcursor );
+               idcursor = NULL;
+       }
+       if( cursor ) {
+               mdb_cursor_close( cursor );
+               cursor = NULL;
+       }
+       if( txn ) {
+               if ( mdb_txn_commit( txn ))
+                       return -1;
+               txn = 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 ( !txn ) {
+               rc = mdb_txn_begin( mdb->mi_dbenv, MDB_RDONLY, &txn );
+               if ( rc )
+                       return NOID;
+               rc = mdb_cursor_open( txn, mdb->mi_id2entry, &cursor );
+               if ( rc ) {
+                       mdb_txn_abort( 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 ( 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 ( !txn ) {
+               rc = mdb_txn_begin( mdb->mi_dbenv, (slapMode & SLAP_TOOL_READONLY) != 0 ?
+                       MDB_RDONLY : 0, &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, txn, dn, &id, NULL, NULL );
+       if ( rc == MDB_NOTFOUND )
+               return NOID;
+       
+       return id;
+}
+
+static int
+mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep )
+{
+       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 ( slapMode & SLAP_TOOL_READONLY ) {
+               Operation op = {0};
+               Opheader ohdr = {0};
+
+               op.o_hdr = &ohdr;
+               op.o_bd = be;
+               op.o_tmpmemctx = NULL;
+               op.o_tmpmfuncs = &ch_mfuncs;
+
+               rc = mdb_id2name( &op, txn, &idcursor, id, &dn, &ndn );
+               if ( rc  ) {
+                       rc = LDAP_OTHER;
+                       mdb_entry_return( e );
+                       e = NULL;
+                       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;
+                       }
+               }
+       }
+       /* Get the header */
+       eh.bv.bv_val = data.mv_data;
+       eh.bv.bv_len = data.mv_size;
+
+       rc = entry_header( &eh );
+       if ( rc ) {
+               rc = LDAP_OTHER;
+               goto done;
+       }
+       eh.bv.bv_len = eh.nvals * sizeof( struct berval );
+       eh.bv.bv_val = ch_malloc( eh.bv.bv_len );
+       rc = entry_decode( &eh, &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;
+       }
+       e->e_bv = eh.bv;
+
+done:
+       if ( e != NULL ) {
+               *ep = e;
+       }
+
+       return rc;
+}
+
+Entry*
+mdb_tool_entry_get( BackendDB *be, ID id )
+{
+       Entry *e = 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, &ndn, &id, 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, tid, &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, 0, 0 );
+                       return rc;
+               }
+               rc = mdb_dn2id_add( op, tid, pid, 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, 0, 0 );
+               } else if ( hole ) {
+                       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;
+               }
+       } 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 0
+       if ( slapMode & SLAP_TOOL_QUICK ) {
+               IndexRec *ir;
+               int i, rc;
+               Attribute *a;
+               
+               ir = mdb_tool_index_rec;
+               memset(ir, 0, mdb->bi_nattrs * sizeof( IndexRec ));
+
+               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;
+               }
+               mdb_tool_ix_id = e->e_id;
+               mdb_tool_ix_op = op;
+               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<slap_tool_thread_max; i++ )
+                       mdb_tool_index_threads[i] = LDAP_BUSY;
+               mdb_tool_index_tcount = slap_tool_thread_max - 1;
+               ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work );
+               ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+               rc = mdb_index_recrun( op, mdb, ir, e->e_id, 0 );
+               if ( rc )
+                       return rc;
+               ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+               for ( i=1; i<slap_tool_thread_max; i++ ) {
+                       if ( mdb_tool_index_threads[i] == LDAP_BUSY ) {
+                               ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main, 
+                                       &mdb_tool_index_mutex );
+                               i--;
+                               continue;
+                       }
+                       if ( mdb_tool_index_threads[i] ) {
+                               rc = mdb_tool_index_threads[i];
+                               break;
+                       }
+               }
+               ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+               return rc;
+       } else
+#endif
+       {
+               return mdb_index_entry_add( op, txn, e );
+       }
+}
+
+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, 0 );
+
+       mdb = (struct mdb_info *) be->be_private;
+
+       if ( !txn ) {
+       rc = mdb_txn_begin( mdb->mi_dbenv, 0, &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, 0, 0 );
+               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, txn, e, text, 0 );
+       if( rc != 0 ) {
+               goto done;
+       }
+
+       rc = mdb_tool_index_add( &op, txn, e );
+       if( rc != 0 ) {
+               snprintf( text->bv_val, text->bv_len,
+                               "index_entry_add failed: %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, 0, 0 );
+               goto done;
+       }
+
+
+       /* id2entry index */
+       rc = mdb_id2entry_add( &op, txn, e );
+       if( rc != 0 ) {
+               snprintf( text->bv_val, text->bv_len,
+                               "id2entry_add failed: %s (%d)",
+                               mdb_strerror(rc), rc );
+               Debug( LDAP_DEBUG_ANY,
+                       "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+                       text->bv_val, 0, 0 );
+               goto done;
+       }
+
+done:
+       if( rc == 0 ) {
+               mdb_writes++;
+               if ( mdb_writes >= mdb_writes_per_commit ) {
+                       rc = mdb_txn_commit( txn );
+                       mdb_writes = 0;
+                       txn = NULL;
+                       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_put) ": %s\n",
+                                       text->bv_val, 0, 0 );
+                               e->e_id = NOID;
+                       }
+               }
+
+       } else {
+               mdb_txn_abort( txn );
+               txn = NULL;
+               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, 0, 0 );
+               e->e_id = NOID;
+       }
+
+       return e->e_id;
+}
+
+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, 0, 0 );
+       assert( tool_base == NULL );
+       assert( tool_filter == NULL );
+
+       /* 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, 0, 0 );
+                                       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, 0, 0 );
+               return -1;
+       }
+
+       if ( !txi ) {
+               rc = mdb_txn_begin( mi->mi_dbenv, 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, 0 );
+                       goto done;
+               }
+       }
+       
+       /*
+        * just (re)add them for now
+        * assume that some other routine (not yet implemented)
+        * will zap index databases
+        *
+        */
+
+       Debug( LDAP_DEBUG_TRACE,
+               "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld, \"%s\" )\n",
+               (long) id, e->e_dn, 0 );
+
+       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 ) {
+                       rc = mdb_txn_commit( txi );
+                       if( rc != 0 ) {
+                               Debug( LDAP_DEBUG_ANY,
+                                       "=> " LDAP_XSTRING(mdb_tool_entry_reindex)
+                                       ": txn_commit failed: %s (%d)\n",
+                                       mdb_strerror(rc), rc, 0 );
+                               e->e_id = NOID;
+                       }
+                       txi = NULL;
+               }
+
+       } else {
+               mdb_txn_abort( txi );
+               Debug( LDAP_DEBUG_ANY,
+                       "=> " LDAP_XSTRING(mdb_tool_entry_reindex)
+                       ": txn_aborted! %s (%d)\n",
+                       mdb_strerror(rc), rc, 0 );
+               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;
+       MDB_txn *tid;
+       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, 0 );
+
+       mdb = (struct mdb_info *) be->be_private;
+
+       if( cursor ) {
+               mdb_cursor_close( cursor );
+               cursor = NULL;
+       }
+       rc = mdb_txn_begin( mdb->mi_dbenv, 0, &tid );
+       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, 0, 0 );
+               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, tid, e );
+       if( rc != 0 ) {
+               snprintf( text->bv_val, text->bv_len,
+                               "id2entry_add failed: %s (%d)",
+                               mdb_strerror(rc), rc );
+               Debug( LDAP_DEBUG_ANY,
+                       "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n",
+                       text->bv_val, 0, 0 );
+               goto done;
+       }
+
+done:
+       if( rc == 0 ) {
+               rc = mdb_txn_commit( tid );
+               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_modify) ": "
+                               "%s\n", text->bv_val, 0, 0 );
+                       e->e_id = NOID;
+               }
+
+       } else {
+               mdb_txn_abort( tid );
+               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, 0, 0 );
+               e->e_id = NOID;
+       }
+
+       return e->e_id;
+}
+
+#if 0
+static void *
+mdb_tool_index_task( void *ctx, void *ptr )
+{
+       int base = *(int *)ptr;
+
+       free( ptr );
+       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 );
+                       ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+                       break;
+               }
+               ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+
+               mdb_tool_index_threads[base] = mdb_index_recrun( mdb_tool_ix_op,
+                       mdb_tool_info, mdb_tool_index_rec, mdb_tool_ix_id, base );
+       }
+
+       return NULL;
+}
+#endif