diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:11:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 11:11:40 +0000 |
commit | 7731832751ab9f3c6ddeb66f186d3d7fa1934a6d (patch) | |
tree | e91015872543a59be2aad26c2fea02e41b57005d /servers/slapd/back-bdb | |
parent | Initial commit. (diff) | |
download | openldap-upstream/2.4.57+dfsg.tar.xz openldap-upstream/2.4.57+dfsg.zip |
Adding upstream version 2.4.57+dfsg.upstream/2.4.57+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers/slapd/back-bdb')
31 files changed, 17659 insertions, 0 deletions
diff --git a/servers/slapd/back-bdb/Makefile.in b/servers/slapd/back-bdb/Makefile.in new file mode 100644 index 0000000..a9d5ba6 --- /dev/null +++ b/servers/slapd/back-bdb/Makefile.in @@ -0,0 +1,53 @@ +# Makefile.in for back-bdb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 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 dbcache.c filterindex.c \ + dn2entry.c dn2id.c error.c id2entry.c idl.c \ + nextid.c cache.c trans.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 dbcache.lo filterindex.lo \ + dn2entry.lo dn2id.lo error.lo id2entry.lo idl.lo \ + nextid.lo cache.lo trans.lo monitor.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-bdb" +BUILD_MOD = @BUILD_BDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_BDB@_DEFS) +MOD_LIBS = $(BDB_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_bdb + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-bdb/add.c b/servers/slapd/back-bdb/add.c new file mode 100644 index 0000000..9a232af --- /dev/null +++ b/servers/slapd/back-bdb/add.c @@ -0,0 +1,547 @@ +/* add.c - ldap BerkeleyDB back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_add(Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct berval pdn; + Entry *p = NULL, *oe = op->ora_e; + EntryInfo *ei; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + DB_TXN *ltid = NULL, *lt2; + ID eid = NOID; + struct bdb_op_info opinfo = {{{ 0 }}}; + int subentry; + DB_LOCK lock; + + int num_retries = 0; + 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(bdb_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(bdb_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(bdb_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 ); + + if( 0 ) { +retry: /* transaction retry */ + if( p ) { + /* free parent and reader lock */ + if ( p != (Entry *)&slap_entry_root ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + } + p = NULL; + } + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": txn_begin failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_add) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* + * 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 = bdb_dn2entry( op, ltid, &op->ora_e->e_nname, &ei, + 1, &lock ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + 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; + } + + p = ei->bei_e; + 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 ); + rs->sr_ref = is_entry_referral( p ) + ? get_entry_referrals( op, p ) + : NULL; + if ( p != (Entry *)&slap_entry_root ) + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + if ( p != (Entry *)&slap_entry_root ) + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 ) ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 ) ) { + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 ) ) { + /* 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 = get_entry_referrals( op, p ); + bdb_unlocked_cache_return_entry_r( bdb, p ); + p = NULL; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 and reader lock */ + if ( p != (Entry *)&slap_entry_root ) { + 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 ... */ + } + } + + bdb_unlocked_cache_return_entry_r( bdb, p ); + } + p = NULL; + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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)) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 = bdb_next_id( op->o_bd, &eid ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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; + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": txn_begin(2) failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_add) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + /* dn2id index */ + rs->sr_err = bdb_dn2id_add( op, lt2, ei, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": dn2id_add failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_KEYEXIST: + rs->sr_err = LDAP_ALREADY_EXISTS; + break; + default: + rs->sr_err = LDAP_OTHER; + } + goto return_results; + } + + /* attribute indexes */ + rs->sr_err = bdb_index_entry_add( op, lt2, op->ora_e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": index_entry_add failed\n", + 0, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + default: + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = "index generation failed"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = bdb_id2entry_add( op->o_bd, lt2, op->ora_e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": id2entry_add failed\n", + 0, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + default: + rs->sr_err = LDAP_OTHER; + } + rs->sr_text = "entry store failed"; + goto return_results; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) 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(bdb_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 ( op->o_noop ) { + if (( rs->sr_err=TXN_ABORT( ltid )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + goto return_results; + } + + } else { + struct berval nrdn; + + /* pick the RDN if not suffix; otherwise pick the entire DN */ + if (pdn.bv_len) { + nrdn.bv_val = op->ora_e->e_nname.bv_val; + nrdn.bv_len = pdn.bv_val - op->ora_e->e_nname.bv_val - 1; + } else { + nrdn = op->ora_e->e_nname; + } + + bdb_cache_add( bdb, ei, op->ora_e, &nrdn, ltid, &lock ); + + if(( rs->sr_err=TXN_COMMIT( ltid, 0 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_add) ": %s : %s (%d)\n", + rs->sr_text, db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + if( success == LDAP_SUCCESS ) { + /* We own the entry now, and it can be purged at will + * Check to make sure it's the same entry we entered with. + * Possibly a callback may have mucked with it, although + * in general callbacks should treat the entry as read-only. + */ + bdb_cache_deref( oe->e_private ); + if ( op->ora_e == oe ) + op->ora_e = NULL; + + if ( bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + } + + 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-bdb/attr.c b/servers/slapd/back-bdb/attr.c new file mode 100644 index 0000000..4a95fe7 --- /dev/null +++ b/servers/slapd/back-bdb/attr.c @@ -0,0 +1,441 @@ +/* attr.c - backend routines for dealing with attributes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.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 +bdb_attr_slot( struct bdb_info *bdb, AttributeDescription *ad, int *ins ) +{ + unsigned base = 0, cursor = 0; + unsigned n = bdb->bi_nattrs; + int val = 0; + + while ( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot; + + val = SLAP_PTRCMP( ad, bdb->bi_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 bdb_info *bdb, AttrInfo *a ) +{ + int x; + int i = bdb_attr_slot( bdb, a->ai_desc, &x ); + + /* Is it a dup? */ + if ( i >= 0 ) + return -1; + + bdb->bi_attrs = ch_realloc( bdb->bi_attrs, ( bdb->bi_nattrs+1 ) * + sizeof( AttrInfo * )); + if ( x < bdb->bi_nattrs ) + AC_MEMCPY( &bdb->bi_attrs[x+1], &bdb->bi_attrs[x], + ( bdb->bi_nattrs - x ) * sizeof( AttrInfo *)); + bdb->bi_attrs[x] = a; + bdb->bi_nattrs++; + return 0; +} + +AttrInfo * +bdb_attr_mask( + struct bdb_info *bdb, + AttributeDescription *desc ) +{ + int i = bdb_attr_slot( bdb, desc, NULL ); + return i < 0 ? NULL : bdb->bi_attrs[i]; +} + +int +bdb_attr_index_config( + struct bdb_info *bdb, + 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 = bdb->bi_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 ) { + bdb->bi_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; + + if ( bdb->bi_flags & BDB_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 = bdb_attr_mask( bdb, 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( bdb, a ); + if( rc ) { + if ( bdb->bi_flags & BDB_IS_OPEN ) { + AttrInfo *b = bdb_attr_mask( bdb, 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 & BDB_INDEX_DELETING ) { + /* If we were editing this attr, reset it */ + b->ai_indexmask &= ~BDB_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 +bdb_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 +bdb_attr_index_unparse( struct bdb_info *bdb, BerVarray *bva ) +{ + int i; + + if ( bdb->bi_defaultmask ) { + aidef.ai_indexmask = bdb->bi_defaultmask; + bdb_attr_index_unparser( &aidef, bva ); + } + for ( i=0; i<bdb->bi_nattrs; i++ ) + bdb_attr_index_unparser( bdb->bi_attrs[i], bva ); +} + +void +bdb_attr_info_free( AttrInfo *ai ) +{ +#ifdef LDAP_COMP_MATCH + free( ai->ai_cr ); +#endif + free( ai ); +} + +void +bdb_attr_index_destroy( struct bdb_info *bdb ) +{ + int i; + + for ( i=0; i<bdb->bi_nattrs; i++ ) + bdb_attr_info_free( bdb->bi_attrs[i] ); + + free( bdb->bi_attrs ); +} + +void bdb_attr_index_free( struct bdb_info *bdb, AttributeDescription *ad ) +{ + int i; + + i = bdb_attr_slot( bdb, ad, NULL ); + if ( i >= 0 ) { + bdb_attr_info_free( bdb->bi_attrs[i] ); + bdb->bi_nattrs--; + for (; i<bdb->bi_nattrs; i++) + bdb->bi_attrs[i] = bdb->bi_attrs[i+1]; + } +} + +void bdb_attr_flush( struct bdb_info *bdb ) +{ + int i; + + for ( i=0; i<bdb->bi_nattrs; i++ ) { + if ( bdb->bi_attrs[i]->ai_indexmask & BDB_INDEX_DELETING ) { + int j; + bdb_attr_info_free( bdb->bi_attrs[i] ); + bdb->bi_nattrs--; + for (j=i; j<bdb->bi_nattrs; j++) + bdb->bi_attrs[j] = bdb->bi_attrs[j+1]; + i--; + } + } +} diff --git a/servers/slapd/back-bdb/back-bdb.h b/servers/slapd/back-bdb/back-bdb.h new file mode 100644 index 0000000..ea68ebb --- /dev/null +++ b/servers/slapd/back-bdb/back-bdb.h @@ -0,0 +1,377 @@ +/* back-bdb.h - bdb back-end header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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_BDB_H_ +#define _BACK_BDB_H_ + +#include <portable.h> +#include "slap.h" +#include <db.h> +#include "alock.h" + +LDAP_BEGIN_DECL + +#define DB_VERSION_FULL ((DB_VERSION_MAJOR << 24) | (DB_VERSION_MINOR << 16) | DB_VERSION_PATCH) + +#define DN_BASE_PREFIX SLAP_INDEX_EQUALITY_PREFIX +#define DN_ONE_PREFIX '%' +#define DN_SUBTREE_PREFIX '@' + +#define DBTzero(t) (memset((t), 0, sizeof(DBT))) +#define DBT2bv(t,bv) ((bv)->bv_val = (t)->data, \ + (bv)->bv_len = (t)->size) +#define bv2DBT(bv,t) ((t)->data = (bv)->bv_val, \ + (t)->size = (bv)->bv_len ) + +#define BDB_TXN_RETRIES 16 + +#define BDB_MAX_ADD_LOOP 30 + +#define BDB_SUFFIX ".bdb" +#define BDB_ID2ENTRY 0 +#define BDB_DN2ID 1 +#define BDB_NDB 2 + +/* The bdb on-disk entry format is pretty space-inefficient. Average + * sized user entries are 3-4K each. You need at least two entries to + * fit into a single database page, more is better. 64K is BDB's + * upper bound. Smaller pages are better for concurrency. + */ +#ifndef BDB_ID2ENTRY_PAGESIZE +#define BDB_ID2ENTRY_PAGESIZE 16384 +#endif + +#define DEFAULT_CACHE_SIZE 1000 + +/* 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 + +typedef struct bdb_idl_cache_entry_s { + struct berval kstr; + ID *idl; + DB *db; + int idl_flags; + struct bdb_idl_cache_entry_s* idl_lru_prev; + struct bdb_idl_cache_entry_s* idl_lru_next; +} bdb_idl_cache_entry_t; + +/* BDB backend specific entry info */ +typedef struct bdb_entry_info { + struct bdb_entry_info *bei_parent; + ID bei_id; + + /* we use the bei_id as a lockobj, but we need to make the size != 4 + * to avoid conflicting with BDB's internal locks. So add a byte here + * that is always zero. + */ + short bei_lockpad; + + short bei_state; +#define CACHE_ENTRY_DELETED 1 +#define CACHE_ENTRY_NO_KIDS 2 +#define CACHE_ENTRY_NOT_LINKED 4 +#define CACHE_ENTRY_NO_GRANDKIDS 8 +#define CACHE_ENTRY_LOADING 0x10 +#define CACHE_ENTRY_WALKING 0x20 +#define CACHE_ENTRY_ONELEVEL 0x40 +#define CACHE_ENTRY_REFERENCED 0x80 +#define CACHE_ENTRY_NOT_CACHED 0x100 + int bei_finders; + + /* + * remaining fields require backend cache lock to access + */ + struct berval bei_nrdn; +#ifdef BDB_HIER + struct berval bei_rdn; + int bei_modrdns; /* track renames */ + int bei_ckids; /* number of kids cached */ + int bei_dkids; /* number of kids on-disk, plus 1 */ +#endif + Entry *bei_e; + Avlnode *bei_kids; +#ifdef SLAP_ZONE_ALLOC + struct bdb_info *bei_bdb; + int bei_zseq; +#endif + ldap_pvt_thread_mutex_t bei_kids_mutex; + + struct bdb_entry_info *bei_lrunext; /* for cache lru list */ + struct bdb_entry_info *bei_lruprev; +} EntryInfo; +#undef BEI +#define BEI(e) ((EntryInfo *) ((e)->e_private)) + +/* for the in-core cache of entries */ +typedef struct bdb_cache { + EntryInfo *c_eifree; /* free list */ + Avlnode *c_idtree; + EntryInfo *c_lruhead; /* lru - add accessed entries here */ + EntryInfo *c_lrutail; /* lru - rem lru entries from here */ + EntryInfo c_dntree; + ID c_maxsize; + ID c_cursize; + ID c_minfree; + ID c_eimax; + ID c_eiused; /* EntryInfo's in use */ + ID c_leaves; /* EntryInfo leaf nodes */ + int c_purging; + DB_TXN *c_txn; /* used by lru cleaner */ + ldap_pvt_thread_rdwr_t c_rwlock; + ldap_pvt_thread_mutex_t c_lru_mutex; + ldap_pvt_thread_mutex_t c_count_mutex; + ldap_pvt_thread_mutex_t c_eifree_mutex; +#ifdef SLAP_ZONE_ALLOC + void *c_zctx; +#endif +} Cache; + +#define CACHE_READ_LOCK 0 +#define CACHE_WRITE_LOCK 1 + +#define BDB_INDICES 128 + +struct bdb_db_info { + struct berval bdi_name; + DB *bdi_db; +}; + +struct bdb_db_pgsize { + struct bdb_db_pgsize *bdp_next; + struct berval bdp_name; + int bdp_size; +}; + +#ifdef LDAP_DEVEL +#define BDB_MONITOR_IDX +#endif /* LDAP_DEVEL */ + +typedef struct bdb_monitor_t { + void *bdm_cb; + struct berval bdm_ndn; +} bdb_monitor_t; + +/* From ldap_rq.h */ +struct re_s; + +struct bdb_info { + DB_ENV *bi_dbenv; + + /* DB_ENV parameters */ + /* The DB_ENV can be tuned via DB_CONFIG */ + char *bi_dbenv_home; + u_int32_t bi_dbenv_xflags; /* extra flags */ + int bi_dbenv_mode; + + int bi_ndatabases; + int bi_db_opflags; /* db-specific flags */ + struct bdb_db_info **bi_databases; + ldap_pvt_thread_mutex_t bi_database_mutex; + struct bdb_db_pgsize *bi_pagesizes; + + slap_mask_t bi_defaultmask; + Cache bi_cache; + struct bdb_attrinfo **bi_attrs; + int bi_nattrs; + void *bi_search_stack; + int bi_search_stack_depth; + int bi_linear_index; + + int bi_txn_cp; + u_int32_t bi_txn_cp_min; + u_int32_t bi_txn_cp_kbyte; + struct re_s *bi_txn_cp_task; + struct re_s *bi_index_task; + + u_int32_t bi_lock_detect; + long bi_shm_key; + + ID bi_lastid; + ldap_pvt_thread_mutex_t bi_lastid_mutex; + ID bi_idl_cache_max_size; + ID bi_idl_cache_size; + Avlnode *bi_idl_tree; + bdb_idl_cache_entry_t *bi_idl_lru_head; + bdb_idl_cache_entry_t *bi_idl_lru_tail; + ldap_pvt_thread_rdwr_t bi_idl_tree_rwlock; + ldap_pvt_thread_mutex_t bi_idl_tree_lrulock; + alock_info_t bi_alock_info; + char *bi_db_config_path; + BerVarray bi_db_config; + char *bi_db_crypt_file; + struct berval bi_db_crypt_key; + bdb_monitor_t bi_monitor; + +#ifdef BDB_MONITOR_IDX + ldap_pvt_thread_mutex_t bi_idx_mutex; + Avlnode *bi_idx; +#endif /* BDB_MONITOR_IDX */ + + int bi_flags; +#define BDB_IS_OPEN 0x01 +#define BDB_HAS_CONFIG 0x02 +#define BDB_UPD_CONFIG 0x04 +#define BDB_DEL_INDEX 0x08 +#define BDB_RE_OPEN 0x10 +#define BDB_CHKSUM 0x20 +#ifdef BDB_HIER + int bi_modrdns; /* number of modrdns completed */ + ldap_pvt_thread_mutex_t bi_modrdns_mutex; +#endif +}; + +#define bi_id2entry bi_databases[BDB_ID2ENTRY] +#define bi_dn2id bi_databases[BDB_DN2ID] + + +struct bdb_lock_info { + struct bdb_lock_info *bli_next; + DB_LOCK bli_lock; + ID bli_id; + int bli_flag; +}; +#define BLI_DONTFREE 1 + +struct bdb_op_info { + OpExtra boi_oe; + DB_TXN* boi_txn; + struct bdb_lock_info *boi_locks; /* used when no txn */ + u_int32_t boi_err; + char boi_acl_cache; + char boi_flag; +}; +#define BOI_DONTFREE 1 + +#define DB_OPEN(db, file, name, type, flags, mode) \ + ((db)->open)(db, file, name, type, flags, mode) + +#if DB_VERSION_MAJOR < 4 +#define LOCK_DETECT(env,f,t,a) lock_detect(env, f, t, a) +#define LOCK_GET(env,i,f,o,m,l) lock_get(env, i, f, o, m, l) +#define LOCK_PUT(env,l) lock_put(env, l) +#define TXN_CHECKPOINT(env,k,m,f) txn_checkpoint(env, k, m, f) +#define TXN_BEGIN(env,p,t,f) txn_begin((env), p, t, f) +#define TXN_PREPARE(txn,gid) txn_prepare((txn), (gid)) +#define TXN_COMMIT(txn,f) txn_commit((txn), (f)) +#define TXN_ABORT(txn) txn_abort((txn)) +#define TXN_ID(txn) txn_id(txn) +#define XLOCK_ID(env, locker) lock_id(env, locker) +#define XLOCK_ID_FREE(env, locker) lock_id_free(env, locker) +#else +#define LOCK_DETECT(env,f,t,a) (env)->lock_detect(env, f, t, a) +#define LOCK_GET(env,i,f,o,m,l) (env)->lock_get(env, i, f, o, m, l) +#define LOCK_PUT(env,l) (env)->lock_put(env, l) +#define TXN_CHECKPOINT(env,k,m,f) (env)->txn_checkpoint(env, k, m, f) +#define TXN_BEGIN(env,p,t,f) (env)->txn_begin((env), p, t, f) +#define TXN_PREPARE(txn,g) (txn)->prepare((txn), (g)) +#define TXN_COMMIT(txn,f) (txn)->commit((txn), (f)) +#define TXN_ABORT(txn) (txn)->abort((txn)) +#define TXN_ID(txn) (txn)->id(txn) +#define XLOCK_ID(env, locker) (env)->lock_id(env, locker) +#define XLOCK_ID_FREE(env, locker) (env)->lock_id_free(env, locker) + +/* BDB 4.1.17 adds txn arg to db->open */ +#if DB_VERSION_FULL >= 0x04010011 +#undef DB_OPEN +#define DB_OPEN(db, file, name, type, flags, mode) \ + ((db)->open)(db, NULL, file, name, type, flags, mode) +#endif + +/* #undef BDB_LOG_DEBUG */ + +#ifdef BDB_LOG_DEBUG + +/* env->log_printf appeared in 4.4 */ +#if DB_VERSION_FULL >= 0x04040000 +#define BDB_LOG_PRINTF(env,txn,fmt,...) (env)->log_printf((env),(txn),(fmt),__VA_ARGS__) +#else +extern int __db_logmsg(const DB_ENV *env, DB_TXN *txn, const char *op, u_int32_t flags, + const char *fmt,...); +#define BDB_LOG_PRINTF(env,txn,fmt,...) __db_logmsg((env),(txn),"DIAGNOSTIC",0,(fmt),__VA_ARGS__) +#endif + +/* !BDB_LOG_DEBUG */ +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + (defined(__GNUC__) && __GNUC__ >= 3 && !defined(__STRICT_ANSI__)) +#define BDB_LOG_PRINTF(a,b,c,...) +#else +#define BDB_LOG_PRINTF (void) /* will evaluate and discard the arguments */ + +#endif /* BDB_LOG_DEBUG */ + +#endif + +#ifndef DB_BUFFER_SMALL +#define DB_BUFFER_SMALL ENOMEM +#endif + +#define BDB_CSN_COMMIT 0 +#define BDB_CSN_ABORT 1 +#define BDB_CSN_RETRY 2 + +/* Copy an ID "src" to pointer "dst" in big-endian byte order */ +#define BDB_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 BDB_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 bdb_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 +} AttrInfo; + +/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */ +#define BDB_INDEX_DELETING 0x8000U /* index is being modified */ +#define BDB_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-bdb.h" + +#endif /* _BACK_BDB_H_ */ diff --git a/servers/slapd/back-bdb/bind.c b/servers/slapd/back-bdb/bind.c new file mode 100644 index 0000000..1daf537 --- /dev/null +++ b/servers/slapd/back-bdb/bind.c @@ -0,0 +1,166 @@ +/* bind.c - bdb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_bind( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e; + Attribute *a; + EntryInfo *ei; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + DB_TXN *rtxn; + DB_LOCK lock; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(bdb_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 = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rs->sr_err) { + case 0: + break; + default: + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + return rs->sr_err; + } + +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, + &lock ); + + switch(rs->sr_err) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap_server_busy" ); + return LDAP_BUSY; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + e = ei->bei_e; + if ( rs->sr_err == DB_NOTFOUND ) { + if( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + } + + rs->sr_err = LDAP_INVALID_CREDENTIALS; + send_ldap_result( op, rs ); + + return rs->sr_err; + } + + 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: + /* free entry and reader lock */ + if( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + } + + 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-bdb/cache.c b/servers/slapd/back-bdb/cache.c new file mode 100644 index 0000000..83aaa62 --- /dev/null +++ b/servers/slapd/back-bdb/cache.c @@ -0,0 +1,1692 @@ +/* cache.c - routines to maintain an in-core cache of entries */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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/errno.h> +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" + +#include "back-bdb.h" + +#include "ldap_rq.h" + +#ifdef BDB_HIER +#define bdb_cache_lru_purge hdb_cache_lru_purge +#endif +static void bdb_cache_lru_purge( struct bdb_info *bdb ); + +static int bdb_cache_delete_internal(Cache *cache, EntryInfo *e, int decr); +#ifdef LDAP_DEBUG +#define SLAPD_UNUSED +#ifdef SLAPD_UNUSED +static void bdb_lru_print(Cache *cache); +static void bdb_idtree_print(Cache *cache); +#endif +#endif + +/* For concurrency experiments only! */ +#if 0 +#define ldap_pvt_thread_rdwr_wlock(a) 0 +#define ldap_pvt_thread_rdwr_wunlock(a) 0 +#define ldap_pvt_thread_rdwr_rlock(a) 0 +#define ldap_pvt_thread_rdwr_runlock(a) 0 +#endif + +#if 0 +#define ldap_pvt_thread_mutex_trylock(a) 0 +#endif + +static EntryInfo * +bdb_cache_entryinfo_new( Cache *cache ) +{ + EntryInfo *ei = NULL; + + if ( cache->c_eifree ) { + ldap_pvt_thread_mutex_lock( &cache->c_eifree_mutex ); + if ( cache->c_eifree ) { + ei = cache->c_eifree; + cache->c_eifree = ei->bei_lrunext; + ei->bei_finders = 0; + ei->bei_lrunext = NULL; + } + ldap_pvt_thread_mutex_unlock( &cache->c_eifree_mutex ); + } + if ( !ei ) { + ei = ch_calloc(1, sizeof(EntryInfo)); + ldap_pvt_thread_mutex_init( &ei->bei_kids_mutex ); + } + + ei->bei_state = CACHE_ENTRY_REFERENCED; + + return ei; +} + +static void +bdb_cache_entryinfo_free( Cache *cache, EntryInfo *ei ) +{ + free( ei->bei_nrdn.bv_val ); + BER_BVZERO( &ei->bei_nrdn ); +#ifdef BDB_HIER + free( ei->bei_rdn.bv_val ); + BER_BVZERO( &ei->bei_rdn ); + ei->bei_modrdns = 0; + ei->bei_ckids = 0; + ei->bei_dkids = 0; +#endif + ei->bei_parent = NULL; + ei->bei_kids = NULL; + ei->bei_lruprev = NULL; + +#if 0 + ldap_pvt_thread_mutex_lock( &cache->c_eifree_mutex ); + ei->bei_lrunext = cache->c_eifree; + cache->c_eifree = ei; + ldap_pvt_thread_mutex_unlock( &cache->c_eifree_mutex ); +#else + ldap_pvt_thread_mutex_destroy( &ei->bei_kids_mutex ); + ch_free( ei ); +#endif +} + +#define LRU_DEL( c, e ) do { \ + if ( e == e->bei_lruprev ) { \ + (c)->c_lruhead = (c)->c_lrutail = NULL; \ + } else { \ + if ( e == (c)->c_lruhead ) (c)->c_lruhead = e->bei_lruprev; \ + if ( e == (c)->c_lrutail ) (c)->c_lrutail = e->bei_lruprev; \ + e->bei_lrunext->bei_lruprev = e->bei_lruprev; \ + e->bei_lruprev->bei_lrunext = e->bei_lrunext; \ + } \ + e->bei_lruprev = NULL; \ +} while ( 0 ) + +/* Note - we now use a Second-Chance / Clock algorithm instead of + * Least-Recently-Used. This tremendously improves concurrency + * because we no longer need to manipulate the lists every time an + * entry is touched. We only need to lock the lists when adding + * or deleting an entry. It's now a circular doubly-linked list. + * We always append to the tail, but the head traverses the circle + * during a purge operation. + */ +static void +bdb_cache_lru_link( struct bdb_info *bdb, EntryInfo *ei ) +{ + + /* Already linked, ignore */ + if ( ei->bei_lruprev ) + return; + + /* Insert into circular LRU list */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + ei->bei_lruprev = bdb->bi_cache.c_lrutail; + if ( bdb->bi_cache.c_lrutail ) { + ei->bei_lrunext = bdb->bi_cache.c_lrutail->bei_lrunext; + bdb->bi_cache.c_lrutail->bei_lrunext = ei; + if ( ei->bei_lrunext ) + ei->bei_lrunext->bei_lruprev = ei; + } else { + ei->bei_lrunext = ei->bei_lruprev = ei; + bdb->bi_cache.c_lruhead = ei; + } + bdb->bi_cache.c_lrutail = ei; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); +} + +#ifdef NO_THREADS +#define NO_DB_LOCK +#endif + +/* #define NO_DB_LOCK 1 */ +/* Note: The BerkeleyDB locks are much slower than regular + * mutexes or rdwr locks. But the BDB implementation has the + * advantage of using a fixed size lock table, instead of + * allocating a lock object per entry in the DB. That's a + * key benefit for scaling. It also frees us from worrying + * about undetectable deadlocks between BDB activity and our + * own cache activity. It's still worth exploring faster + * alternatives though. + */ + +/* Atomically release and reacquire a lock */ +int +bdb_cache_entry_db_relock( + struct bdb_info *bdb, + DB_TXN *txn, + EntryInfo *ei, + int rw, + int tryOnly, + DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + DBT lockobj; + DB_LOCKREQ list[2]; + + if ( !lock ) return 0; + + DBTzero( &lockobj ); + lockobj.data = &ei->bei_id; + lockobj.size = sizeof(ei->bei_id) + 1; + + list[0].op = DB_LOCK_PUT; + list[0].lock = *lock; + list[1].op = DB_LOCK_GET; + list[1].lock = *lock; + list[1].mode = rw ? DB_LOCK_WRITE : DB_LOCK_READ; + list[1].obj = &lockobj; + rc = bdb->bi_dbenv->lock_vec(bdb->bi_dbenv, TXN_ID(txn), tryOnly ? DB_LOCK_NOWAIT : 0, + list, 2, NULL ); + + if (rc && !tryOnly) { + Debug( LDAP_DEBUG_TRACE, + "bdb_cache_entry_db_relock: entry %ld, rw %d, rc %d\n", + ei->bei_id, rw, rc ); + } else { + *lock = list[1].lock; + } + return rc; +#endif +} + +static int +bdb_cache_entry_db_lock( struct bdb_info *bdb, DB_TXN *txn, EntryInfo *ei, + int rw, int tryOnly, DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + DBT lockobj; + int db_rw; + + if ( !lock ) return 0; + + if (rw) + db_rw = DB_LOCK_WRITE; + else + db_rw = DB_LOCK_READ; + + DBTzero( &lockobj ); + lockobj.data = &ei->bei_id; + lockobj.size = sizeof(ei->bei_id) + 1; + + rc = LOCK_GET(bdb->bi_dbenv, TXN_ID(txn), tryOnly ? DB_LOCK_NOWAIT : 0, + &lockobj, db_rw, lock); + if (rc && !tryOnly) { + Debug( LDAP_DEBUG_TRACE, + "bdb_cache_entry_db_lock: entry %ld, rw %d, rc %d\n", + ei->bei_id, rw, rc ); + } + return rc; +#endif /* NO_DB_LOCK */ +} + +int +bdb_cache_entry_db_unlock ( struct bdb_info *bdb, DB_LOCK *lock ) +{ +#ifdef NO_DB_LOCK + return 0; +#else + int rc; + + if ( !lock || lock->mode == DB_LOCK_NG ) return 0; + + rc = LOCK_PUT ( bdb->bi_dbenv, lock ); + return rc; +#endif +} + +void +bdb_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, + int rw, DB_LOCK *lock ) +{ + EntryInfo *ei; + int free = 0; + + ei = e->e_private; + if ( ei && ( ei->bei_state & CACHE_ENTRY_NOT_CACHED )) { + bdb_cache_entryinfo_lock( ei ); + if ( ei->bei_state & CACHE_ENTRY_NOT_CACHED ) { + /* Releasing the entry can only be done when + * we know that nobody else is using it, i.e we + * should have an entry_db writelock. But the + * flag is only set by the thread that loads the + * entry, and only if no other threads has found + * it while it was working. All other threads + * clear the flag, which mean that we should be + * the only thread using the entry if the flag + * is set here. + */ + ei->bei_e = NULL; + ei->bei_state ^= CACHE_ENTRY_NOT_CACHED; + free = 1; + } + bdb_cache_entryinfo_unlock( ei ); + } + bdb_cache_entry_db_unlock( bdb, lock ); + if ( free ) { + e->e_private = NULL; + bdb_entry_return( e ); + } +} + +static int +bdb_cache_entryinfo_destroy( EntryInfo *e ) +{ + ldap_pvt_thread_mutex_destroy( &e->bei_kids_mutex ); + free( e->bei_nrdn.bv_val ); +#ifdef BDB_HIER + free( e->bei_rdn.bv_val ); +#endif + free( e ); + return 0; +} + +/* Do a length-ordered sort on normalized RDNs */ +static int +bdb_rdn_cmp( const void *v_e1, const void *v_e2 ) +{ + const EntryInfo *e1 = v_e1, *e2 = v_e2; + int rc = e1->bei_nrdn.bv_len - e2->bei_nrdn.bv_len; + if (rc == 0) { + rc = strncmp( e1->bei_nrdn.bv_val, e2->bei_nrdn.bv_val, + e1->bei_nrdn.bv_len ); + } + return rc; +} + +static int +bdb_id_cmp( const void *v_e1, const void *v_e2 ) +{ + const EntryInfo *e1 = v_e1, *e2 = v_e2; + return e1->bei_id - e2->bei_id; +} + +static int +bdb_id_dup_err( void *v1, void *v2 ) +{ + EntryInfo *e2 = v2; + e2->bei_lrunext = v1; + return -1; +} + +/* Create an entryinfo in the cache. Caller must release the locks later. + */ +static int +bdb_entryinfo_add_internal( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res ) +{ + EntryInfo *ei2 = NULL; + + *res = NULL; + + ei2 = bdb_cache_entryinfo_new( &bdb->bi_cache ); + + bdb_cache_entryinfo_lock( ei->bei_parent ); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + + ei2->bei_id = ei->bei_id; + ei2->bei_parent = ei->bei_parent; +#ifdef BDB_HIER + ei2->bei_rdn = ei->bei_rdn; +#endif +#ifdef SLAP_ZONE_ALLOC + ei2->bei_bdb = bdb; +#endif + + /* Add to cache ID tree */ + if (avl_insert( &bdb->bi_cache.c_idtree, ei2, bdb_id_cmp, + bdb_id_dup_err )) { + EntryInfo *eix = ei2->bei_lrunext; + bdb_cache_entryinfo_free( &bdb->bi_cache, ei2 ); + ei2 = eix; +#ifdef BDB_HIER + /* It got freed above because its value was + * assigned to ei2. + */ + ei->bei_rdn.bv_val = NULL; +#endif + } else { + int rc; + + bdb->bi_cache.c_eiused++; + ber_dupbv( &ei2->bei_nrdn, &ei->bei_nrdn ); + + /* This is a new leaf node. But if parent had no kids, then it was + * a leaf and we would be decrementing that. So, only increment if + * the parent already has kids. + */ + if ( ei->bei_parent->bei_kids || !ei->bei_parent->bei_id ) + bdb->bi_cache.c_leaves++; + rc = avl_insert( &ei->bei_parent->bei_kids, ei2, bdb_rdn_cmp, + avl_dup_error ); +#ifdef BDB_HIER + /* it's possible for hdb_cache_find_parent to beat us to it */ + if ( !rc ) { + ei->bei_parent->bei_ckids++; + } +#endif + } + + *res = ei2; + return 0; +} + +/* Find the EntryInfo for the requested DN. If the DN cannot be found, return + * the info for its closest ancestor. *res should be NULL to process a + * complete DN starting from the tree root. Otherwise *res must be the + * immediate parent of the requested DN, and only the RDN will be searched. + * The EntryInfo is locked upon return and must be unlocked by the caller. + */ +int +bdb_cache_find_ndn( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo **res ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + EntryInfo ei, *eip, *ei2; + int rc = 0; + char *ptr; + + /* this function is always called with normalized DN */ + if ( *res ) { + /* we're doing a onelevel search for an RDN */ + ei.bei_nrdn.bv_val = ndn->bv_val; + ei.bei_nrdn.bv_len = dn_rdnlen( op->o_bd, ndn ); + eip = *res; + } else { + /* we're searching a full DN from the root */ + ptr = ndn->bv_val + ndn->bv_len - op->o_bd->be_nsuffix[0].bv_len; + ei.bei_nrdn.bv_val = ptr; + ei.bei_nrdn.bv_len = op->o_bd->be_nsuffix[0].bv_len; + /* Skip to next rdn if suffix is empty */ + if ( ei.bei_nrdn.bv_len == 0 ) { + for (ptr = ei.bei_nrdn.bv_val - 2; ptr > ndn->bv_val + && !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= ndn->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + ei.bei_nrdn.bv_len = ei.bei_nrdn.bv_val - ptr; + ei.bei_nrdn.bv_val = ptr; + } + } + eip = &bdb->bi_cache.c_dntree; + } + + for ( bdb_cache_entryinfo_lock( eip ); eip; ) { + eip->bei_state |= CACHE_ENTRY_REFERENCED; + ei.bei_parent = eip; + ei2 = (EntryInfo *)avl_find( eip->bei_kids, &ei, bdb_rdn_cmp ); + if ( !ei2 ) { + DBC *cursor; + int len = ei.bei_nrdn.bv_len; + + if ( BER_BVISEMPTY( ndn )) { + *res = eip; + return LDAP_SUCCESS; + } + + ei.bei_nrdn.bv_len = ndn->bv_len - + (ei.bei_nrdn.bv_val - ndn->bv_val); + eip->bei_finders++; + bdb_cache_entryinfo_unlock( eip ); + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Reading %s", + ei.bei_nrdn.bv_val ); + + cursor = NULL; + rc = bdb_dn2id( op, &ei.bei_nrdn, &ei, txn, &cursor ); + if (rc) { + bdb_cache_entryinfo_lock( eip ); + eip->bei_finders--; + if ( cursor ) cursor->c_close( cursor ); + *res = eip; + return rc; + } + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Read got %s(%d)", + ei.bei_nrdn.bv_val, ei.bei_id ); + + /* DN exists but needs to be added to cache */ + ei.bei_nrdn.bv_len = len; + rc = bdb_entryinfo_add_internal( bdb, &ei, &ei2 ); + /* add_internal left eip and c_rwlock locked */ + eip->bei_finders--; + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + if ( cursor ) cursor->c_close( cursor ); + if ( rc ) { + *res = eip; + return rc; + } + } + bdb_cache_entryinfo_lock( ei2 ); + if ( ei2->bei_state & CACHE_ENTRY_DELETED ) { + /* In the midst of deleting? Give it a chance to + * complete. + */ + bdb_cache_entryinfo_unlock( ei2 ); + bdb_cache_entryinfo_unlock( eip ); + ldap_pvt_thread_yield(); + bdb_cache_entryinfo_lock( eip ); + *res = eip; + return DB_NOTFOUND; + } + bdb_cache_entryinfo_unlock( eip ); + + eip = ei2; + + /* Advance to next lower RDN */ + for (ptr = ei.bei_nrdn.bv_val - 2; ptr > ndn->bv_val + && !DN_SEPARATOR(*ptr); ptr--) /* empty */; + if ( ptr >= ndn->bv_val ) { + if (DN_SEPARATOR(*ptr)) ptr++; + ei.bei_nrdn.bv_len = ei.bei_nrdn.bv_val - ptr - 1; + ei.bei_nrdn.bv_val = ptr; + } + if ( ptr < ndn->bv_val ) { + *res = eip; + break; + } + } + + return rc; +} + +#ifdef BDB_HIER +/* Walk up the tree from a child node, looking for an ID that's already + * been linked into the cache. + */ +int +hdb_cache_find_parent( + Operation *op, + DB_TXN *txn, + ID id, + EntryInfo **res ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + EntryInfo ei, eip, *ei2 = NULL, *ein = NULL, *eir = NULL; + int rc, add; + + ei.bei_id = id; + ei.bei_kids = NULL; + ei.bei_ckids = 0; + + for (;;) { + rc = hdb_dn2id_parent( op, txn, &ei, &eip.bei_id ); + if ( rc ) break; + + /* Save the previous node, if any */ + ei2 = ein; + + /* Create a new node for the current ID */ + ein = bdb_cache_entryinfo_new( &bdb->bi_cache ); + ein->bei_id = ei.bei_id; + ein->bei_kids = ei.bei_kids; + ein->bei_nrdn = ei.bei_nrdn; + ein->bei_rdn = ei.bei_rdn; + ein->bei_ckids = ei.bei_ckids; +#ifdef SLAP_ZONE_ALLOC + ein->bei_bdb = bdb; +#endif + ei.bei_ckids = 0; + add = 1; + + /* This node is not fully connected yet */ + ein->bei_state |= CACHE_ENTRY_NOT_LINKED; + + /* If this is the first time, save this node + * to be returned later. + */ + if ( eir == NULL ) { + eir = ein; + ein->bei_finders++; + } + +again: + /* Insert this node into the ID tree */ + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + if ( avl_insert( &bdb->bi_cache.c_idtree, (caddr_t)ein, + bdb_id_cmp, bdb_id_dup_err ) ) { + EntryInfo *eix = ein->bei_lrunext; + + if ( bdb_cache_entryinfo_trylock( eix )) { + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + goto again; + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + + /* Someone else created this node just before us. + * Free our new copy and use the existing one. + */ + bdb_cache_entryinfo_free( &bdb->bi_cache, ein ); + + /* if it was the node we were looking for, just return it */ + if ( eir == ein ) { + *res = eix; + rc = 0; + break; + } + + ein = ei2; + ei2 = eix; + add = 0; + + /* otherwise, link up what we have and return */ + goto gotparent; + } + + /* If there was a previous node, link it to this one */ + if ( ei2 ) ei2->bei_parent = ein; + + /* Look for this node's parent */ +par2: + if ( eip.bei_id ) { + ei2 = (EntryInfo *) avl_find( bdb->bi_cache.c_idtree, + (caddr_t) &eip, bdb_id_cmp ); + } else { + ei2 = &bdb->bi_cache.c_dntree; + } + if ( ei2 && bdb_cache_entryinfo_trylock( ei2 )) { + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_cache.c_rwlock ); + goto par2; + } + if ( add ) + bdb->bi_cache.c_eiused++; + if ( ei2 && ( ei2->bei_kids || !ei2->bei_id )) + bdb->bi_cache.c_leaves++; + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + +gotparent: + /* Got the parent, link in and we're done. */ + if ( ei2 ) { + bdb_cache_entryinfo_lock( eir ); + ein->bei_parent = ei2; + + if ( avl_insert( &ei2->bei_kids, (caddr_t)ein, bdb_rdn_cmp, + avl_dup_error) == 0 ) + ei2->bei_ckids++; + + /* Reset all the state info */ + for (ein = eir; ein != ei2; ein=ein->bei_parent) + ein->bei_state &= ~CACHE_ENTRY_NOT_LINKED; + + bdb_cache_entryinfo_unlock( ei2 ); + eir->bei_finders--; + + *res = eir; + break; + } + ei.bei_kids = NULL; + ei.bei_id = eip.bei_id; + ei.bei_ckids = 1; + avl_insert( &ei.bei_kids, (caddr_t)ein, bdb_rdn_cmp, + avl_dup_error ); + } + return rc; +} + +/* Used by hdb_dn2idl when loading the EntryInfo for all the children + * of a given node + */ +int hdb_cache_load( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res ) +{ + EntryInfo *ei2; + int rc; + + /* See if we already have this one */ + bdb_cache_entryinfo_lock( ei->bei_parent ); + ei2 = (EntryInfo *)avl_find( ei->bei_parent->bei_kids, ei, bdb_rdn_cmp ); + bdb_cache_entryinfo_unlock( ei->bei_parent ); + + if ( !ei2 ) { + /* Not found, add it */ + struct berval bv; + + /* bei_rdn was not malloc'd before, do it now */ + ber_dupbv( &bv, &ei->bei_rdn ); + ei->bei_rdn = bv; + + rc = bdb_entryinfo_add_internal( bdb, ei, res ); + bdb_cache_entryinfo_unlock( ei->bei_parent ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + } else { + /* Found, return it */ + *res = ei2; + return 0; + } + return rc; +} +#endif + +/* This is best-effort only. If all entries in the cache are + * busy, they will all be kept. This is unlikely to happen + * unless the cache is very much smaller than the working set. + */ +static void +bdb_cache_lru_purge( struct bdb_info *bdb ) +{ + DB_LOCK lock, *lockp; + EntryInfo *elru, *elnext = NULL; + int islocked; + ID eicount, ecount; + ID count, efree, eifree = 0; +#ifdef LDAP_DEBUG + int iter; +#endif + + /* Wait for the mutex; we're the only one trying to purge. */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize ) { + efree = bdb->bi_cache.c_cursize - bdb->bi_cache.c_maxsize; + efree += bdb->bi_cache.c_minfree; + } else { + efree = 0; + } + + /* maximum number of EntryInfo leaves to cache. In slapcat + * we always free all leaf nodes. + */ + + if ( slapMode & SLAP_TOOL_READONLY ) { + eifree = bdb->bi_cache.c_leaves; + } else if ( bdb->bi_cache.c_eimax && + bdb->bi_cache.c_leaves > bdb->bi_cache.c_eimax ) { + eifree = bdb->bi_cache.c_minfree * 10; + if ( eifree >= bdb->bi_cache.c_leaves ) + eifree /= 2; + } + + if ( !efree && !eifree ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + bdb->bi_cache.c_purging = 0; + return; + } + + if ( bdb->bi_cache.c_txn ) { + lockp = &lock; + } else { + lockp = NULL; + } + + count = 0; + eicount = 0; + ecount = 0; +#ifdef LDAP_DEBUG + iter = 0; +#endif + + /* Look for an unused entry to remove */ + for ( elru = bdb->bi_cache.c_lruhead; elru; elru = elnext ) { + elnext = elru->bei_lrunext; + + if ( bdb_cache_entryinfo_trylock( elru )) + goto bottom; + + /* This flag implements the clock replacement behavior */ + if ( elru->bei_state & ( CACHE_ENTRY_REFERENCED )) { + elru->bei_state &= ~CACHE_ENTRY_REFERENCED; + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + /* If this node is in the process of linking into the cache, + * or this node is being deleted, skip it. + */ + if (( elru->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_DELETED | CACHE_ENTRY_LOADING | + CACHE_ENTRY_ONELEVEL )) || + elru->bei_finders > 0 ) { + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + if ( bdb_cache_entryinfo_trylock( elru->bei_parent )) { + bdb_cache_entryinfo_unlock( elru ); + goto bottom; + } + + /* entryinfo is locked */ + islocked = 1; + + /* If we can successfully writelock it, then + * the object is idle. + */ + if ( bdb_cache_entry_db_lock( bdb, + bdb->bi_cache.c_txn, elru, 1, 1, lockp ) == 0 ) { + + /* Free entry for this node if it's present */ + if ( elru->bei_e ) { + ecount++; + + /* the cache may have gone over the limit while we + * weren't looking, so double check. + */ + if ( !efree && ecount > bdb->bi_cache.c_maxsize ) + efree = bdb->bi_cache.c_minfree; + + if ( count < efree ) { + elru->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, elru->bei_e, elru->bei_zseq ); +#else + bdb_entry_return( elru->bei_e ); +#endif + elru->bei_e = NULL; + count++; + } else { + /* Keep this node cached, skip to next */ + bdb_cache_entry_db_unlock( bdb, lockp ); + goto next; + } + } + bdb_cache_entry_db_unlock( bdb, lockp ); + + /* + * If it is a leaf node, and we're over the limit, free it. + */ + if ( elru->bei_kids ) { + /* Drop from list, we ignore it... */ + LRU_DEL( &bdb->bi_cache, elru ); + } else if ( eicount < eifree ) { + /* Too many leaf nodes, free this one */ + bdb_cache_delete_internal( &bdb->bi_cache, elru, 0 ); + bdb_cache_delete_cleanup( &bdb->bi_cache, elru ); + islocked = 0; + eicount++; + } /* Leave on list until we need to free it */ + } + +next: + if ( islocked ) { + bdb_cache_entryinfo_unlock( elru ); + bdb_cache_entryinfo_unlock( elru->bei_parent ); + } + + if ( count >= efree && eicount >= eifree ) + break; +bottom: + if ( elnext == bdb->bi_cache.c_lruhead ) + break; +#ifdef LDAP_DEBUG + iter++; +#endif + } + + if ( count || ecount > bdb->bi_cache.c_cursize ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + /* HACK: we seem to be losing track, fix up now */ + if ( ecount > bdb->bi_cache.c_cursize ) + bdb->bi_cache.c_cursize = ecount; + bdb->bi_cache.c_cursize -= count; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + } + bdb->bi_cache.c_lruhead = elnext; + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + bdb->bi_cache.c_purging = 0; +} + +/* + * cache_find_id - find an entry in the cache, given id. + * The entry is locked for Read upon return. Call with flag ID_LOCKED if + * the supplied *eip was already locked. + */ + +int +bdb_cache_find_id( + Operation *op, + DB_TXN *tid, + ID id, + EntryInfo **eip, + int flag, + DB_LOCK *lock ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *ep = NULL; + int rc = 0, load = 0; + EntryInfo ei = { 0 }; + + ei.bei_id = id; + +#ifdef SLAP_ZONE_ALLOC + slap_zh_rlock(bdb->bi_cache.c_zctx); +#endif + /* If we weren't given any info, see if we have it already cached */ + if ( !*eip ) { +again: ldap_pvt_thread_rdwr_rlock( &bdb->bi_cache.c_rwlock ); + *eip = (EntryInfo *) avl_find( bdb->bi_cache.c_idtree, + (caddr_t) &ei, bdb_id_cmp ); + if ( *eip ) { + /* If the lock attempt fails, the info is in use */ + if ( bdb_cache_entryinfo_trylock( *eip )) { + int del = (*eip)->bei_state & CACHE_ENTRY_DELETED; + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + /* If this node is being deleted, treat + * as if the delete has already finished + */ + if ( del ) { + return DB_NOTFOUND; + } + /* otherwise, wait for the info to free up */ + ldap_pvt_thread_yield(); + goto again; + } + /* If this info isn't hooked up to its parent yet, + * unlock and wait for it to be fully initialized + */ + if ( (*eip)->bei_state & CACHE_ENTRY_NOT_LINKED ) { + bdb_cache_entryinfo_unlock( *eip ); + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_yield(); + goto again; + } + flag |= ID_LOCKED; + } + ldap_pvt_thread_rdwr_runlock( &bdb->bi_cache.c_rwlock ); + } + + /* See if the ID exists in the database; add it to the cache if so */ + if ( !*eip ) { +#ifndef BDB_HIER + rc = bdb_id2entry( op->o_bd, tid, id, &ep ); + if ( rc == 0 ) { + rc = bdb_cache_find_ndn( op, tid, + &ep->e_nname, eip ); + if ( *eip ) flag |= ID_LOCKED; + if ( rc ) { + ep->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, ep, (*eip)->bei_zseq ); +#else + bdb_entry_return( ep ); +#endif + ep = NULL; + } + } +#else + rc = hdb_cache_find_parent(op, tid, id, eip ); + if ( rc == 0 ) flag |= ID_LOCKED; +#endif + } + + /* Ok, we found the info, do we have the entry? */ + if ( rc == 0 ) { + if ( !( flag & ID_LOCKED )) { + bdb_cache_entryinfo_lock( *eip ); + flag |= ID_LOCKED; + } + + if ( (*eip)->bei_state & CACHE_ENTRY_DELETED ) { + rc = DB_NOTFOUND; + } else { + (*eip)->bei_finders++; + (*eip)->bei_state |= CACHE_ENTRY_REFERENCED; + if ( flag & ID_NOENTRY ) { + bdb_cache_entryinfo_unlock( *eip ); + return 0; + } + /* Make sure only one thread tries to load the entry */ +load1: +#ifdef SLAP_ZONE_ALLOC + if ((*eip)->bei_e && !slap_zn_validate( + bdb->bi_cache.c_zctx, (*eip)->bei_e, (*eip)->bei_zseq)) { + (*eip)->bei_e = NULL; + (*eip)->bei_zseq = 0; + } +#endif + if ( !(*eip)->bei_e && !((*eip)->bei_state & CACHE_ENTRY_LOADING)) { + load = 1; + (*eip)->bei_state |= CACHE_ENTRY_LOADING; + flag |= ID_CHKPURGE; + } + + if ( !load ) { + /* Clear the uncached state if we are not + * loading it, i.e it is already cached or + * another thread is currently loading it. + */ + if ( (*eip)->bei_state & CACHE_ENTRY_NOT_CACHED ) { + (*eip)->bei_state ^= CACHE_ENTRY_NOT_CACHED; + flag |= ID_CHKPURGE; + } + } + + if ( flag & ID_LOCKED ) { + bdb_cache_entryinfo_unlock( *eip ); + flag ^= ID_LOCKED; + } + rc = bdb_cache_entry_db_lock( bdb, tid, *eip, load, 0, lock ); + if ( (*eip)->bei_state & CACHE_ENTRY_DELETED ) { + rc = DB_NOTFOUND; + bdb_cache_entry_db_unlock( bdb, lock ); + bdb_cache_entryinfo_lock( *eip ); + (*eip)->bei_finders--; + bdb_cache_entryinfo_unlock( *eip ); + } else if ( rc == 0 ) { + if ( load ) { + if ( !ep) { + rc = bdb_id2entry( op->o_bd, tid, id, &ep ); + } + if ( rc == 0 ) { + ep->e_private = *eip; +#ifdef BDB_HIER + while ( (*eip)->bei_state & CACHE_ENTRY_NOT_LINKED ) + ldap_pvt_thread_yield(); + bdb_fix_dn( ep, 0 ); +#endif + bdb_cache_entryinfo_lock( *eip ); + + (*eip)->bei_e = ep; +#ifdef SLAP_ZONE_ALLOC + (*eip)->bei_zseq = *((ber_len_t *)ep - 2); +#endif + ep = NULL; + if ( flag & ID_NOCACHE ) { + /* Set the cached state only if no other thread + * found the info while we were loading the entry. + */ + if ( (*eip)->bei_finders == 1 ) { + (*eip)->bei_state |= CACHE_ENTRY_NOT_CACHED; + flag ^= ID_CHKPURGE; + } + } + bdb_cache_entryinfo_unlock( *eip ); + bdb_cache_lru_link( bdb, *eip ); + } + if ( rc == 0 ) { + /* If we succeeded, downgrade back to a readlock. */ + rc = bdb_cache_entry_db_relock( bdb, tid, + *eip, 0, 0, lock ); + } else { + /* Otherwise, release the lock. */ + bdb_cache_entry_db_unlock( bdb, lock ); + } + } else if ( !(*eip)->bei_e ) { + /* Some other thread is trying to load the entry, + * wait for it to finish. + */ + bdb_cache_entry_db_unlock( bdb, lock ); + bdb_cache_entryinfo_lock( *eip ); + flag |= ID_LOCKED; + goto load1; +#ifdef BDB_HIER + } else { + /* Check for subtree renames + */ + rc = bdb_fix_dn( (*eip)->bei_e, 1 ); + if ( rc ) { + bdb_cache_entry_db_relock( bdb, + tid, *eip, 1, 0, lock ); + /* check again in case other modifier did it already */ + if ( bdb_fix_dn( (*eip)->bei_e, 1 ) ) + rc = bdb_fix_dn( (*eip)->bei_e, 2 ); + bdb_cache_entry_db_relock( bdb, + tid, *eip, 0, 0, lock ); + } +#endif + } + bdb_cache_entryinfo_lock( *eip ); + (*eip)->bei_finders--; + if ( load ) + (*eip)->bei_state ^= CACHE_ENTRY_LOADING; + bdb_cache_entryinfo_unlock( *eip ); + } + } + } + if ( flag & ID_LOCKED ) { + bdb_cache_entryinfo_unlock( *eip ); + } + if ( ep ) { + ep->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, ep, (*eip)->bei_zseq ); +#else + bdb_entry_return( ep ); +#endif + } + if ( rc == 0 ) { + int purge = 0; + + if (( flag & ID_CHKPURGE ) || bdb->bi_cache.c_eimax ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + if ( flag & ID_CHKPURGE ) { + bdb->bi_cache.c_cursize++; + if ( !bdb->bi_cache.c_purging && bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + } else if ( !bdb->bi_cache.c_purging && bdb->bi_cache.c_eimax && bdb->bi_cache.c_leaves > bdb->bi_cache.c_eimax ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + } + if ( purge ) + bdb_cache_lru_purge( bdb ); + } + +#ifdef SLAP_ZONE_ALLOC + if (rc == 0 && (*eip)->bei_e) { + slap_zn_rlock(bdb->bi_cache.c_zctx, (*eip)->bei_e); + } + slap_zh_runlock(bdb->bi_cache.c_zctx); +#endif + return rc; +} + +int +bdb_cache_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + int rc; + + if ( BEI(e)->bei_kids ) { + return 0; + } + if ( BEI(e)->bei_state & CACHE_ENTRY_NO_KIDS ) { + return DB_NOTFOUND; + } + rc = bdb_dn2id_children( op, txn, e ); + if ( rc == DB_NOTFOUND ) { + BEI(e)->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + } + return rc; +} + +/* Update the cache after a successful database Add. */ +int +bdb_cache_add( + struct bdb_info *bdb, + EntryInfo *eip, + Entry *e, + struct berval *nrdn, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *new, ei; + int rc, purge = 0; +#ifdef BDB_HIER + struct berval rdn = e->e_name; +#endif + + ei.bei_id = e->e_id; + ei.bei_parent = eip; + ei.bei_nrdn = *nrdn; + ei.bei_lockpad = 0; + +#if 0 + /* Lock this entry so that bdb_add can run to completion. + * It can only fail if BDB has run out of lock resources. + */ + rc = bdb_cache_entry_db_lock( bdb, txn, &ei, 0, 0, lock ); + if ( rc ) { + bdb_cache_entryinfo_unlock( eip ); + return rc; + } +#endif + +#ifdef BDB_HIER + if ( nrdn->bv_len != e->e_nname.bv_len ) { + char *ptr = ber_bvchr( &rdn, ',' ); + assert( ptr != NULL ); + rdn.bv_len = ptr - rdn.bv_val; + } + ber_dupbv( &ei.bei_rdn, &rdn ); + if ( eip->bei_dkids ) eip->bei_dkids++; +#endif + + if (eip->bei_parent) { + bdb_cache_entryinfo_lock( eip->bei_parent ); + eip->bei_parent->bei_state &= ~CACHE_ENTRY_NO_GRANDKIDS; + bdb_cache_entryinfo_unlock( eip->bei_parent ); + } + + rc = bdb_entryinfo_add_internal( bdb, &ei, &new ); + /* bdb_csn_commit can cause this when adding the database root entry */ + if ( new->bei_e ) { + new->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( bdb, new->bei_e, new->bei_zseq ); +#else + bdb_entry_return( new->bei_e ); +#endif + } + new->bei_e = e; + e->e_private = new; + new->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + eip->bei_state &= ~CACHE_ENTRY_NO_KIDS; + bdb_cache_entryinfo_unlock( eip ); + + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex ); + ++bdb->bi_cache.c_cursize; + if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize && + !bdb->bi_cache.c_purging ) { + purge = 1; + bdb->bi_cache.c_purging = 1; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex ); + + new->bei_finders = 1; + bdb_cache_lru_link( bdb, new ); + + if ( purge ) + bdb_cache_lru_purge( bdb ); + + return rc; +} + +void bdb_cache_deref( + EntryInfo *ei + ) +{ + bdb_cache_entryinfo_lock( ei ); + ei->bei_finders--; + bdb_cache_entryinfo_unlock( ei ); +} + +int +bdb_cache_modify( + struct bdb_info *bdb, + Entry *e, + Attribute *newAttrs, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e); + int rc; + /* Get write lock on data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + + /* If we've done repeated mods on a cached entry, then e_attrs + * is no longer contiguous with the entry, and must be freed. + */ + if ( ! rc ) { + if ( (void *)e->e_attrs != (void *)(e+1) ) { + attrs_free( e->e_attrs ); + } + e->e_attrs = newAttrs; + } + return rc; +} + +/* + * Change the rdn in the entryinfo. Also move to a new parent if needed. + */ +int +bdb_cache_modrdn( + struct bdb_info *bdb, + Entry *e, + struct berval *nrdn, + Entry *new, + EntryInfo *ein, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e), *pei; + int rc; +#ifdef BDB_HIER + struct berval rdn; +#endif + + /* Get write lock on data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + if ( rc ) return rc; + + /* If we've done repeated mods on a cached entry, then e_attrs + * is no longer contiguous with the entry, and must be freed. + */ + if ( (void *)e->e_attrs != (void *)(e+1) ) { + attrs_free( e->e_attrs ); + } + e->e_attrs = new->e_attrs; + if( e->e_nname.bv_val < e->e_bv.bv_val || + e->e_nname.bv_val > e->e_bv.bv_val + e->e_bv.bv_len ) + { + ch_free(e->e_name.bv_val); + ch_free(e->e_nname.bv_val); + } + e->e_name = new->e_name; + e->e_nname = new->e_nname; + + /* Lock the parent's kids AVL tree */ + pei = ei->bei_parent; + bdb_cache_entryinfo_lock( pei ); + avl_delete( &pei->bei_kids, (caddr_t) ei, bdb_rdn_cmp ); + free( ei->bei_nrdn.bv_val ); + ber_dupbv( &ei->bei_nrdn, nrdn ); + +#ifdef BDB_HIER + free( ei->bei_rdn.bv_val ); + + rdn = e->e_name; + if ( nrdn->bv_len != e->e_nname.bv_len ) { + char *ptr = ber_bvchr(&rdn, ','); + assert( ptr != NULL ); + rdn.bv_len = ptr - rdn.bv_val; + } + ber_dupbv( &ei->bei_rdn, &rdn ); + + /* If new parent, decrement kid counts */ + if ( ein ) { + pei->bei_ckids--; + if ( pei->bei_dkids ) { + pei->bei_dkids--; + if ( pei->bei_dkids < 2 ) + pei->bei_state |= CACHE_ENTRY_NO_KIDS | CACHE_ENTRY_NO_GRANDKIDS; + } + } +#endif + + if (!ein) { + ein = ei->bei_parent; + } else { + ei->bei_parent = ein; + bdb_cache_entryinfo_unlock( pei ); + bdb_cache_entryinfo_lock( ein ); + + /* new parent now has kids */ + if ( ein->bei_state & CACHE_ENTRY_NO_KIDS ) + ein->bei_state ^= CACHE_ENTRY_NO_KIDS; + /* grandparent has grandkids */ + if ( ein->bei_parent ) + ein->bei_parent->bei_state &= ~CACHE_ENTRY_NO_GRANDKIDS; +#ifdef BDB_HIER + /* parent might now have grandkids */ + if ( ein->bei_state & CACHE_ENTRY_NO_GRANDKIDS && + !(ei->bei_state & CACHE_ENTRY_NO_KIDS)) + ein->bei_state ^= CACHE_ENTRY_NO_GRANDKIDS; + + ein->bei_ckids++; + if ( ein->bei_dkids ) ein->bei_dkids++; +#endif + } + +#ifdef BDB_HIER + /* Record the generation number of this change */ + ldap_pvt_thread_mutex_lock( &bdb->bi_modrdns_mutex ); + bdb->bi_modrdns++; + ei->bei_modrdns = bdb->bi_modrdns; + ldap_pvt_thread_mutex_unlock( &bdb->bi_modrdns_mutex ); +#endif + + avl_insert( &ein->bei_kids, ei, bdb_rdn_cmp, avl_dup_error ); + bdb_cache_entryinfo_unlock( ein ); + return rc; +} +/* + * cache_delete - delete the entry e from the cache. + * + * returns: 0 e was deleted ok + * 1 e was not in the cache + * -1 something bad happened + */ +int +bdb_cache_delete( + struct bdb_info *bdb, + Entry *e, + DB_TXN *txn, + DB_LOCK *lock ) +{ + EntryInfo *ei = BEI(e); + int rc, busy = 0, counter = 0; + + assert( e->e_private != NULL ); + + /* Lock the entry's info */ + bdb_cache_entryinfo_lock( ei ); + + /* Set this early, warn off any queriers */ + ei->bei_state |= CACHE_ENTRY_DELETED; + + if (( ei->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_LOADING | CACHE_ENTRY_ONELEVEL )) || + ei->bei_finders > 0 ) + busy = 1; + + bdb_cache_entryinfo_unlock( ei ); + + while ( busy && counter < 1000) { + ldap_pvt_thread_yield(); + busy = 0; + bdb_cache_entryinfo_lock( ei ); + if (( ei->bei_state & ( CACHE_ENTRY_NOT_LINKED | + CACHE_ENTRY_LOADING | CACHE_ENTRY_ONELEVEL )) || + ei->bei_finders > 0 ) + busy = 1; + bdb_cache_entryinfo_unlock( ei ); + counter ++; + } + if( busy ) { + bdb_cache_entryinfo_lock( ei ); + ei->bei_state ^= CACHE_ENTRY_DELETED; + bdb_cache_entryinfo_unlock( ei ); + return DB_LOCK_DEADLOCK; + } + + /* Get write lock on the data */ + rc = bdb_cache_entry_db_relock( bdb, txn, ei, 1, 0, lock ); + if ( rc ) { + bdb_cache_entryinfo_lock( ei ); + /* couldn't lock, undo and give up */ + ei->bei_state ^= CACHE_ENTRY_DELETED; + bdb_cache_entryinfo_unlock( ei ); + return rc; + } + + Debug( LDAP_DEBUG_TRACE, "====> bdb_cache_delete( %ld )\n", + e->e_id, 0, 0 ); + + /* set lru mutex */ + ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex ); + + bdb_cache_entryinfo_lock( ei->bei_parent ); + bdb_cache_entryinfo_lock( ei ); + rc = bdb_cache_delete_internal( &bdb->bi_cache, e->e_private, 1 ); + bdb_cache_entryinfo_unlock( ei ); + + /* free lru mutex */ + ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex ); + + return( rc ); +} + +void +bdb_cache_delete_cleanup( + Cache *cache, + EntryInfo *ei ) +{ + /* Enter with ei locked */ + + /* already freed? */ + if ( !ei->bei_parent ) return; + + if ( ei->bei_e ) { + ei->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( ei->bei_bdb, ei->bei_e, ei->bei_zseq ); +#else + bdb_entry_return( ei->bei_e ); +#endif + ei->bei_e = NULL; + } + + bdb_cache_entryinfo_unlock( ei ); + bdb_cache_entryinfo_free( cache, ei ); +} + +static int +bdb_cache_delete_internal( + Cache *cache, + EntryInfo *e, + int decr ) +{ + int rc = 0; /* return code */ + int decr_leaf = 0; + + /* already freed? */ + if ( !e->bei_parent ) { + assert(0); + return -1; + } + +#ifdef BDB_HIER + e->bei_parent->bei_ckids--; + if ( decr && e->bei_parent->bei_dkids ) e->bei_parent->bei_dkids--; +#endif + /* dn tree */ + if ( avl_delete( &e->bei_parent->bei_kids, (caddr_t) e, bdb_rdn_cmp ) + == NULL ) + { + rc = -1; + assert(0); + } + if ( e->bei_parent->bei_kids ) + decr_leaf = 1; + + ldap_pvt_thread_rdwr_wlock( &cache->c_rwlock ); + /* id tree */ + if ( avl_delete( &cache->c_idtree, (caddr_t) e, bdb_id_cmp )) { + cache->c_eiused--; + if ( decr_leaf ) + cache->c_leaves--; + } else { + rc = -1; + assert(0); + } + ldap_pvt_thread_rdwr_wunlock( &cache->c_rwlock ); + bdb_cache_entryinfo_unlock( e->bei_parent ); + + if ( rc == 0 ){ + /* lru */ + LRU_DEL( cache, e ); + + if ( e->bei_e ) { + ldap_pvt_thread_mutex_lock( &cache->c_count_mutex ); + cache->c_cursize--; + ldap_pvt_thread_mutex_unlock( &cache->c_count_mutex ); + } + } + + return( rc ); +} + +static void +bdb_entryinfo_release( void *data ) +{ + EntryInfo *ei = (EntryInfo *)data; + if ( ei->bei_kids ) { + avl_free( ei->bei_kids, NULL ); + } + if ( ei->bei_e ) { + ei->bei_e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return( ei->bei_bdb, ei->bei_e, ei->bei_zseq ); +#else + bdb_entry_return( ei->bei_e ); +#endif + } + bdb_cache_entryinfo_destroy( ei ); +} + +void +bdb_cache_release_all( Cache *cache ) +{ + /* set cache write lock */ + ldap_pvt_thread_rdwr_wlock( &cache->c_rwlock ); + /* set lru mutex */ + ldap_pvt_thread_mutex_lock( &cache->c_lru_mutex ); + + Debug( LDAP_DEBUG_TRACE, "====> bdb_cache_release_all\n", 0, 0, 0 ); + + avl_free( cache->c_dntree.bei_kids, NULL ); + avl_free( cache->c_idtree, bdb_entryinfo_release ); + for (;cache->c_eifree;cache->c_eifree = cache->c_lruhead) { + cache->c_lruhead = cache->c_eifree->bei_lrunext; + bdb_cache_entryinfo_destroy(cache->c_eifree); + } + cache->c_cursize = 0; + cache->c_eiused = 0; + cache->c_leaves = 0; + cache->c_idtree = NULL; + cache->c_lruhead = NULL; + cache->c_lrutail = NULL; + cache->c_dntree.bei_kids = NULL; + + /* free lru mutex */ + ldap_pvt_thread_mutex_unlock( &cache->c_lru_mutex ); + /* free cache write lock */ + ldap_pvt_thread_rdwr_wunlock( &cache->c_rwlock ); +} + +#ifdef LDAP_DEBUG +static void +bdb_lru_count( Cache *cache ) +{ + EntryInfo *e; + int ei = 0, ent = 0, nc = 0; + + for ( e = cache->c_lrutail; ; ) { + ei++; + if ( e->bei_e ) { + ent++; + if ( e->bei_state & CACHE_ENTRY_NOT_CACHED ) + nc++; + fprintf( stderr, "ei %d entry %p dn %s\n", ei, (void *) e->bei_e, e->bei_e->e_name.bv_val ); + } + e = e->bei_lrunext; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "counted %d entryInfos and %d entries, %d notcached\n", + ei, ent, nc ); + ei = 0; + for ( e = cache->c_lrutail; ; ) { + ei++; + e = e->bei_lruprev; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "counted %d entryInfos (on lruprev)\n", ei ); +} + +#ifdef SLAPD_UNUSED +static void +bdb_lru_print( Cache *cache ) +{ + EntryInfo *e; + + fprintf( stderr, "LRU circle head: %p\n", (void *) cache->c_lruhead ); + fprintf( stderr, "LRU circle (tail forward):\n" ); + for ( e = cache->c_lrutail; ; ) { + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + e = e->bei_lrunext; + if ( e == cache->c_lrutail ) + break; + } + fprintf( stderr, "LRU circle (tail backward):\n" ); + for ( e = cache->c_lrutail; ; ) { + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + e = e->bei_lruprev; + if ( e == cache->c_lrutail ) + break; + } +} + +static int +bdb_entryinfo_print(void *data, void *arg) +{ + EntryInfo *e = data; + fprintf( stderr, "\t%p, %p id %ld rdn \"%s\"\n", + (void *) e, (void *) e->bei_e, e->bei_id, e->bei_nrdn.bv_val ); + return 0; +} + +static void +bdb_idtree_print(Cache *cache) +{ + avl_apply( cache->c_idtree, bdb_entryinfo_print, NULL, -1, AVL_INORDER ); +} +#endif +#endif + +static void +bdb_reader_free( void *key, void *data ) +{ + /* DB_ENV *env = key; */ + DB_TXN *txn = data; + + if ( txn ) TXN_ABORT( txn ); +} + +/* free up any keys used by the main thread */ +void +bdb_reader_flush( DB_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 ); + bdb_reader_free( env, data ); + } +} + +int +bdb_reader_get( Operation *op, DB_ENV *env, DB_TXN **txn ) +{ + int i, rc; + void *data; + void *ctx; + + if ( !env || !txn ) 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(); + } + + /* Shouldn't happen unless we're single-threaded */ + if ( !ctx ) { + *txn = NULL; + return 0; + } + + if ( ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) { + for ( i=0, rc=1; rc != 0 && i<4; i++ ) { + rc = TXN_BEGIN( env, NULL, txn, DB_READ_COMMITTED ); + if (rc) ldap_pvt_thread_yield(); + } + if ( rc != 0) { + return rc; + } + data = *txn; + if ( ( rc = ldap_pvt_thread_pool_setkey( ctx, env, + data, bdb_reader_free, NULL, NULL ) ) ) { + TXN_ABORT( *txn ); + Debug( LDAP_DEBUG_ANY, "bdb_reader_get: err %s(%d)\n", + db_strerror(rc), rc, 0 ); + + return rc; + } + } else { + *txn = data; + } + return 0; +} diff --git a/servers/slapd/back-bdb/compare.c b/servers/slapd/back-bdb/compare.c new file mode 100644 index 0000000..adc4575 --- /dev/null +++ b/servers/slapd/back-bdb/compare.c @@ -0,0 +1,143 @@ +/* compare.c - bdb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_compare( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei; + int manageDSAit = get_manageDSAit( op ); + + DB_TXN *rtxn; + DB_LOCK lock; + + rs->sr_err = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + +dn2entry_retry: + /* get entry */ + rs->sr_err = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + e = ei->bei_e; + if ( rs->sr_err == DB_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 ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + rs->sr_err = LDAP_REFERRAL; + } + + bdb_cache_return_entry_r( bdb, e, &lock ); + 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; + } + + 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 (!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: + /* free entry */ + if ( e != NULL ) { + bdb_cache_return_entry_r( bdb, e, &lock ); + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/config.c b/servers/slapd/back-bdb/config.c new file mode 100644 index 0000000..5746db8 --- /dev/null +++ b/servers/slapd/back-bdb/config.c @@ -0,0 +1,951 @@ +/* config.c - bdb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +#include "config.h" + +#include "lutil.h" +#include "ldap_rq.h" + +#ifdef DB_DIRTY_READ +# define SLAP_BDB_ALLOW_DIRTY_READ +#endif + +#define bdb_cf_gen BDB_SYMBOL(cf_gen) +#define bdb_cf_cleanup BDB_SYMBOL(cf_cleanup) +#define bdb_checkpoint BDB_SYMBOL(checkpoint) +#define bdb_online_index BDB_SYMBOL(online_index) + +static ConfigDriver bdb_cf_gen; + +enum { + BDB_CHKPT = 1, + BDB_CONFIG, + BDB_CRYPTFILE, + BDB_CRYPTKEY, + BDB_DIRECTORY, + BDB_NOSYNC, + BDB_DIRTYR, + BDB_INDEX, + BDB_LOCKD, + BDB_SSTACK, + BDB_MODE, + BDB_PGSIZE, + BDB_CHECKSUM +}; + +static ConfigTable bdbcfg[] = { + { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|BDB_DIRECTORY, + bdb_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Directory for database content' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "cachefree", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_minfree), + "( OLcfgDbAt:1.11 NAME 'olcDbCacheFree' " + "DESC 'Number of extra entries to free when max is reached' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "cachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_maxsize), + "( OLcfgDbAt:1.1 NAME 'olcDbCacheSize' " + "DESC 'Entry cache size in entries' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "checkpoint", "kbyte> <min", 3, 3, 0, ARG_MAGIC|BDB_CHKPT, + bdb_cf_gen, "( OLcfgDbAt:1.2 NAME 'olcDbCheckpoint' " + "DESC 'Database checkpoint interval in kbytes and minutes' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "checksum", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|BDB_CHECKSUM, + bdb_cf_gen, "( OLcfgDbAt:1.16 NAME 'olcDbChecksum' " + "DESC 'Enable database checksum validation' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "cryptfile", "file", 2, 2, 0, ARG_STRING|ARG_MAGIC|BDB_CRYPTFILE, + bdb_cf_gen, "( OLcfgDbAt:1.13 NAME 'olcDbCryptFile' " + "DESC 'Pathname of file containing the DB encryption key' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL }, + { "cryptkey", "key", 2, 2, 0, ARG_BERVAL|ARG_MAGIC|BDB_CRYPTKEY, + bdb_cf_gen, "( OLcfgDbAt:1.14 NAME 'olcDbCryptKey' " + "DESC 'DB encryption key' " + "SYNTAX OMsOctetString SINGLE-VALUE )",NULL, NULL }, + { "dbconfig", "DB_CONFIG setting", 1, 0, 0, ARG_MAGIC|BDB_CONFIG, + bdb_cf_gen, "( OLcfgDbAt:1.3 NAME 'olcDbConfig' " + "DESC 'BerkeleyDB DB_CONFIG configuration directives' " + "SYNTAX OMsIA5String X-ORDERED 'VALUES' )", NULL, NULL }, + { "dbnosync", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|BDB_NOSYNC, + bdb_cf_gen, "( OLcfgDbAt:1.4 NAME 'olcDbNoSync' " + "DESC 'Disable synchronous database writes' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "dbpagesize", "db> <size", 3, 3, 0, ARG_MAGIC|BDB_PGSIZE, + bdb_cf_gen, "( OLcfgDbAt:1.15 NAME 'olcDbPageSize' " + "DESC 'Page size of specified DB, in Kbytes' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "dirtyread", NULL, 1, 2, 0, +#ifdef SLAP_BDB_ALLOW_DIRTY_READ + ARG_ON_OFF|ARG_MAGIC|BDB_DIRTYR, bdb_cf_gen, +#else + ARG_IGNORED, NULL, +#endif + "( OLcfgDbAt:1.5 NAME 'olcDbDirtyRead' " + "DESC 'Allow reads of uncommitted data' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "dncachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_cache.c_eimax), + "( OLcfgDbAt:1.12 NAME 'olcDbDNcacheSize' " + "DESC 'DN cache size' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "idlcachesize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_idl_cache_max_size), + "( OLcfgDbAt:1.6 NAME 'olcDbIDLcacheSize' " + "DESC 'IDL cache size in IDLs' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|BDB_INDEX, + bdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute index parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "linearindex", NULL, 1, 2, 0, ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_linear_index), + "( OLcfgDbAt:1.7 NAME 'olcDbLinearIndex' " + "DESC 'Index attributes one at a time' " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "lockdetect", "policy", 2, 2, 0, ARG_MAGIC|BDB_LOCKD, + bdb_cf_gen, "( OLcfgDbAt:1.8 NAME 'olcDbLockDetect' " + "DESC 'Deadlock detection algorithm' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "mode", "mode", 2, 2, 0, ARG_MAGIC|BDB_MODE, + bdb_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|BDB_SSTACK, + bdb_cf_gen, "( OLcfgDbAt:1.9 NAME 'olcDbSearchStack' " + "DESC 'Depth of search stack in IDLs' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "shm_key", "key", 2, 2, 0, ARG_LONG|ARG_OFFSET, + (void *)offsetof(struct bdb_info, bi_shm_key), + "( OLcfgDbAt:1.10 NAME 'olcDbShmKey' " + "DESC 'Key for shared memory region' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs bdbocs[] = { + { +#ifdef BDB_HIER + "( OLcfgDbOc:1.2 " + "NAME 'olcHdbConfig' " + "DESC 'HDB backend configuration' " +#else + "( OLcfgDbOc:1.1 " + "NAME 'olcBdbConfig' " + "DESC 'BDB backend configuration' " +#endif + "SUP olcDatabaseConfig " + "MUST olcDbDirectory " + "MAY ( olcDbCacheSize $ olcDbCheckpoint $ olcDbChecksum $ " + "olcDbConfig $ olcDbCryptFile $ olcDbCryptKey $ " + "olcDbNoSync $ olcDbDirtyRead $ olcDbIDLcacheSize $ " + "olcDbIndex $ olcDbLinearIndex $ olcDbLockDetect $ " + "olcDbMode $ olcDbSearchStack $ olcDbShmKey $ " + "olcDbCacheFree $ olcDbDNcacheSize $ olcDbPageSize ) )", + Cft_Database, bdbcfg }, + { NULL, 0, NULL } +}; + +static slap_verbmasks bdb_lockd[] = { + { BER_BVC("default"), DB_LOCK_DEFAULT }, + { BER_BVC("oldest"), DB_LOCK_OLDEST }, + { BER_BVC("random"), DB_LOCK_RANDOM }, + { BER_BVC("youngest"), DB_LOCK_YOUNGEST }, + { BER_BVC("fewest"), DB_LOCK_MINLOCKS }, + { BER_BVNULL, 0 } +}; + +/* perform periodic checkpoints */ +static void * +bdb_checkpoint( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct bdb_info *bdb = rtask->arg; + + TXN_CHECKPOINT( bdb->bi_dbenv, bdb->bi_txn_cp_kbyte, + bdb->bi_txn_cp_min, 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 * +bdb_online_index( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + BackendDB *be = rtask->arg; + struct bdb_info *bdb = be->be_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + DBC *curs; + DBT key, data; + DB_TXN *txn; + DB_LOCK lock; + ID id, nid; + EntryInfo *ei; + int rc, getnext = 1; + int i; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + op->o_bd = be; + + DBTzero( &key ); + DBTzero( &data ); + + id = 1; + key.data = &nid; + key.size = key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + + while ( 1 ) { + if ( slapd_shutdown ) + break; + + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &txn, bdb->bi_db_opflags ); + if ( rc ) + break; + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_online_index) ": txn id: %x\n", + txn->id(txn), 0, 0 ); + if ( getnext ) { + getnext = 0; + BDB_ID2DISK( id, &nid ); + rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, txn, &curs, bdb->bi_db_opflags ); + if ( rc ) { + TXN_ABORT( txn ); + break; + } + rc = curs->c_get( curs, &key, &data, DB_SET_RANGE ); + curs->c_close( curs ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_NOTFOUND ) + rc = 0; + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + break; + } + BDB_DISK2ID( &nid, &id ); + } + + ei = NULL; + rc = bdb_cache_find_id( op, txn, id, &ei, 0, &lock ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + if ( rc == DB_NOTFOUND ) { + id++; + getnext = 1; + continue; + } + break; + } + if ( ei->bei_e ) { + rc = bdb_index_entry( op, txn, BDB_INDEX_UPDATE_OP, ei->bei_e ); + if ( rc ) { + TXN_ABORT( txn ); + if ( rc == DB_LOCK_DEADLOCK ) { + ldap_pvt_thread_yield(); + continue; + } + break; + } + rc = TXN_COMMIT( txn, 0 ); + txn = NULL; + } + id++; + getnext = 1; + } + + for ( i = 0; i < bdb->bi_nattrs; i++ ) { + if ( bdb->bi_attrs[ i ]->ai_indexmask & BDB_INDEX_DELETING + || bdb->bi_attrs[ i ]->ai_newmask == 0 ) + { + continue; + } + bdb->bi_attrs[ i ]->ai_indexmask = bdb->bi_attrs[ i ]->ai_newmask; + bdb->bi_attrs[ i ]->ai_newmask = 0; + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + bdb->bi_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 +bdb_cf_cleanup( ConfigArgs *c ) +{ + struct bdb_info *bdb = c->be->be_private; + int rc = 0; + BerVarray bva; + + if ( bdb->bi_flags & BDB_DEL_INDEX ) { + bdb_attr_flush( bdb ); + bdb->bi_flags ^= BDB_DEL_INDEX; + } + + if ( bdb->bi_flags & BDB_RE_OPEN ) { + bdb->bi_flags ^= BDB_RE_OPEN; + bva = bdb->bi_db_config; + bdb->bi_db_config = NULL; + rc = c->be->bd_info->bi_db_close( c->be, &c->reply ); + if ( rc == 0 ) { + if ( bdb->bi_flags & BDB_UPD_CONFIG ) { + if ( bva ) { + int i; + FILE *f = fopen( bdb->bi_db_config_path, "w" ); + if ( f ) { + bdb->bi_db_config = bva; + bva = NULL; + for (i=0; bdb->bi_db_config[i].bv_val; i++) + fprintf( f, "%s\n", bdb->bi_db_config[i].bv_val ); + fclose( f ); + } else { + ber_bvarray_free( bva ); + } + } else { + unlink( bdb->bi_db_config_path ); + } + bdb->bi_flags ^= BDB_UPD_CONFIG; + } + 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(bdb_cf_cleanup) + ": %s\n", c->cr_msg, 0, 0 ); + rc = LDAP_OTHER; + } + } + return rc; +} + +static int +bdb_cf_gen( ConfigArgs *c ) +{ + struct bdb_info *bdb = c->be->be_private; + int rc; + + if ( c->op == SLAP_CONFIG_EMIT ) { + rc = 0; + switch( c->type ) { + case BDB_MODE: { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "0%o", bdb->bi_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 BDB_CHKPT: + if ( bdb->bi_txn_cp ) { + char buf[64]; + struct berval bv; + bv.bv_len = snprintf( buf, sizeof(buf), "%ld %ld", + (long) bdb->bi_txn_cp_kbyte, (long) bdb->bi_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 BDB_CRYPTFILE: + if ( bdb->bi_db_crypt_file ) { + c->value_string = ch_strdup( bdb->bi_db_crypt_file ); + } else { + rc = 1; + } + break; + + /* If a crypt file has been set, its contents are copied here. + * But we don't want the key to be incorporated here. + */ + case BDB_CRYPTKEY: + if ( !bdb->bi_db_crypt_file && !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + value_add_one( &c->rvalue_vals, &bdb->bi_db_crypt_key ); + } else { + rc = 1; + } + break; + + case BDB_DIRECTORY: + if ( bdb->bi_dbenv_home ) { + c->value_string = ch_strdup( bdb->bi_dbenv_home ); + } else { + rc = 1; + } + break; + + case BDB_CONFIG: + if ( !( bdb->bi_flags & BDB_IS_OPEN ) + && !bdb->bi_db_config ) + { + char buf[SLAP_TEXT_BUFLEN]; + FILE *f = fopen( bdb->bi_db_config_path, "r" ); + struct berval bv; + + if ( f ) { + bdb->bi_flags |= BDB_HAS_CONFIG; + while ( fgets( buf, sizeof(buf), f )) { + ber_str2bv( buf, 0, 1, &bv ); + if ( bv.bv_len > 0 && bv.bv_val[bv.bv_len-1] == '\n' ) { + bv.bv_len--; + bv.bv_val[bv.bv_len] = '\0'; + } + /* shouldn't need this, but ... */ + if ( bv.bv_len > 0 && bv.bv_val[bv.bv_len-1] == '\r' ) { + bv.bv_len--; + bv.bv_val[bv.bv_len] = '\0'; + } + ber_bvarray_add( &bdb->bi_db_config, &bv ); + } + fclose( f ); + } + } + if ( bdb->bi_db_config ) { + int i; + struct berval bv; + + bv.bv_val = c->log; + for (i=0; !BER_BVISNULL(&bdb->bi_db_config[i]); i++) { + bv.bv_len = sprintf( bv.bv_val, "{%d}%s", i, + bdb->bi_db_config[i].bv_val ); + value_add_one( &c->rvalue_vals, &bv ); + } + } + if ( !c->rvalue_vals ) rc = 1; + break; + + case BDB_NOSYNC: + if ( bdb->bi_dbenv_xflags & DB_TXN_NOSYNC ) + c->value_int = 1; + break; + + case BDB_CHECKSUM: + if ( bdb->bi_flags & BDB_CHKSUM ) + c->value_int = 1; + break; + + case BDB_INDEX: + bdb_attr_index_unparse( bdb, &c->rvalue_vals ); + if ( !c->rvalue_vals ) rc = 1; + break; + + case BDB_LOCKD: + rc = 1; + if ( bdb->bi_lock_detect != DB_LOCK_DEFAULT ) { + int i; + for (i=0; !BER_BVISNULL(&bdb_lockd[i].word); i++) { + if ( bdb->bi_lock_detect == (u_int32_t)bdb_lockd[i].mask ) { + value_add_one( &c->rvalue_vals, &bdb_lockd[i].word ); + rc = 0; + break; + } + } + } + break; + + case BDB_SSTACK: + c->value_int = bdb->bi_search_stack_depth; + break; + + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps; + char buf[SLAP_TEXT_BUFLEN]; + struct berval bv; + int rc = 1; + + bv.bv_val = buf; + for ( ps = bdb->bi_pagesizes; ps; ps = ps->bdp_next ) { + bv.bv_len = sprintf( buf, "%s %d", ps->bdp_name.bv_val, + ps->bdp_size / 1024 ); + value_add_one( &c->rvalue_vals, &bv ); + rc = 0; + + } + break; + } + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + rc = 0; + switch( c->type ) { + case BDB_MODE: +#if 0 + /* FIXME: does it make any sense to change the mode, + * if we don't exec a chmod()? */ + bdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + break; +#endif + + /* single-valued no-ops */ + case BDB_LOCKD: + case BDB_SSTACK: + break; + + case BDB_CHKPT: + if ( bdb->bi_txn_cp_task ) { + struct re_s *re = bdb->bi_txn_cp_task; + bdb->bi_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 ); + } + bdb->bi_txn_cp = 0; + break; + case BDB_CONFIG: + if ( c->valx < 0 ) { + ber_bvarray_free( bdb->bi_db_config ); + bdb->bi_db_config = NULL; + } else { + int i = c->valx; + ch_free( bdb->bi_db_config[i].bv_val ); + for (; bdb->bi_db_config[i].bv_val; i++) + bdb->bi_db_config[i] = bdb->bi_db_config[i+1]; + } + bdb->bi_flags |= BDB_UPD_CONFIG|BDB_RE_OPEN; + c->cleanup = bdb_cf_cleanup; + break; + /* Doesn't really make sense to change these on the fly; + * the entire DB must be dumped and reloaded + */ + case BDB_CRYPTFILE: + if ( bdb->bi_db_crypt_file ) { + ch_free( bdb->bi_db_crypt_file ); + bdb->bi_db_crypt_file = NULL; + } + /* FALLTHRU */ + case BDB_CRYPTKEY: + if ( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + ch_free( bdb->bi_db_crypt_key.bv_val ); + BER_BVZERO( &bdb->bi_db_crypt_key ); + } + break; + case BDB_DIRECTORY: + bdb->bi_flags |= BDB_RE_OPEN; + bdb->bi_flags ^= BDB_HAS_CONFIG; + ch_free( bdb->bi_dbenv_home ); + bdb->bi_dbenv_home = NULL; + ch_free( bdb->bi_db_config_path ); + bdb->bi_db_config_path = NULL; + c->cleanup = bdb_cf_cleanup; + ldap_pvt_thread_pool_purgekey( bdb->bi_dbenv ); + break; + case BDB_NOSYNC: + bdb->bi_dbenv->set_flags( bdb->bi_dbenv, DB_TXN_NOSYNC, 0 ); + break; + case BDB_CHECKSUM: + bdb->bi_flags &= ~BDB_CHKSUM; + break; + case BDB_INDEX: + if ( c->valx == -1 ) { + int i; + + /* delete all */ + for ( i = 0; i < bdb->bi_nattrs; i++ ) { + bdb->bi_attrs[i]->ai_indexmask |= BDB_INDEX_DELETING; + } + bdb->bi_defaultmask = 0; + bdb->bi_flags |= BDB_DEL_INDEX; + c->cleanup = bdb_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 )) { + bdb->bi_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 = bdb_attr_mask( bdb, ad ); + /* if we got here... */ + assert( ai != NULL ); + + ai->ai_indexmask |= BDB_INDEX_DELETING; + bdb->bi_flags |= BDB_DEL_INDEX; + c->cleanup = bdb_cf_cleanup; + } + + bv.bv_val[ bv.bv_len ] = sep; + ldap_charray_free( attrs ); + } + } + break; + /* doesn't make sense on the fly; the DB file must be + * recreated + */ + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps, **prev; + int i; + + for ( i = 0, prev = &bdb->bi_pagesizes, ps = *prev; ps; + prev = &ps->bdp_next, ps = ps->bdp_next, i++ ) { + if ( c->valx == -1 || i == c->valx ) { + *prev = ps->bdp_next; + ch_free( ps ); + ps = *prev; + if ( i == c->valx ) break; + } + } + } + break; + } + return rc; + } + + switch( c->type ) { + case BDB_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; + } + bdb->bi_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)); + } + } + bdb->bi_dbenv_mode = mode; + } + break; + case BDB_CHKPT: { + long l; + bdb->bi_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; + } + bdb->bi_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; + } + bdb->bi_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) && bdb->bi_txn_cp_min ) { + struct re_s *re = bdb->bi_txn_cp_task; + if ( re ) { + re->interval.tv_sec = bdb->bi_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 ); + bdb->bi_txn_cp_task = ldap_pvt_runqueue_insert( &slapd_rq, + bdb->bi_txn_cp_min * 60, bdb_checkpoint, bdb, + LDAP_XSTRING(bdb_checkpoint), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } break; + + case BDB_CONFIG: { + char *ptr = c->line; + struct berval bv; + + if ( c->op == SLAP_CONFIG_ADD ) { + ptr += STRLENOF("dbconfig"); + while (!isspace((unsigned char)*ptr)) ptr++; + while (isspace((unsigned char)*ptr)) ptr++; + } + + if ( bdb->bi_flags & BDB_IS_OPEN ) { + bdb->bi_flags |= BDB_UPD_CONFIG|BDB_RE_OPEN; + c->cleanup = bdb_cf_cleanup; + } else { + /* If we're just starting up... + */ + FILE *f; + /* If a DB_CONFIG file exists, or we don't know the path + * to the DB_CONFIG file, ignore these directives + */ + if (( bdb->bi_flags & BDB_HAS_CONFIG ) || !bdb->bi_db_config_path ) + break; + f = fopen( bdb->bi_db_config_path, "a" ); + if ( f ) { + /* FIXME: EBCDIC probably needs special handling */ + fprintf( f, "%s\n", ptr ); + fclose( f ); + } + } + ber_str2bv( ptr, 0, 1, &bv ); + ber_bvarray_add( &bdb->bi_db_config, &bv ); + } + break; + + case BDB_CRYPTFILE: + rc = lutil_get_filed_password( c->value_string, &bdb->bi_db_crypt_key ); + if ( rc == 0 ) { + bdb->bi_db_crypt_file = c->value_string; + } + break; + + /* Cannot set key if file was already set */ + case BDB_CRYPTKEY: + if ( bdb->bi_db_crypt_file ) { + rc = 1; + } else { + bdb->bi_db_crypt_key = c->value_bv; + } + break; + + case BDB_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 ( bdb->bi_dbenv_home ) + ch_free( bdb->bi_dbenv_home ); + bdb->bi_dbenv_home = c->value_string; + + /* See if a DB_CONFIG file already exists here */ + if ( bdb->bi_db_config_path ) + ch_free( bdb->bi_db_config_path ); + bdb->bi_db_config_path = ch_malloc( len + + STRLENOF(LDAP_DIRSEP) + STRLENOF("DB_CONFIG") + 1 ); + ptr = lutil_strcopy( bdb->bi_db_config_path, bdb->bi_dbenv_home ); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "DB_CONFIG" ); + + f = fopen( bdb->bi_db_config_path, "r" ); + if ( f ) { + bdb->bi_flags |= BDB_HAS_CONFIG; + fclose(f); + } + } + break; + + case BDB_NOSYNC: + if ( c->value_int ) + bdb->bi_dbenv_xflags |= DB_TXN_NOSYNC; + else + bdb->bi_dbenv_xflags &= ~DB_TXN_NOSYNC; + if ( bdb->bi_flags & BDB_IS_OPEN ) { + bdb->bi_dbenv->set_flags( bdb->bi_dbenv, DB_TXN_NOSYNC, + c->value_int ); + } + break; + + case BDB_CHECKSUM: + if ( c->value_int ) + bdb->bi_flags |= BDB_CHKSUM; + else + bdb->bi_flags &= ~BDB_CHKSUM; + break; + + case BDB_INDEX: + rc = bdb_attr_index_config( bdb, c->fname, c->lineno, + c->argc - 1, &c->argv[1], &c->reply); + + if( rc != LDAP_SUCCESS ) return 1; + if (( bdb->bi_flags & BDB_IS_OPEN ) && !bdb->bi_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 ); + bdb->bi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000, + bdb_online_index, c->be, + LDAP_XSTRING(bdb_online_index), c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + break; + + case BDB_LOCKD: + rc = verb_to_mask( c->argv[1], bdb_lockd ); + if ( BER_BVISNULL(&bdb_lockd[rc].word) ) { + fprintf( stderr, "%s: " + "bad policy (%s) in \"lockDetect <policy>\" line\n", + c->log, c->argv[1] ); + return 1; + } + bdb->bi_lock_detect = (u_int32_t)rc; + break; + + case BDB_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; + } + bdb->bi_search_stack_depth = c->value_int; + break; + + case BDB_PGSIZE: { + struct bdb_db_pgsize *ps, **prev; + int i, s; + + s = atoi(c->argv[2]); + if ( s < 1 || s > 64 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: size must be > 0 and <= 64: %d", + c->log, s ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + i = strlen(c->argv[1]); + ps = ch_malloc( sizeof(struct bdb_db_pgsize) + i + 1 ); + ps->bdp_next = NULL; + ps->bdp_name.bv_len = i; + ps->bdp_name.bv_val = (char *)(ps+1); + strcpy( ps->bdp_name.bv_val, c->argv[1] ); + ps->bdp_size = s * 1024; + for ( prev = &bdb->bi_pagesizes; *prev; prev = &(*prev)->bdp_next ) + ; + *prev = ps; + } + break; + } + return 0; +} + +int bdb_back_init_cf( BackendInfo *bi ) +{ + int rc; + bi->bi_cf_ocs = bdbocs; + + rc = config_register_schema( bdbcfg, bdbocs ); + if ( rc ) return rc; + return 0; +} diff --git a/servers/slapd/back-bdb/dbcache.c b/servers/slapd/back-bdb/dbcache.c new file mode 100644 index 0000000..74c304a --- /dev/null +++ b/servers/slapd/back-bdb/dbcache.c @@ -0,0 +1,210 @@ +/* dbcache.c - manage cache of open databases */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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/errno.h> +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> +#include <sys/stat.h> + +#include "slap.h" +#include "back-bdb.h" +#include "lutil_hash.h" + +#ifdef BDB_INDEX_USE_HASH +/* Pass-thru hash function. Since the indexer is already giving us hash + * values as keys, we don't need BDB to re-hash them. + */ +static u_int32_t +bdb_db_hash( + DB *db, + const void *bytes, + u_int32_t length +) +{ + u_int32_t ret = 0; + unsigned char *dst = (unsigned char *)&ret; + const unsigned char *src = (const unsigned char *)bytes; + + if ( length > sizeof(u_int32_t) ) + length = sizeof(u_int32_t); + + while ( length ) { + *dst++ = *src++; + length--; + } + return ret; +} +#define BDB_INDEXTYPE DB_HASH +#else +#define BDB_INDEXTYPE DB_BTREE +#endif + +/* If a configured size is found, return it, otherwise return 0 */ +int +bdb_db_findsize( + struct bdb_info *bdb, + struct berval *name +) +{ + struct bdb_db_pgsize *bp; + int rc; + + for ( bp = bdb->bi_pagesizes; bp; bp=bp->bdp_next ) { + rc = strncmp( name->bv_val, bp->bdp_name.bv_val, name->bv_len ); + if ( !rc ) { + if ( name->bv_len == bp->bdp_name.bv_len ) + return bp->bdp_size; + if ( name->bv_len < bp->bdp_name.bv_len && + bp->bdp_name.bv_val[name->bv_len] == '.' ) + return bp->bdp_size; + } + } + return 0; +} + +int +bdb_db_cache( + Backend *be, + struct berval *name, + DB **dbout ) +{ + int i, flags; + int rc; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct bdb_db_info *db; + char *file; + + *dbout = NULL; + + for( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + if( !ber_bvcmp( &bdb->bi_databases[i]->bdi_name, name) ) { + *dbout = bdb->bi_databases[i]->bdi_db; + return 0; + } + } + + ldap_pvt_thread_mutex_lock( &bdb->bi_database_mutex ); + + /* check again! may have been added by another thread */ + for( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + if( !ber_bvcmp( &bdb->bi_databases[i]->bdi_name, name) ) { + *dbout = bdb->bi_databases[i]->bdi_db; + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return 0; + } + } + + if( i >= BDB_INDICES ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return -1; + } + + db = (struct bdb_db_info *) ch_calloc(1, sizeof(struct bdb_db_info)); + + ber_dupbv( &db->bdi_name, name ); + + rc = db_create( &db->bdi_db, bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db_create(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + ch_free( db ); + return rc; + } + + if( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_ENCRYPT ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db set_flags(DB_ENCRYPT)(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + } + + if( bdb->bi_flags & BDB_CHKSUM ) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_CHKSUM ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db set_flags(DB_CHKSUM)(%s) failed: %s (%d)\n", + bdb->bi_dbenv_home, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + } + + /* If no explicit size set, use the FS default */ + flags = bdb_db_findsize( bdb, name ); + if ( flags ) + rc = db->bdi_db->set_pagesize( db->bdi_db, flags ); + +#ifdef BDB_INDEX_USE_HASH + rc = db->bdi_db->set_h_hash( db->bdi_db, bdb_db_hash ); +#endif + rc = db->bdi_db->set_flags( db->bdi_db, DB_DUP | DB_DUPSORT ); + + file = ch_malloc( db->bdi_name.bv_len + sizeof(BDB_SUFFIX) ); + strcpy( file, db->bdi_name.bv_val ); + strcpy( file+db->bdi_name.bv_len, BDB_SUFFIX ); + +#ifdef HAVE_EBCDIC + __atoe( file ); +#endif + flags = DB_CREATE | DB_THREAD; +#ifdef DB_AUTO_COMMIT + if ( !( slapMode & SLAP_TOOL_QUICK )) + flags |= DB_AUTO_COMMIT; +#endif + /* Cannot Truncate when Transactions are in use */ + if ( (slapMode & (SLAP_TOOL_QUICK|SLAP_TRUNCATE_MODE)) == + (SLAP_TOOL_QUICK|SLAP_TRUNCATE_MODE)) + flags |= DB_TRUNCATE; + + rc = DB_OPEN( db->bdi_db, + file, NULL /* name */, + BDB_INDEXTYPE, bdb->bi_db_opflags | flags, bdb->bi_dbenv_mode ); + + ch_free( file ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_cache: db_open(%s) failed: %s (%d)\n", + name->bv_val, db_strerror(rc), rc ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + return rc; + } + + bdb->bi_databases[i] = db; + bdb->bi_ndatabases = i+1; + + *dbout = db->bdi_db; + + ldap_pvt_thread_mutex_unlock( &bdb->bi_database_mutex ); + return 0; +} diff --git a/servers/slapd/back-bdb/delete.c b/servers/slapd/back-bdb/delete.c new file mode 100644 index 0000000..14b34fa --- /dev/null +++ b/servers/slapd/back-bdb/delete.c @@ -0,0 +1,605 @@ +/* delete.c - bdb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_delete( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *matched = NULL; + struct berval pdn = {0, NULL}; + Entry *e = NULL; + Entry *p = NULL; + EntryInfo *ei = NULL, *eip = NULL; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + ID eid; + + DB_LOCK lock, plock; + + int num_retries = 0; + + int rc; + + 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(bdb_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 ); + } + + if( 0 ) { +retry: /* transaction retry */ + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + if( p != NULL ) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + Debug( LDAP_DEBUG_TRACE, + "==> " LDAP_XSTRING(bdb_delete) ": retrying...\n", + 0, 0, 0 ); + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + parent_is_glue = 0; + parent_is_leaf = 0; + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_delete) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_ndn, &pdn ); + } + + /* get entry */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + 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 == 0 ) { + e = ei->bei_e; + eip = ei->bei_parent; + } else { + matched = ei->bei_e; + } + + /* FIXME : dn2entry() should return non-glue entry */ + if ( e == NULL || ( !manageDSAit && is_entry_glue( e ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + if ( matched != NULL ) { + rs->sr_matched = ch_strdup( matched->e_dn ); + rs->sr_ref = is_entry_referral( matched ) + ? get_entry_referrals( op, matched ) + : NULL; + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, matched); + matched = NULL; + + } else { + rs->sr_ref = referral_rewrite( default_referral, NULL, + &op->o_req_dn, LDAP_SCOPE_DEFAULT ); + } + + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED; + goto return_results; + } + + rc = bdb_cache_find_id( op, ltid, eip->bei_id, &eip, 0, &plock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + case DB_NOTFOUND: + break; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( eip ) p = eip->bei_e; + + if ( pdn.bv_len != 0 ) { + if( p == NULL || !bvmatch( &pdn, &p->e_nname )) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": parent " + "does not exist\n", 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "could not locate parent of entry"; + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_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 ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_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(bdb_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 ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_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(bdb_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(bdb_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; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_begin(2) failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_delete) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + BDB_LOG_PRINTF( bdb->bi_dbenv, lt2, "slapd Starting delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + /* Can't do it if we have kids */ + rs->sr_err = bdb_cache_children( op, lt2, e ); + if( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_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(bdb_delete) + ": has_children failed: %s (%d)\n", + db_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 = bdb_dn2id_delete( op, lt2, eip, e ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": dn2id failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete indices for old attributes */ + rs->sr_err = bdb_index_entry_del( op, lt2, e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": index failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + 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 = bdb_index_values( op, lt2, slap_schema.si_ad_entryCSN, + vals, 0, SLAP_INDEX_ADD_OP ); + if ( rs->sr_err != LDAP_SUCCESS ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entryCSN index update failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + + /* delete from id2entry */ + rs->sr_err = bdb_id2entry_delete( op->o_bd, lt2, e ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_delete) ": id2entry failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + 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 = bdb_cache_children( op, lt2, p ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_delete) + ": has_children failed: %s (%d)\n", + db_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; + } + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + + BDB_LOG_PRINTF( bdb->bi_dbenv, lt2, "slapd Commit1 delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) failed"; + goto return_results; + } + + eid = e->e_id; + +#if 0 /* Do we want to reclaim deleted IDs? */ + ldap_pvt_thread_mutex_lock( &bdb->bi_lastid_mutex ); + if ( e->e_id == bdb->bi_lastid ) { + bdb_last_id( op->o_bd, ltid ); + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_lastid_mutex ); +#endif + + if( op->o_noop ) { + if ( ( rs->sr_err = TXN_ABORT( ltid ) ) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + goto return_results; + } + } else { + + BDB_LOG_PRINTF( bdb->bi_dbenv, ltid, "slapd Cache delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + rc = bdb_cache_delete( bdb, e, ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + rs->sr_err = TXN_COMMIT( ltid, 0 ); + } + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + BDB_LOG_PRINTF( bdb->bi_dbenv, NULL, "slapd Committed delete %s(%d)", + e->e_nname.bv_val, e->e_id ); + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + db_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(bdb_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + eid, 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 ) + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + + /* free entry */ + if( e != NULL ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + /* Free the EntryInfo and the Entry */ + bdb_cache_entryinfo_lock( BEI(e) ); + bdb_cache_delete_cleanup( &bdb->bi_cache, BEI(e) ); + } else { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + } + } + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_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( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-bdb/dn2entry.c b/servers/slapd/back-bdb/dn2entry.c new file mode 100644 index 0000000..2213fe9 --- /dev/null +++ b/servers/slapd/back-bdb/dn2entry.c @@ -0,0 +1,84 @@ +/* dn2entry.c - routines to deal with the dn2id / id2entry glue */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.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 +bdb_dn2entry( + Operation *op, + DB_TXN *tid, + struct berval *dn, + EntryInfo **e, + int matched, + DB_LOCK *lock ) +{ + EntryInfo *ei = NULL; + int rc, rc2; + + Debug(LDAP_DEBUG_TRACE, "bdb_dn2entry(\"%s\")\n", + dn->bv_val, 0, 0 ); + + *e = NULL; + + rc = bdb_cache_find_ndn( op, tid, dn, &ei ); + if ( rc ) { + if ( matched && rc == DB_NOTFOUND ) { + /* Set the return value, whether we have its entry + * or not. + */ + *e = ei; + if ( ei && ei->bei_id ) { + rc2 = bdb_cache_find_id( op, tid, ei->bei_id, + &ei, ID_LOCKED, lock ); + if ( rc2 ) rc = rc2; + } else if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + memset( lock, 0, sizeof( *lock )); + lock->mode = DB_LOCK_NG; + } + } else if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + } + } else { + rc = bdb_cache_find_id( op, tid, ei->bei_id, &ei, ID_LOCKED, + lock ); + if ( rc == 0 ) { + *e = ei; + } else if ( matched && rc == DB_NOTFOUND ) { + /* always return EntryInfo */ + if ( ei->bei_parent ) { + ei = ei->bei_parent; + rc2 = bdb_cache_find_id( op, tid, ei->bei_id, &ei, 0, + lock ); + if ( rc2 ) rc = rc2; + } + *e = ei; + } + } + + return rc; +} diff --git a/servers/slapd/back-bdb/dn2id.c b/servers/slapd/back-bdb/dn2id.c new file mode 100644 index 0000000..1904c9c --- /dev/null +++ b/servers/slapd/back-bdb/dn2id.c @@ -0,0 +1,1215 @@ +/* dn2id.c - routines to deal with the dn2id index */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "idl.h" +#include "lutil.h" + +#ifndef BDB_HIER +int +bdb_dn2id_add( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int rc; + DBT key, data; + ID nid; + char *buf; + struct berval ptr, pdn; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_add 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + assert( e->e_id != NOID ); + + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + buf = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + key.data = buf; + buf[0] = DN_BASE_PREFIX; + ptr.bv_val = buf + 1; + ptr.bv_len = e->e_nname.bv_len; + AC_MEMCPY( ptr.bv_val, e->e_nname.bv_val, e->e_nname.bv_len ); + ptr.bv_val[ptr.bv_len] = '\0'; + + DBTzero( &data ); + data.data = &nid; + data.size = sizeof( nid ); + BDB_ID2DISK( e->e_id, &nid ); + + /* store it -- don't override */ + rc = db->put( db, txn, &key, &data, DB_NOOVERWRITE ); + if( rc != 0 ) { + char buf[ SLAP_TEXT_BUFLEN ]; + snprintf( buf, sizeof( buf ), "%s => bdb_dn2id_add dn=\"%s\" ID=0x%lx", + op->o_log_prefix, e->e_name.bv_val, e->e_id ); + Debug( LDAP_DEBUG_ANY, "%s: put failed: %s %d\n", + buf, db_strerror(rc), rc ); + goto done; + } + +#ifndef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + buf[0] = DN_SUBTREE_PREFIX; + rc = db->put( db, txn, &key, &data, DB_NOOVERWRITE ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: subtree (%s) put failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + +#ifdef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + pdn.bv_val[-1] = DN_ONE_PREFIX; + key.data = pdn.bv_val-1; + ptr = pdn; + + rc = bdb_idl_insert_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: parent (%s) insert failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + } + +#ifndef BDB_MULTIPLE_SUFFIXES + while( !be_issuffix( op->o_bd, &ptr )) +#else + for (;;) +#endif + { + ptr.bv_val[-1] = DN_SUBTREE_PREFIX; + + rc = bdb_idl_insert_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_add 0x%lx: subtree (%s) insert failed: %d\n", + e->e_id, ptr.bv_val, rc ); + break; + } +#ifdef BDB_MULTIPLE_SUFFIXES + if( be_issuffix( op->o_bd, &ptr )) break; +#endif + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + key.data = pdn.bv_val - 1; + ptr = pdn; + } + } + +done: + op->o_tmpfree( buf, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + +int +bdb_dn2id_delete( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + char *buf; + DBT key; + struct berval pdn, ptr; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_delete 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + buf = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + key.data = buf; + key.flags = DB_DBT_USERMEM; + buf[0] = DN_BASE_PREFIX; + ptr.bv_val = buf+1; + ptr.bv_len = e->e_nname.bv_len; + AC_MEMCPY( ptr.bv_val, e->e_nname.bv_val, e->e_nname.bv_len ); + ptr.bv_val[ptr.bv_len] = '\0'; + + /* delete it */ + rc = db->del( db, txn, &key, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_dn2id_delete 0x%lx: delete failed: %s %d\n", + e->e_id, db_strerror(rc), rc ); + goto done; + } + +#ifndef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + buf[0] = DN_SUBTREE_PREFIX; + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: subtree (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + +#ifdef BDB_MULTIPLE_SUFFIXES + if( !be_issuffix( op->o_bd, &ptr )) +#endif + { + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + pdn.bv_val[-1] = DN_ONE_PREFIX; + key.data = pdn.bv_val - 1; + ptr = pdn; + + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: parent (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } + } + +#ifndef BDB_MULTIPLE_SUFFIXES + while( !be_issuffix( op->o_bd, &ptr )) +#else + for (;;) +#endif + { + ptr.bv_val[-1] = DN_SUBTREE_PREFIX; + + rc = bdb_idl_delete_key( op->o_bd, db, txn, &key, e->e_id ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_dn2id_delete 0x%lx: subtree (%s) delete failed: %d\n", + e->e_id, ptr.bv_val, rc ); + goto done; + } +#ifdef BDB_MULTIPLE_SUFFIXES + if( be_issuffix( op->o_bd, &ptr )) break; +#endif + dnParent( &ptr, &pdn ); + + key.size = pdn.bv_len + 2; + key.ulen = key.size; + key.data = pdn.bv_val - 1; + ptr = pdn; + } + } + +done: + op->o_tmpfree( buf, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_delete 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + +int +bdb_dn2id( + Operation *op, + struct berval *dn, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int rc; + DBT key, data; + ID nid; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id(\"%s\")\n", dn->bv_val, 0, 0 ); + + DBTzero( &key ); + key.size = dn->bv_len + 2; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = DN_BASE_PREFIX; + AC_MEMCPY( &((char *)key.data)[1], dn->bv_val, key.size - 1 ); + + /* store the ID */ + DBTzero( &data ); + data.data = &nid; + data.ulen = sizeof(ID); + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, txn, cursor, bdb->bi_db_opflags ); + + /* fetch it */ + if ( !rc ) + rc = (*cursor)->c_get( *cursor, &key, &data, DB_SET ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + } else { + BDB_DISK2ID( &nid, &ei->bei_id ); + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id: got id=0x%lx\n", + ei->bei_id, 0, 0 ); + } + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; +} + +int +bdb_dn2id_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + DBT key, data; + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + ID id; + int rc; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2id_children(\"%s\")\n", + e->e_nname.bv_val, 0, 0 ); + DBTzero( &key ); + key.size = e->e_nname.bv_len + 2; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = DN_ONE_PREFIX; + AC_MEMCPY( &((char *)key.data)[1], e->e_nname.bv_val, key.size - 1 ); + + if ( bdb->bi_idl_cache_size ) { + rc = bdb_idl_cache_get( bdb, db, &key, NULL ); + if ( rc != LDAP_NO_SUCH_OBJECT ) { + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; + } + } + /* we actually could do a empty get... */ + DBTzero( &data ); + data.data = &id; + data.ulen = sizeof(id); + data.flags = DB_DBT_USERMEM; + data.doff = 0; + data.dlen = sizeof(id); + + rc = db->get( db, txn, &key, &data, bdb->bi_db_opflags ); + op->o_tmpfree( key.data, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_dn2id_children(\"%s\"): %s (%d)\n", + e->e_nname.bv_val, + rc == 0 ? "" : ( rc == DB_NOTFOUND ? "no " : + db_strerror(rc) ), rc ); + + return rc; +} + +int +bdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ) +{ + int rc; + DBT key; + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + int prefix = ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) + ? DN_ONE_PREFIX : DN_SUBTREE_PREFIX; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_dn2idl(\"%s\")\n", + ndn->bv_val, 0, 0 ); + +#ifndef BDB_MULTIPLE_SUFFIXES + if ( prefix == DN_SUBTREE_PREFIX + && ( ei->bei_id == 0 || + ( ei->bei_parent->bei_id == 0 && op->o_bd->be_suffix[0].bv_len ))) { + BDB_IDL_ALL(bdb, ids); + return 0; + } +#endif + + DBTzero( &key ); + key.size = ndn->bv_len + 2; + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + key.data = op->o_tmpalloc( key.size, op->o_tmpmemctx ); + ((char *)key.data)[0] = prefix; + AC_MEMCPY( &((char *)key.data)[1], ndn->bv_val, key.size - 1 ); + + BDB_IDL_ZERO( ids ); + rc = bdb_idl_fetch_key( op->o_bd, db, txn, &key, ids, NULL, 0 ); + + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_dn2idl: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + + } else { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_dn2idl: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST( ids ), (long) BDB_IDL_LAST( ids ) ); + } + + op->o_tmpfree( key.data, op->o_tmpmemctx ); + return rc; +} + +#else /* BDB_HIER */ +/* 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 +hdb_dup_compare( + DB *db, + const DBT *usrkey, + const DBT *curkey +) +{ + diskNode *un, *cn; + int rc; + + un = (diskNode *)usrkey->data; + cn = (diskNode *)curkey->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; + + return strcmp( un->nrdn, cn->nrdn ); +} + +/* This function constructs a full DN for a given entry. + */ +int hdb_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; +} + +/* 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 +hdb_dn2id_add( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + ID nid; + int rc, rlen, nrlen; + diskNode *d; + char *ptr; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_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'; + BDB_ID2DISK( e->e_id, ptr ); + + DBTzero(&key); + DBTzero(&data); + key.size = sizeof(ID); + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( eip->bei_id, &nid ); + + key.data = &nid; + + /* Need to make dummy root node once. Subsequent attempts + * will fail harmlessly. + */ + if ( eip->bei_id == 0 ) { + diskNode dummy = {{0, 0}, "", "", ""}; + data.data = &dummy; + data.size = sizeof(diskNode); + data.flags = DB_DBT_USERMEM; + + db->put( db, txn, &key, &data, DB_NODUPDATA ); + } + + data.data = d; + data.size = sizeof(diskNode) + rlen + nrlen; + data.flags = DB_DBT_USERMEM; + + rc = db->put( db, txn, &key, &data, DB_NODUPDATA ); + + if (rc == 0) { + BDB_ID2DISK( e->e_id, &nid ); + BDB_ID2DISK( eip->bei_id, ptr ); + d->nrdnlen[0] ^= 0x80; + + rc = db->put( db, txn, &key, &data, DB_NODUPDATA ); + } + + /* Update all parents' IDL cache entries */ + if ( rc == 0 && bdb->bi_idl_cache_size ) { + ID tmp[2]; + char *ptr = ((char *)&tmp[1])-1; + key.data = ptr; + key.size = sizeof(ID)+1; + tmp[1] = eip->bei_id; + *ptr = DN_ONE_PREFIX; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + if ( eip->bei_parent ) { + *ptr = DN_SUBTREE_PREFIX; + for (; eip && eip->bei_parent->bei_id; eip = eip->bei_parent) { + tmp[1] = eip->bei_id; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + } + /* Handle DB with empty suffix */ + if ( !op->o_bd->be_suffix[0].bv_len && eip ) { + tmp[1] = eip->bei_id; + bdb_idl_cache_add_id( bdb, db, &key, e->e_id ); + } + } + } + + op->o_tmpfree( d, op->o_tmpmemctx ); + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id_add 0x%lx: %d\n", e->e_id, rc, 0 ); + + return rc; +} + +int +hdb_dn2id_delete( + Operation *op, + DB_TXN *txn, + EntryInfo *eip, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + DBC *cursor; + diskNode *d; + int rc; + ID nid; + unsigned char dlen[2]; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2id_delete 0x%lx: \"%s\"\n", + e->e_id, e->e_ndn, 0 ); + + DBTzero(&key); + key.size = sizeof(ID); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( eip->bei_id, &nid ); + + DBTzero(&data); + data.size = sizeof(diskNode) + BEI(e)->bei_nrdn.bv_len - sizeof(ID) - 1; + data.ulen = data.size; + data.dlen = data.size; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + key.data = &nid; + + d = op->o_tmpalloc( data.size, op->o_tmpmemctx ); + d->nrdnlen[1] = BEI(e)->bei_nrdn.bv_len & 0xff; + d->nrdnlen[0] = (BEI(e)->bei_nrdn.bv_len >> 8) | 0x80; + dlen[0] = d->nrdnlen[0]; + dlen[1] = d->nrdnlen[1]; + memcpy( d->nrdn, BEI(e)->bei_nrdn.bv_val, BEI(e)->bei_nrdn.bv_len+1 ); + data.data = d; + + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if ( rc ) goto func_leave; + + /* Delete our ID from the parent's list */ + rc = cursor->c_get( cursor, &key, &data, DB_GET_BOTH_RANGE ); + if ( rc == 0 ) { + if ( dlen[1] == d->nrdnlen[1] && dlen[0] == d->nrdnlen[0] && + !strcmp( d->nrdn, BEI(e)->bei_nrdn.bv_val )) + rc = cursor->c_del( cursor, 0 ); + else + rc = DB_NOTFOUND; + } + + /* 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 ) { + BDB_ID2DISK( e->e_id, &nid ); + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc == 0 ) + rc = cursor->c_del( cursor, 0 ); + } + + cursor->c_close( cursor ); +func_leave: + op->o_tmpfree( d, op->o_tmpmemctx ); + + /* Delete IDL cache entries */ + if ( rc == 0 && bdb->bi_idl_cache_size ) { + ID tmp[2]; + char *ptr = ((char *)&tmp[1])-1; + key.data = ptr; + key.size = sizeof(ID)+1; + tmp[1] = eip->bei_id; + *ptr = DN_ONE_PREFIX; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + if ( eip ->bei_parent ) { + *ptr = DN_SUBTREE_PREFIX; + for (; eip && eip->bei_parent->bei_id; eip = eip->bei_parent) { + tmp[1] = eip->bei_id; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + } + /* Handle DB with empty suffix */ + if ( !op->o_bd->be_suffix[0].bv_len && eip ) { + tmp[1] = eip->bei_id; + bdb_idl_cache_del_id( bdb, db, &key, e->e_id ); + } + } + } + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id_delete 0x%lx: %d\n", e->e_id, rc, 0 ); + return rc; +} + + +int +hdb_dn2id( + Operation *op, + struct berval *in, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + int rc = 0, nrlen; + diskNode *d; + char *ptr; + unsigned char dlen[2]; + ID idp, parentID; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2id(\"%s\")\n", in->bv_val, 0, 0 ); + + nrlen = dn_rdnlen( op->o_bd, in ); + if (!nrlen) nrlen = in->bv_len; + + DBTzero(&key); + key.size = sizeof(ID); + key.data = &idp; + key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + parentID = ( ei->bei_parent != NULL ) ? ei->bei_parent->bei_id : 0; + BDB_ID2DISK( parentID, &idp ); + + DBTzero(&data); + data.size = sizeof(diskNode) + nrlen - sizeof(ID) - 1; + data.ulen = data.size * 3; + data.dlen = data.ulen; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + rc = db->cursor( db, txn, cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + d = op->o_tmpalloc( data.size * 3, op->o_tmpmemctx ); + d->nrdnlen[1] = nrlen & 0xff; + d->nrdnlen[0] = (nrlen >> 8) | 0x80; + dlen[0] = d->nrdnlen[0]; + dlen[1] = d->nrdnlen[1]; + ptr = lutil_strncopy( d->nrdn, in->bv_val, nrlen ); + *ptr = '\0'; + data.data = d; + + rc = (*cursor)->c_get( *cursor, &key, &data, DB_GET_BOTH_RANGE ); + if ( rc == 0 && (dlen[1] != d->nrdnlen[1] || dlen[0] != d->nrdnlen[0] || + strncmp( d->nrdn, in->bv_val, nrlen ))) { + rc = DB_NOTFOUND; + } + if ( rc == 0 ) { + ptr = (char *) data.data + data.size - sizeof(ID); + BDB_DISK2ID( ptr, &ei->bei_id ); + ei->bei_rdn.bv_len = data.size - sizeof(diskNode) - nrlen; + ptr = d->nrdn + nrlen + 1; + ber_str2bv( ptr, ei->bei_rdn.bv_len, 1, &ei->bei_rdn ); + if ( ei->bei_parent != NULL && !ei->bei_parent->bei_dkids ) { + db_recno_t dkids; + /* How many children does the parent have? */ + /* FIXME: do we need to lock the parent + * entryinfo? Seems safe... + */ + (*cursor)->c_count( *cursor, &dkids, 0 ); + ei->bei_parent->bei_dkids = dkids; + } + } + + op->o_tmpfree( d, op->o_tmpmemctx ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id: get failed: %s (%d)\n", + db_strerror( rc ), rc, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= hdb_dn2id: got id=0x%lx\n", + ei->bei_id, 0, 0 ); + } + + return rc; +} + +int +hdb_dn2id_parent( + Operation *op, + DB_TXN *txn, + EntryInfo *ei, + ID *idp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->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; + BDB_ID2DISK( ei->bei_id, &nid ); + + DBTzero(&data); + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, txn, &cursor, bdb->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); + BDB_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; +} + +int +hdb_dn2id_children( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db = bdb->bi_dn2id->bdi_db; + DBT key, data; + DBC *cursor; + int rc; + ID id; + diskNode d; + + DBTzero(&key); + key.size = sizeof(ID); + key.data = &e->e_id; + key.flags = DB_DBT_USERMEM; + BDB_ID2DISK( e->e_id, &id ); + + /* IDL cache is in host byte order */ + if ( bdb->bi_idl_cache_size ) { + rc = bdb_idl_cache_get( bdb, db, &key, NULL ); + if ( rc != LDAP_NO_SUCH_OBJECT ) { + return rc; + } + } + + key.data = &id; + DBTzero(&data); + data.data = &d; + data.ulen = sizeof(d); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = sizeof(d); + + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc == 0 ) { + db_recno_t dkids; + rc = cursor->c_count( cursor, &dkids, 0 ); + if ( rc == 0 ) { + BEI(e)->bei_dkids = dkids; + if ( dkids < 2 ) rc = DB_NOTFOUND; + } + } + cursor->c_close( cursor ); + return rc; +} + +/* bdb_dn2idl: + * We can't just use bdb_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 bdb_dn2idl_internal + * function uses this cookie when calling itself. + */ + +struct dn2id_cookie { + struct bdb_info *bdb; + 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; + + bdb_idl_append_one( idl, ei->bei_id ); + return 0; +} + +static int +hdb_dn2idl_internal( + struct dn2id_cookie *cx +) +{ + BDB_IDL_ZERO( cx->tmp ); + + if ( cx->bdb->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 = bdb_idl_cache_get(cx->bdb, cx->db, &cx->key, ids); + if ( cx->rc == LDAP_SUCCESS ) { + if ( cx->depth ) { + bdb_idl_delete( cx->tmp, cx->id ); /* ITS#6983, drop our own ID */ + bdb_idl_append( cx->ids, cx->tmp ); + cx->need_sort = 1; + } + return cx->rc; + } + } + *ptr = DN_ONE_PREFIX; + cx->rc = bdb_idl_cache_get(cx->bdb, cx->db, &cx->key, cx->tmp); + if ( cx->rc == LDAP_SUCCESS ) { + goto gotit; + } + if ( cx->rc == DB_NOTFOUND ) { + return cx->rc; + } + } + + bdb_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 ) { + bdb_cache_entryinfo_unlock( cx->ei ); + ldap_pvt_thread_yield(); + bdb_cache_entryinfo_lock( cx->ei ); + if ( cx->ei->bei_ckids+1 == cx->ei->bei_dkids ) { + goto synced; + } + } + + cx->ei->bei_state |= CACHE_ENTRY_ONELEVEL; + + bdb_cache_entryinfo_unlock( cx->ei ); + + cx->rc = cx->db->cursor( cx->db, NULL, &cx->dbc, + cx->bdb->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 = BDB_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; + + BDB_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. + * hdb_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; + bdb_idl_append_one( cx->tmp, ei.bei_id ); + hdb_cache_load( cx->bdb, &ei, &ei2 ); + } + } + } + } + + cx->rc = cx->dbc->c_close( cx->dbc ); +done_one: + bdb_cache_entryinfo_lock( cx->ei ); + cx->ei->bei_state &= ~CACHE_ENTRY_ONELEVEL; + bdb_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 bdb_idl_sort + * will sort it later. + */ + avl_apply( cx->ei->bei_kids, apply_func, + cx->tmp, -1, AVL_POSTORDER ); + } + bdb_cache_entryinfo_unlock( cx->ei ); + } + + if ( !BDB_IDL_IS_RANGE( cx->tmp ) && cx->tmp[0] > 3 ) + bdb_idl_sort( cx->tmp, cx->buf ); + if ( cx->bdb->bi_idl_cache_max_size && !BDB_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; + bdb_idl_cache_put( cx->bdb, cx->db, &cx->key, cx->tmp, cx->rc ); + } + +gotit: + if ( !BDB_IDL_IS_ZERO( cx->tmp )) { + if ( cx->prefix == DN_SUBTREE_PREFIX ) { + bdb_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( BDB_IDL_SIZEOF( cx->tmp ), + cx->op->o_tmpmemctx ); + BDB_IDL_CPY( save, cx->tmp ); + + idcurs = 0; + cx->depth++; + for ( cx->id = bdb_idl_first( save, &idcurs ); + cx->id != NOID; + cx->id = bdb_idl_next( save, &idcurs )) { + EntryInfo *ei2; + cx->ei = NULL; + if ( bdb_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 )) { + BDB_ID2DISK( cx->id, &cx->nid ); + hdb_dn2idl_internal( cx ); + if ( !BDB_IDL_IS_ZERO( cx->tmp )) + nokids = 0; + } + bdb_cache_entryinfo_lock( ei2 ); + ei2->bei_finders--; + bdb_cache_entryinfo_unlock( ei2 ); + } + } + cx->depth--; + cx->op->o_tmpfree( save, cx->op->o_tmpmemctx ); + if ( nokids ) { + bdb_cache_entryinfo_lock( ei ); + ei->bei_state |= CACHE_ENTRY_NO_GRANDKIDS; + bdb_cache_entryinfo_unlock( ei ); + } + } + /* Make sure caller knows it had kids! */ + cx->tmp[0]=1; + + cx->rc = 0; + } else { + BDB_IDL_CPY( cx->ids, cx->tmp ); + } + } + return cx->rc; +} + +int +hdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ) +{ + struct bdb_info *bdb = (struct bdb_info *)op->o_bd->be_private; + struct dn2id_cookie cx; + + Debug( LDAP_DEBUG_TRACE, "=> hdb_dn2idl(\"%s\")\n", + ndn->bv_val, 0, 0 ); + +#ifndef BDB_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 ))) + { + BDB_IDL_ALL( bdb, ids ); + return 0; + } +#endif + + cx.id = ei->bei_id; + BDB_ID2DISK( cx.id, &cx.nid ); + cx.ei = ei; + cx.bdb = bdb; + cx.db = cx.bdb->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 + BDB_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 { + BDB_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); + + hdb_dn2idl_internal(&cx); + if ( cx.need_sort ) { + char *ptr = ((char *)&cx.id)-1; + if ( !BDB_IDL_IS_RANGE( cx.ids ) && cx.ids[0] > 3 ) + bdb_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.bdb->bi_idl_cache_max_size ) + bdb_idl_cache_put( cx.bdb, cx.db, &cx.key, cx.ids, cx.rc ); + } + + if ( cx.rc == DB_NOTFOUND ) + cx.rc = LDAP_SUCCESS; + + return cx.rc; +} +#endif /* BDB_HIER */ diff --git a/servers/slapd/back-bdb/error.c b/servers/slapd/back-bdb/error.c new file mode 100644 index 0000000..788ef5c --- /dev/null +++ b/servers/slapd/back-bdb/error.c @@ -0,0 +1,62 @@ +/* error.c - BDB errcall routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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 "slap.h" +#include "back-bdb.h" + +#if DB_VERSION_FULL < 0x04030000 +void bdb_errcall( const char *pfx, char * msg ) +#else +void bdb_errcall( const DB_ENV *env, const char *pfx, const char * msg ) +#endif +{ +#ifdef HAVE_EBCDIC + if ( msg[0] > 0x7f ) + __etoa( msg ); +#endif + Debug( LDAP_DEBUG_ANY, "bdb(%s): %s\n", pfx, msg, 0 ); +} + +#if DB_VERSION_FULL >= 0x04030000 +void bdb_msgcall( const DB_ENV *env, const char *msg ) +{ +#ifdef HAVE_EBCDIC + if ( msg[0] > 0x7f ) + __etoa( msg ); +#endif + Debug( LDAP_DEBUG_TRACE, "bdb: %s\n", msg, 0, 0 ); +} +#endif + +#ifdef HAVE_EBCDIC + +#undef db_strerror + +/* Not re-entrant! */ +char *ebcdic_dberror( int rc ) +{ + static char msg[1024]; + + strcpy( msg, db_strerror( rc ) ); + __etoa( msg ); + return msg; +} +#endif diff --git a/servers/slapd/back-bdb/extended.c b/servers/slapd/back-bdb/extended.c new file mode 100644 index 0000000..018608e --- /dev/null +++ b/servers/slapd/back-bdb/extended.c @@ -0,0 +1,54 @@ +/* extended.c - bdb backend extended routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "lber_pvt.h" + +static struct exop { + struct berval *oid; + BI_op_extended *extended; +} exop_table[] = { + { NULL, NULL } +}; + +int +bdb_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-bdb/filterindex.c b/servers/slapd/back-bdb/filterindex.c new file mode 100644 index 0000000..7f6c64f --- /dev/null +++ b/servers/slapd/back-bdb/filterindex.c @@ -0,0 +1,1183 @@ +/* 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-2021 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-bdb.h" +#include "idl.h" +#ifdef LDAP_COMP_MATCH +#include <component.h> +#endif + +static int presence_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeDescription *desc, + ID *ids ); + +static int equality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int inequality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ); +static int approx_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ); +static int substring_candidates( + Operation *op, + DB_TXN *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ); + +static int list_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *stack ); + +static int +ext_candidates( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack); + +#ifdef LDAP_COMP_MATCH +static int +comp_candidates ( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ComponentFilter *f, + ID *ids, + ID *tmp, + ID *stack); + +static int +ava_comp_candidates ( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + AttributeAliasing *aa, + ID *ids, + ID *tmp, + ID *stack); +#endif + +int +bdb_filter_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ) +{ + int rc = 0; +#ifdef LDAP_COMP_MATCH + AttributeAliasing *aa; +#endif + Debug( LDAP_DEBUG_FILTER, "=> bdb_filter_candidates\n", 0, 0, 0 ); + + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + BDB_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: + BDB_IDL_ZERO( ids ); + break; + case LDAP_COMPARE_TRUE: { + struct bdb_info *bdb = (struct bdb_info *)op->o_bd->be_private; + BDB_IDL_ALL( bdb, 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 ); + { struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, 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 */ + { struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + } + +out: + Debug( LDAP_DEBUG_FILTER, + "<= bdb_filter_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST( ids ), + (long) BDB_IDL_LAST( ids ) ); + + return rc; +} + +#ifdef LDAP_COMP_MATCH +static int +comp_list_candidates( + Operation *op, + DB_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; + } + BDB_IDL_ZERO( save ); + rc = comp_candidates( op, rtxn, mra, f, save, tmp, save+BDB_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 ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_intersection( ids, save ); + } + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_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) BDB_IDL_FIRST(ids), + (long) BDB_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, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ComponentAssertion *ca, + ID *ids, + ID *tmp, + ID *stack) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + 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; + + BDB_IDL_ALL( bdb, ids ); + + if ( !ca->ca_comp_ref ) + return 0; + + ai = bdb_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 = bdb_index_param( op->o_bd, mra->ma_desc, LDAP_FILTER_EQUALITY, + &db, &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 = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_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) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +ava_comp_candidates ( + Operation *op, + DB_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 ) { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, 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, + DB_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 ); + { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, 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: + { + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + BDB_IDL_ALL( bdb, ids ); + } + rc = LDAP_PROTOCOL_ERROR; + } + + return( rc ); +} +#endif + +static int +ext_candidates( + Operation *op, + DB_TXN *rtxn, + MatchingRuleAssertion *mra, + ID *ids, + ID *tmp, + ID *stack) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + +#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; + EntryInfo *ei; + + BDB_IDL_ZERO( ids ); + if ( mra->ma_rule == slap_schema.si_mr_distinguishedNameMatch ) { + ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( rc == LDAP_SUCCESS ) + bdb_idl_insert( ids, ei->bei_id ); + if ( ei ) + bdb_cache_entryinfo_unlock( ei ); + 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 ) { + struct berval pdn; + ei = NULL; + dnParent( &mra->ma_value, &pdn ); + bdb_cache_find_ndn( op, rtxn, &pdn, &ei ); + if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + while ( ei && ei->bei_id ) { + bdb_idl_insert( ids, ei->bei_id ); + ei = ei->bei_parent; + } + } + 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 + scope = LDAP_SCOPE_BASE; + if ( scope > LDAP_SCOPE_BASE ) { + ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei ); + if ( ei ) + bdb_cache_entryinfo_unlock( ei ); + if ( rc == LDAP_SUCCESS ) { + int sc = op->ors_scope; + op->ors_scope = scope; + rc = bdb_dn2idl( op, rtxn, &mra->ma_value, ei, ids, + stack ); + op->ors_scope = sc; + } + return 0; + } + } + } + + BDB_IDL_ALL( bdb, ids ); + return 0; +} + +static int +list_candidates( + Operation *op, + DB_TXN *rtxn, + Filter *flist, + int ftype, + ID *ids, + ID *tmp, + ID *save ) +{ + int rc = 0; + Filter *f; + + Debug( LDAP_DEBUG_FILTER, "=> bdb_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; + } + BDB_IDL_ZERO( save ); + rc = bdb_filter_candidates( op, rtxn, f, save, tmp, + save+BDB_IDL_UM_SIZE ); + + if ( rc != 0 ) { + if ( rc == DB_LOCK_DEADLOCK ) + return rc; + + if ( ftype == LDAP_FILTER_AND ) { + rc = 0; + continue; + } + break; + } + + + if ( ftype == LDAP_FILTER_AND ) { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_intersection( ids, save ); + } + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } else { + if ( f == flist ) { + BDB_IDL_CPY( ids, save ); + } else { + bdb_idl_union( ids, save ); + } + } + } + + if( rc == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_FILTER, + "<= bdb_list_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + + } else { + Debug( LDAP_DEBUG_FILTER, + "<= bdb_list_candidates: undefined rc=%d\n", + rc, 0, 0 ); + } + + return rc; +} + +static int +presence_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeDescription *desc, + ID *ids ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_presence_candidates (%s)\n", + desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + if( desc == slap_schema.si_ad_objectClass ) { + return 0; + } + + rc = bdb_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT, + &db, &mask, &prefix ); + + if( rc == LDAP_INAPPROPRIATE_MATCHING ) { + /* not indexed */ + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: (%s) not indexed\n", + desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_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, + "<= bdb_presence_candidates: (%s) no prefix\n", + desc->ad_cname.bv_val, 0, 0 ); + return -1; + } + + rc = bdb_key_read( op->o_bd, db, rtxn, &prefix, ids, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_presense_candidates: (%s) " + "key read failed (%d)\n", + desc->ad_cname.bv_val, rc, 0 ); + goto done; + } + + Debug(LDAP_DEBUG_TRACE, + "<= bdb_presence_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + +done: + return rc; +} + +static int +equality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_equality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + if ( ava->aa_desc == slap_schema.si_ad_entryDN ) { + EntryInfo *ei = NULL; + rc = bdb_cache_find_ndn( op, rtxn, &ava->aa_value, &ei ); + if ( rc == LDAP_SUCCESS ) { + /* exactly one ID can match */ + ids[0] = 1; + ids[1] = ei->bei_id; + } + if ( ei ) { + bdb_cache_entryinfo_unlock( ei ); + } + if ( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + } + return rc; + } + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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 = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= bdb_equality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + + +static int +approx_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_approx_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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 = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_approx_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_approx_candidates %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +substring_candidates( + Operation *op, + DB_TXN *rtxn, + SubstringsAssertion *sub, + ID *ids, + ID *tmp ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int i; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_substring_candidates (%s)\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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 = bdb_key_read( op->o_bd, db, rtxn, &keys[i], tmp, NULL, 0 ); + + if( rc == DB_NOTFOUND ) { + BDB_IDL_ZERO( ids ); + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (%s) " + "key read failed (%d)\n", + sub->sa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_substring_candidates: (%s) NULL\n", + sub->sa_desc->ad_cname.bv_val, 0, 0 ); + BDB_IDL_ZERO( ids ); + break; + } + + if ( i == 0 ) { + BDB_IDL_CPY( ids, tmp ); + } else { + bdb_idl_intersection( ids, tmp ); + } + + if( BDB_IDL_IS_ZERO( ids ) ) + break; + } + + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, "<= bdb_substring_candidates: %ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} + +static int +inequality_candidates( + Operation *op, + DB_TXN *rtxn, + AttributeAssertion *ava, + ID *ids, + ID *tmp, + int gtorlt ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + DB *db; + int rc; + slap_mask_t mask; + struct berval prefix = {0, NULL}; + struct berval *keys = NULL; + MatchingRule *mr; + DBC * cursor = NULL; + + Debug( LDAP_DEBUG_TRACE, "=> bdb_inequality_candidates (%s)\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + + BDB_IDL_ALL( bdb, ids ); + + rc = bdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY, + &db, &mask, &prefix ); + + if ( rc == LDAP_INAPPROPRIATE_MATCHING ) { + Debug( LDAP_DEBUG_ANY, + "<= bdb_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, + "<= bdb_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, + "<= bdb_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, + "<= bdb_inequality_candidates: (%s) no keys\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + return 0; + } + + BDB_IDL_ZERO( ids ); + while(1) { + rc = bdb_key_read( op->o_bd, db, rtxn, &keys[0], tmp, &cursor, gtorlt ); + + if( rc == DB_NOTFOUND ) { + rc = 0; + break; + } else if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s) " + "key read failed (%d)\n", + ava->aa_desc->ad_cname.bv_val, rc, 0 ); + break; + } + + if( BDB_IDL_IS_ZERO( tmp ) ) { + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: (%s) NULL\n", + ava->aa_desc->ad_cname.bv_val, 0, 0 ); + break; + } + + bdb_idl_union( ids, tmp ); + + if( op->ors_limit && op->ors_limit->lms_s_unchecked != -1 && + BDB_IDL_N( ids ) >= (unsigned) op->ors_limit->lms_s_unchecked ) { + cursor->c_close( cursor ); + break; + } + } + ber_bvarray_free_x( keys, op->o_tmpmemctx ); + + Debug( LDAP_DEBUG_TRACE, + "<= bdb_inequality_candidates: id=%ld, first=%ld, last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_IDL_LAST(ids) ); + return( rc ); +} diff --git a/servers/slapd/back-bdb/id2entry.c b/servers/slapd/back-bdb/id2entry.c new file mode 100644 index 0000000..d0e76ab --- /dev/null +++ b/servers/slapd/back-bdb/id2entry.c @@ -0,0 +1,446 @@ +/* id2entry.c - routines to deal with the id2entry database */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +static int bdb_id2entry_put( + BackendDB *be, + DB_TXN *tid, + Entry *e, + int flag ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key, data; + struct berval bv; + int rc; + ID nid; +#ifdef BDB_HIER + 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; +#endif + DBTzero( &key ); + + /* Store ID in BigEndian format */ + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( e->e_id, &nid ); + + rc = entry_encode( e, &bv ); +#ifdef BDB_HIER + e->e_name = odn; e->e_nname = ondn; +#endif + if( rc != LDAP_SUCCESS ) { + return -1; + } + + DBTzero( &data ); + bv2DBT( &bv, &data ); + + rc = db->put( db, tid, &key, &data, flag ); + + free( bv.bv_val ); + return rc; +} + +/* + * This routine adds (or updates) an entry on disk. + * The cache should be already be updated. + */ + + +int bdb_id2entry_add( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + return bdb_id2entry_put(be, tid, e, DB_NOOVERWRITE); +} + +int bdb_id2entry_update( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + return bdb_id2entry_put(be, tid, e, 0); +} + +int bdb_id2entry( + BackendDB *be, + DB_TXN *tid, + ID id, + Entry **e ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key, data; + DBC *cursor; + EntryHeader eh; + char buf[16]; + int rc = 0, off; + ID nid; + + *e = NULL; + + DBTzero( &key ); + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( id, &nid ); + + DBTzero( &data ); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + /* fetch it */ + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc ) return rc; + + /* Get the nattrs / nvals counts first */ + data.ulen = data.dlen = sizeof(buf); + data.data = buf; + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc ) goto finish; + + + eh.bv.bv_val = buf; + eh.bv.bv_len = data.size; + rc = entry_header( &eh ); + if ( rc ) goto finish; + + if ( eh.nvals ) { + /* Get the size */ + data.flags ^= DB_DBT_PARTIAL; + data.ulen = 0; + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc != DB_BUFFER_SMALL ) goto finish; + + /* Allocate a block and retrieve the data */ + off = eh.data - eh.bv.bv_val; + eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + data.size; + eh.bv.bv_val = ch_malloc( eh.bv.bv_len ); + eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval ); + data.data = eh.data; + data.ulen = data.size; + + /* skip past already parsed nattr/nvals */ + eh.data += off; + + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + } + +finish: + cursor->c_close( cursor ); + + if( rc != 0 ) { + return rc; + } + + if ( eh.nvals ) { +#ifdef SLAP_ZONE_ALLOC + rc = entry_decode(&eh, e, bdb->bi_cache.c_zctx); +#else + rc = entry_decode(&eh, e); +#endif + } else { + *e = entry_alloc(); + } + + if( rc == 0 ) { + (*e)->e_id = id; + } else { + /* only free on error. On success, the entry was + * decoded in place. + */ +#ifndef SLAP_ZONE_ALLOC + ch_free(eh.bv.bv_val); +#endif + } +#ifdef SLAP_ZONE_ALLOC + ch_free(eh.bv.bv_val); +#endif + + return rc; +} + +int bdb_id2entry_delete( + BackendDB *be, + DB_TXN *tid, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db = bdb->bi_id2entry->bdi_db; + DBT key; + int rc; + ID nid; + + DBTzero( &key ); + key.data = &nid; + key.size = sizeof(ID); + BDB_ID2DISK( e->e_id, &nid ); + + /* delete from database */ + rc = db->del( db, tid, &key, 0 ); + + return rc; +} + +int bdb_entry_return( + Entry *e +) +{ + /* Our entries are allocated in two blocks; the data comes from + * the db itself and the Entry structure and associated pointers + * are allocated in entry_decode. The db data pointer is saved + * in e_bv. + */ + if ( e->e_bv.bv_val ) { + /* See if the DNs were changed by modrdn */ + if( e->e_nname.bv_val < e->e_bv.bv_val || e->e_nname.bv_val > + e->e_bv.bv_val + e->e_bv.bv_len ) { + ch_free(e->e_name.bv_val); + ch_free(e->e_nname.bv_val); + } + e->e_name.bv_val = NULL; + e->e_nname.bv_val = NULL; + /* In tool mode the e_bv buffer is realloc'd, leave it alone */ + if( !(slapMode & SLAP_TOOL_MODE) ) { + free( e->e_bv.bv_val ); + } + BER_BVZERO( &e->e_bv ); + } + entry_free( e ); + return 0; +} + +int bdb_entry_release( + Operation *op, + Entry *e, + int rw ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *boi; + OpExtra *oex; + + /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE, + SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */ + + if ( slapMode & SLAP_SERVER_MODE ) { + /* If not in our cache, just free it */ + if ( !e->e_private ) { +#ifdef SLAP_ZONE_ALLOC + return bdb_entry_return( bdb, e, -1 ); +#else + return bdb_entry_return( e ); +#endif + } + /* free entry and reader or writer lock */ + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) break; + } + boi = (struct bdb_op_info *)oex; + + /* lock is freed with txn */ + if ( !boi || boi->boi_txn ) { + bdb_unlocked_cache_return_entry_rw( bdb, e, rw ); + } else { + struct bdb_lock_info *bli, *prev; + for ( prev=(struct bdb_lock_info *)&boi->boi_locks, + bli = boi->boi_locks; bli; prev=bli, bli=bli->bli_next ) { + if ( bli->bli_id == e->e_id ) { + bdb_cache_return_entry_rw( bdb, e, rw, &bli->bli_lock ); + prev->bli_next = bli->bli_next; + /* Cleanup, or let caller know we unlocked */ + if ( bli->bli_flag & BLI_DONTFREE ) + bli->bli_flag = 0; + else + op->o_tmpfree( bli, op->o_tmpmemctx ); + break; + } + } + if ( !boi->boi_locks ) { + LDAP_SLIST_REMOVE( &op->o_extra, &boi->boi_oe, OpExtra, oe_next ); + if ( !(boi->boi_flag & BOI_DONTFREE)) + op->o_tmpfree( boi, op->o_tmpmemctx ); + } + } + } else { +#ifdef SLAP_ZONE_ALLOC + int zseq = -1; + if (e->e_private != NULL) { + BEI(e)->bei_e = NULL; + zseq = BEI(e)->bei_zseq; + } +#else + if (e->e_private != NULL) + BEI(e)->bei_e = NULL; +#endif + e->e_private = NULL; +#ifdef SLAP_ZONE_ALLOC + bdb_entry_return ( bdb, e, zseq ); +#else + bdb_entry_return ( e ); +#endif + } + + return 0; +} + +/* return LDAP_SUCCESS IFF we can retrieve the specified entry. + */ +int bdb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *at, + int rw, + Entry **ent ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *boi = NULL; + DB_TXN *txn = NULL; + Entry *e = NULL; + EntryInfo *ei; + int rc; + const char *at_name = at ? at->ad_cname.bv_val : "(null)"; + + DB_LOCK lock; + + Debug( LDAP_DEBUG_ARGS, + "=> bdb_entry_get: ndn: \"%s\"\n", ndn->bv_val, 0, 0 ); + Debug( LDAP_DEBUG_ARGS, + "=> bdb_entry_get: oc: \"%s\", at: \"%s\"\n", + oc ? oc->soc_cname.bv_val : "(null)", at_name, 0); + + if( op ) { + OpExtra *oex; + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) break; + } + boi = (struct bdb_op_info *)oex; + if ( boi ) + txn = boi->boi_txn; + } + + if ( !txn ) { + rc = bdb_reader_get( op, bdb->bi_dbenv, &txn ); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + } + +dn2entry_retry: + /* can we find entry */ + rc = bdb_dn2entry( op, txn, ndn, &ei, 0, &lock ); + switch( rc ) { + case DB_NOTFOUND: + case 0: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + /* the txn must abort and retry */ + if ( txn ) { + if ( boi ) boi->boi_err = rc; + return LDAP_BUSY; + } + ldap_pvt_thread_yield(); + goto dn2entry_retry; + default: + if ( boi ) boi->boi_err = rc; + return (rc != LDAP_BUSY) ? LDAP_OTHER : LDAP_BUSY; + } + if (ei) e = ei->bei_e; + if (e == NULL) { + Debug( LDAP_DEBUG_ACL, + "=> bdb_entry_get: cannot find entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + return LDAP_NO_SUCH_OBJECT; + } + + Debug( LDAP_DEBUG_ACL, + "=> bdb_entry_get: found entry: \"%s\"\n", + ndn->bv_val, 0, 0 ); + + if ( oc && !is_entry_objectclass( e, oc, 0 )) { + Debug( LDAP_DEBUG_ACL, + "<= bdb_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, + "<= bdb_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 */ + bdb_cache_return_entry_rw(bdb, e, rw, &lock); + + } else { + if ( slapMode & SLAP_SERVER_MODE ) { + *ent = e; + /* big drag. we need a place to store a read lock so we can + * release it later?? If we're in a txn, nothing is needed + * here because the locks will go away with the txn. + */ + if ( op ) { + if ( !boi ) { + boi = op->o_tmpcalloc(1,sizeof(struct bdb_op_info),op->o_tmpmemctx); + boi->boi_oe.oe_key = bdb; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &boi->boi_oe, oe_next ); + } + if ( !boi->boi_txn ) { + struct bdb_lock_info *bli; + bli = op->o_tmpalloc( sizeof(struct bdb_lock_info), + op->o_tmpmemctx ); + bli->bli_next = boi->boi_locks; + bli->bli_id = e->e_id; + bli->bli_flag = 0; + bli->bli_lock = lock; + boi->boi_locks = bli; + } + } + } else { + *ent = entry_dup( e ); + bdb_cache_return_entry_rw(bdb, e, rw, &lock); + } + } + + Debug( LDAP_DEBUG_TRACE, + "bdb_entry_get: rc=%d\n", + rc, 0, 0 ); + return(rc); +} diff --git a/servers/slapd/back-bdb/idl.c b/servers/slapd/back-bdb/idl.c new file mode 100644 index 0000000..dce5d57 --- /dev/null +++ b/servers/slapd/back-bdb/idl.c @@ -0,0 +1,1570 @@ +/* idl.c - ldap id list handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.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) ) + +#define IDL_LRU_DELETE( bdb, e ) do { \ + if ( (e) == (bdb)->bi_idl_lru_head ) { \ + if ( (e)->idl_lru_next == (bdb)->bi_idl_lru_head ) { \ + (bdb)->bi_idl_lru_head = NULL; \ + } else { \ + (bdb)->bi_idl_lru_head = (e)->idl_lru_next; \ + } \ + } \ + if ( (e) == (bdb)->bi_idl_lru_tail ) { \ + if ( (e)->idl_lru_prev == (bdb)->bi_idl_lru_tail ) { \ + assert( (bdb)->bi_idl_lru_head == NULL ); \ + (bdb)->bi_idl_lru_tail = NULL; \ + } else { \ + (bdb)->bi_idl_lru_tail = (e)->idl_lru_prev; \ + } \ + } \ + (e)->idl_lru_next->idl_lru_prev = (e)->idl_lru_prev; \ + (e)->idl_lru_prev->idl_lru_next = (e)->idl_lru_next; \ +} while ( 0 ) + +static int +bdb_idl_entry_cmp( const void *v_idl1, const void *v_idl2 ) +{ + const bdb_idl_cache_entry_t *idl1 = v_idl1, *idl2 = v_idl2; + int rc; + + if ((rc = SLAP_PTRCMP( idl1->db, idl2->db ))) return rc; + if ((rc = idl1->kstr.bv_len - idl2->kstr.bv_len )) return rc; + return ( memcmp ( idl1->kstr.bv_val, idl2->kstr.bv_val , idl1->kstr.bv_len ) ); +} + +#if IDL_DEBUG > 0 +static void idl_check( ID *ids ) +{ + if( BDB_IDL_IS_RANGE( ids ) ) { + assert( BDB_IDL_RANGE_FIRST(ids) <= BDB_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( BDB_IDL_IS_RANGE( ids ) ) { + Debug( LDAP_DEBUG_ANY, + "IDL: range ( %ld - %ld )\n", + (long) BDB_IDL_RANGE_FIRST( ids ), + (long) BDB_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 bdb_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 = 1; + int val = 0; + unsigned n = ids[0]; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = IDL_CMP( id, ids[cursor] ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; + +#else + /* (reverse) linear search */ + int i; + +#if IDL_DEBUG > 0 + idl_check( ids ); +#endif + + for( i=ids[0]; i; i-- ) { + if( id > ids[i] ) { + break; + } + } + + return i+1; +#endif +} + +int bdb_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 (BDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= BDB_IDL_RANGE_FIRST(ids) && id <= BDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < BDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > BDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + + x = bdb_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] >= BDB_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; +} + +int bdb_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 (BDB_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 = bdb_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 * +bdb_show_key( + DBT *key, + char *buf ) +{ + if ( key->size == 4 /* LUTIL_HASH_BYTES */ ) { + unsigned char *c = key->data; + sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] ); + return buf; + } else { + return key->data; + } +} + +/* Find a db/key pair in the IDL cache. If ids is non-NULL, + * copy the cached IDL into it, otherwise just return the status. + */ +int +bdb_idl_cache_get( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids ) +{ + bdb_idl_cache_entry_t idl_tmp; + bdb_idl_cache_entry_t *matched_idl_entry; + int rc = LDAP_NO_SUCH_OBJECT; + + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_rlock( &bdb->bi_idl_tree_rwlock ); + matched_idl_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( matched_idl_entry != NULL ) { + if ( matched_idl_entry->idl && ids ) + BDB_IDL_CPY( ids, matched_idl_entry->idl ); + matched_idl_entry->idl_flags |= CACHE_ENTRY_REFERENCED; + if ( matched_idl_entry->idl ) + rc = LDAP_SUCCESS; + else + rc = DB_NOTFOUND; + } + ldap_pvt_thread_rdwr_runlock( &bdb->bi_idl_tree_rwlock ); + + return rc; +} + +void +bdb_idl_cache_put( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids, + int rc ) +{ + bdb_idl_cache_entry_t idl_tmp; + bdb_idl_cache_entry_t *ee, *eprev; + + if ( rc == DB_NOTFOUND || BDB_IDL_IS_ZERO( ids )) + return; + + DBT2bv( key, &idl_tmp.kstr ); + + ee = (bdb_idl_cache_entry_t *) ch_malloc( + sizeof( bdb_idl_cache_entry_t ) ); + ee->db = db; + ee->idl = (ID*) ch_malloc( BDB_IDL_SIZEOF ( ids ) ); + BDB_IDL_CPY( ee->idl, ids ); + + ee->idl_lru_prev = NULL; + ee->idl_lru_next = NULL; + ee->idl_flags = 0; + ber_dupbv( &ee->kstr, &idl_tmp.kstr ); + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + if ( avl_insert( &bdb->bi_idl_tree, (caddr_t) ee, + bdb_idl_entry_cmp, avl_dup_error )) + { + ch_free( ee->kstr.bv_val ); + ch_free( ee->idl ); + ch_free( ee ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); + return; + } + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + /* LRU_ADD */ + if ( bdb->bi_idl_lru_head ) { + assert( bdb->bi_idl_lru_tail != NULL ); + assert( bdb->bi_idl_lru_head->idl_lru_prev != NULL ); + assert( bdb->bi_idl_lru_head->idl_lru_next != NULL ); + + ee->idl_lru_next = bdb->bi_idl_lru_head; + ee->idl_lru_prev = bdb->bi_idl_lru_head->idl_lru_prev; + bdb->bi_idl_lru_head->idl_lru_prev->idl_lru_next = ee; + bdb->bi_idl_lru_head->idl_lru_prev = ee; + } else { + ee->idl_lru_next = ee->idl_lru_prev = ee; + bdb->bi_idl_lru_tail = ee; + } + bdb->bi_idl_lru_head = ee; + + if ( bdb->bi_idl_cache_size >= bdb->bi_idl_cache_max_size ) { + int i; + eprev = bdb->bi_idl_lru_tail; + for ( i = 0; (ee = eprev) != NULL && i < 10; i++ ) { + eprev = ee->idl_lru_prev; + if ( eprev == ee ) { + eprev = NULL; + } + if ( ee->idl_flags & CACHE_ENTRY_REFERENCED ) { + ee->idl_flags ^= CACHE_ENTRY_REFERENCED; + continue; + } + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) ee, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_put: " + "AVL delete failed\n", + 0, 0, 0 ); + } + IDL_LRU_DELETE( bdb, ee ); + i++; + --bdb->bi_idl_cache_size; + ch_free( ee->kstr.bv_val ); + ch_free( ee->idl ); + ch_free( ee ); + } + bdb->bi_idl_lru_tail = eprev; + assert( bdb->bi_idl_lru_tail != NULL + || bdb->bi_idl_lru_head == NULL ); + } + bdb->bi_idl_cache_size++; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_del( + struct bdb_info *bdb, + DB *db, + DBT *key ) +{ + bdb_idl_cache_entry_t *matched_idl_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + matched_idl_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( matched_idl_entry != NULL ) { + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) matched_idl_entry, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_del: " + "AVL delete failed\n", + 0, 0, 0 ); + } + --bdb->bi_idl_cache_size; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + IDL_LRU_DELETE( bdb, matched_idl_entry ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + free( matched_idl_entry->kstr.bv_val ); + if ( matched_idl_entry->idl ) + free( matched_idl_entry->idl ); + free( matched_idl_entry ); + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_add_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ) +{ + bdb_idl_cache_entry_t *cache_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + cache_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( cache_entry != NULL ) { + if ( !BDB_IDL_IS_RANGE( cache_entry->idl ) && + cache_entry->idl[0] < BDB_IDL_DB_MAX ) { + size_t s = BDB_IDL_SIZEOF( cache_entry->idl ) + sizeof(ID); + cache_entry->idl = ch_realloc( cache_entry->idl, s ); + } + bdb_idl_insert( cache_entry->idl, id ); + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +void +bdb_idl_cache_del_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ) +{ + bdb_idl_cache_entry_t *cache_entry, idl_tmp; + DBT2bv( key, &idl_tmp.kstr ); + idl_tmp.db = db; + ldap_pvt_thread_rdwr_wlock( &bdb->bi_idl_tree_rwlock ); + cache_entry = avl_find( bdb->bi_idl_tree, &idl_tmp, + bdb_idl_entry_cmp ); + if ( cache_entry != NULL ) { + bdb_idl_delete( cache_entry->idl, id ); + if ( cache_entry->idl[0] == 0 ) { + if ( avl_delete( &bdb->bi_idl_tree, (caddr_t) cache_entry, + bdb_idl_entry_cmp ) == NULL ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_cache_del: " + "AVL delete failed\n", + 0, 0, 0 ); + } + --bdb->bi_idl_cache_size; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + IDL_LRU_DELETE( bdb, cache_entry ); + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + free( cache_entry->kstr.bv_val ); + free( cache_entry->idl ); + free( cache_entry ); + } + } + ldap_pvt_thread_rdwr_wunlock( &bdb->bi_idl_tree_rwlock ); +} + +int +bdb_idl_fetch_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID *ids, + DBC **saved_cursor, + int get_flag ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data, key2, *kptr; + DBC *cursor; + ID *i; + void *ptr; + size_t len; + int rc2; + int flags = bdb->bi_db_opflags | DB_MULTIPLE; + int opflag; + + /* If using BerkeleyDB 4.0, the buf must be large enough to + * grab the entire IDL in one get(), otherwise BDB will leak + * resources on subsequent get's. We can safely call get() + * twice - once for the data, and once to get the DB_NOTFOUND + * result meaning there's no more data. See ITS#2040 for details. + * This bug is fixed in BDB 4.1 so a smaller buffer will work if + * stack space is too limited. + * + * configure now requires Berkeley DB 4.1. + */ +#if DB_VERSION_FULL < 0x04010000 +# define BDB_ENOUGH 5 +#else + /* We sometimes test with tiny IDLs, and BDB always wants buffers + * that are at least one page in size. + */ +# if BDB_IDL_DB_SIZE < 4096 +# define BDB_ENOUGH 2048 +# else +# define BDB_ENOUGH 1 +# endif +#endif + ID buf[BDB_IDL_DB_SIZE*BDB_ENOUGH]; + + char keybuf[16]; + + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_fetch_key: %s\n", + bdb_show_key( key, keybuf ), 0, 0 ); + + assert( ids != NULL ); + + if ( saved_cursor && *saved_cursor ) { + opflag = DB_NEXT; + } else if ( get_flag == LDAP_FILTER_GE ) { + opflag = DB_SET_RANGE; + } else if ( get_flag == LDAP_FILTER_LE ) { + opflag = DB_FIRST; + } else { + opflag = DB_SET; + } + + /* only non-range lookups can use the IDL cache */ + if ( bdb->bi_idl_cache_size && opflag == DB_SET ) { + rc = bdb_idl_cache_get( bdb, db, key, ids ); + if ( rc != LDAP_NO_SUCH_OBJECT ) return rc; + } + + DBTzero( &data ); + + data.data = buf; + data.ulen = sizeof(buf); + data.flags = DB_DBT_USERMEM; + + /* If we're not reusing an existing cursor, get a new one */ + if( opflag != DB_NEXT ) { + rc = db->cursor( db, txn, &cursor, bdb->bi_db_opflags ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "cursor failed: %s (%d)\n", db_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 ) { + DBTzero( &key2 ); + key2.flags = DB_DBT_USERMEM; + key2.ulen = sizeof(keybuf); + key2.data = keybuf; + key2.size = key->size; + AC_MEMCPY( keybuf, key->data, key->size ); + kptr = &key2; + } else { + kptr = key; + } + len = key->size; + rc = cursor->c_get( cursor, kptr, &data, flags | opflag ); + + /* skip presence key on range inequality lookups */ + while (rc == 0 && kptr->size != len) { + rc = cursor->c_get( cursor, kptr, &data, flags | DB_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->data, + key->data, key->size ) > 0 ) { + rc = DB_NOTFOUND; + } + if (rc == 0) { + i = ids; + while (rc == 0) { + u_int8_t *j; + + DB_MULTIPLE_INIT( ptr, &data ); + while (ptr) { + DB_MULTIPLE_NEXT(ptr, &data, j, len); + if (j) { + ++i; + BDB_DISK2ID( j, i ); + } + } + rc = cursor->c_get( cursor, key, &data, flags | DB_NEXT_DUP ); + } + if ( rc == DB_NOTFOUND ) rc = 0; + ids[0] = i - ids; + /* On disk, a range is denoted by 0 in the first element */ + if (ids[1] == 0) { + if (ids[0] != BDB_IDL_RANGE_SIZE) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "range size mismatch: expected %d, got %ld\n", + BDB_IDL_RANGE_SIZE, ids[0], 0 ); + cursor->c_close( cursor ); + return -1; + } + BDB_IDL_RANGE( ids, ids[2], ids[3] ); + } + data.size = BDB_IDL_SIZEOF(ids); + } + + if ( saved_cursor && rc == 0 ) { + if ( !*saved_cursor ) + *saved_cursor = cursor; + rc2 = 0; + } + else + rc2 = cursor->c_close( cursor ); + if (rc2) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "close failed: %s (%d)\n", db_strerror(rc2), rc2, 0 ); + return rc2; + } + + if( rc == DB_NOTFOUND ) { + return rc; + + } else if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "get failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + return rc; + + } else if ( data.size == 0 || data.size % sizeof( ID ) ) { + /* size not multiple of ID size */ + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "odd size: expected %ld multiple, got %ld\n", + (long) sizeof( ID ), (long) data.size, 0 ); + return -1; + + } else if ( data.size != BDB_IDL_SIZEOF(ids) ) { + /* size mismatch */ + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_fetch_key: " + "get size mismatch: expected %ld, got %ld\n", + (long) ((1 + ids[0]) * sizeof( ID )), (long) data.size, 0 ); + return -1; + } + + if ( bdb->bi_idl_cache_max_size ) { + bdb_idl_cache_put( bdb, db, key, ids, rc ); + } + + return rc; +} + + +int +bdb_idl_insert_key( + BackendDB *be, + DB *db, + DB_TXN *tid, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data; + DBC *cursor; + ID lo, hi, nlo, nhi, nid; + char *err; + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_insert_key: %lx %s\n", + (long) id, bdb_show_key( key, buf ), 0 ); + } + + assert( id != NOID ); + + DBTzero( &data ); + data.size = sizeof( ID ); + data.ulen = data.size; + data.flags = DB_DBT_USERMEM; + + BDB_ID2DISK( id, &nid ); + + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "cursor failed: %s (%d)\n", db_strerror(rc), rc, 0 ); + return rc; + } + data.data = &nlo; + /* Fetch the first data item for this key, to see if it + * exists and if it's a range. + */ + rc = cursor->c_get( cursor, key, &data, DB_SET ); + err = "c_get"; + if ( rc == 0 ) { + if ( nlo != 0 ) { + /* not a range, count the number of items */ + db_recno_t count; + rc = cursor->c_count( cursor, &count, 0 ); + if ( rc != 0 ) { + err = "c_count"; + goto fail; + } + if ( count >= BDB_IDL_DB_MAX ) { + /* No room, convert to a range */ + DBT key2 = *key; + db_recno_t i; + + key2.dlen = key2.ulen; + key2.flags |= DB_DBT_PARTIAL; + + BDB_DISK2ID( &nlo, &lo ); + data.data = &nhi; + + rc = cursor->c_get( cursor, &key2, &data, DB_NEXT_NODUP ); + if ( rc != 0 && rc != DB_NOTFOUND ) { + err = "c_get next_nodup"; + goto fail; + } + if ( rc == DB_NOTFOUND ) { + rc = cursor->c_get( cursor, key, &data, DB_LAST ); + if ( rc != 0 ) { + err = "c_get last"; + goto fail; + } + } else { + rc = cursor->c_get( cursor, key, &data, DB_PREV ); + if ( rc != 0 ) { + err = "c_get prev"; + goto fail; + } + } + BDB_DISK2ID( &nhi, &hi ); + /* Update hi/lo if needed, then delete all the items + * between lo and hi + */ + if ( id < lo ) { + lo = id; + nlo = nid; + } else if ( id > hi ) { + hi = id; + nhi = nid; + } + data.data = &nid; + /* Don't fetch anything, just position cursor */ + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + rc = cursor->c_get( cursor, key, &data, DB_SET ); + if ( rc != 0 ) { + err = "c_get 2"; + goto fail; + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del range1"; + goto fail; + } + /* Delete all the records */ + for ( i=1; i<count; i++ ) { + rc = cursor->c_get( cursor, &key2, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get next_dup"; + goto fail; + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del range"; + goto fail; + } + } + /* Store the range marker */ + data.size = data.ulen = sizeof(ID); + data.flags = DB_DBT_USERMEM; + nid = 0; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put range"; + goto fail; + } + nid = nlo; + rc = cursor->c_put( cursor, key, &data, DB_KEYLAST ); + if ( rc != 0 ) { + err = "c_put lo"; + goto fail; + } + nid = nhi; + rc = cursor->c_put( cursor, key, &data, DB_KEYLAST ); + if ( rc != 0 ) { + err = "c_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 + */ + hi = id; + data.data = &nlo; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + BDB_DISK2ID( &nlo, &lo ); + if ( id > lo ) { + data.data = &nhi; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + BDB_DISK2ID( &nhi, &hi ); + } + if ( id < lo || id > hi ) { + /* Delete the current lo/hi */ + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del"; + goto fail; + } + data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } else if ( rc == DB_NOTFOUND ) { +put1: data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_NODUPDATA ); + /* Don't worry if it's already there */ + if ( rc != 0 && rc != DB_KEYEXIST ) { + err = "c_put id"; + goto fail; + } + } else { + /* initial c_get failed, nothing was done */ +fail: + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "%s failed: %s (%d)\n", err, db_strerror(rc), rc ); + cursor->c_close( cursor ); + return rc; + } + /* If key was added (didn't already exist) and using IDL cache, + * update key in IDL cache. + */ + if ( !rc && bdb->bi_idl_cache_max_size ) { + bdb_idl_cache_add_id( bdb, db, key, id ); + } + rc = cursor->c_close( cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_insert_key: " + "c_close failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + return rc; +} + +int +bdb_idl_delete_key( + BackendDB *be, + DB *db, + DB_TXN *tid, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + DBT data; + DBC *cursor; + ID lo, hi, tmp, nid, nlo, nhi; + char *err; + + { + char buf[16]; + Debug( LDAP_DEBUG_ARGS, + "bdb_idl_delete_key: %lx %s\n", + (long) id, bdb_show_key( key, buf ), 0 ); + } + assert( id != NOID ); + + if ( bdb->bi_idl_cache_size ) { + bdb_idl_cache_del( bdb, db, key ); + } + + BDB_ID2DISK( id, &nid ); + + DBTzero( &data ); + data.data = &tmp; + data.size = sizeof( id ); + data.ulen = data.size; + data.flags = DB_DBT_USERMEM; + + rc = db->cursor( db, tid, &cursor, bdb->bi_db_opflags ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_delete_key: " + "cursor failed: %s (%d)\n", db_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 = cursor->c_get( cursor, key, &data, DB_SET ); + err = "c_get"; + if ( rc == 0 ) { + if ( tmp != 0 ) { + /* Not a range, just delete it */ + if (tmp != nid) { + /* position to correct item */ + tmp = nid; + rc = cursor->c_get( cursor, key, &data, DB_GET_BOTH ); + if ( rc != 0 ) { + err = "c_get id"; + goto fail; + } + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del id"; + goto fail; + } + } else { + /* It's a range, see if we need to rewrite + * the boundaries + */ + data.data = &nlo; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get lo"; + goto fail; + } + BDB_DISK2ID( &nlo, &lo ); + data.data = &nhi; + rc = cursor->c_get( cursor, key, &data, DB_NEXT_DUP ); + if ( rc != 0 ) { + err = "c_get hi"; + goto fail; + } + BDB_DISK2ID( &nhi, &hi ); + if ( id == lo || id == hi ) { + if ( id == lo ) { + id++; + lo = id; + } else if ( id == hi ) { + id--; + hi = id; + } + if ( lo >= hi ) { + /* The range has collapsed... */ + rc = db->del( db, tid, key, 0 ); + if ( rc != 0 ) { + err = "del"; + goto fail; + } + } else { + if ( id == lo ) { + /* reposition on lo slot */ + data.data = &nlo; + cursor->c_get( cursor, key, &data, DB_PREV ); + } + rc = cursor->c_del( cursor, 0 ); + if ( rc != 0 ) { + err = "c_del"; + goto fail; + } + } + if ( lo <= hi ) { + BDB_ID2DISK( id, &nid ); + data.data = &nid; + rc = cursor->c_put( cursor, key, &data, DB_KEYFIRST ); + if ( rc != 0 ) { + err = "c_put lo/hi"; + goto fail; + } + } + } + } + } else { + /* initial c_get failed, nothing was done */ +fail: + if ( rc != DB_NOTFOUND ) { + Debug( LDAP_DEBUG_ANY, "=> bdb_idl_delete_key: " + "%s failed: %s (%d)\n", err, db_strerror(rc), rc ); + } + cursor->c_close( cursor ); + return rc; + } + rc = cursor->c_close( cursor ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> bdb_idl_delete_key: c_close failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + + return rc; +} + + +/* + * idl_intersection - return a = a intersection b + */ +int +bdb_idl_intersection( + ID *a, + ID *b ) +{ + ID ida, idb; + ID idmax, idmin; + ID cursora = 0, cursorb = 0, cursorc; + int swap = 0; + + if ( BDB_IDL_IS_ZERO( a ) || BDB_IDL_IS_ZERO( b ) ) { + a[0] = 0; + return 0; + } + + idmin = IDL_MAX( BDB_IDL_FIRST(a), BDB_IDL_FIRST(b) ); + idmax = IDL_MIN( BDB_IDL_LAST(a), BDB_IDL_LAST(b) ); + if ( idmin > idmax ) { + a[0] = 0; + return 0; + } else if ( idmin == idmax ) { + a[0] = 1; + a[1] = idmin; + return 0; + } + + if ( BDB_IDL_IS_RANGE( a ) ) { + if ( BDB_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 ( BDB_IDL_IS_RANGE( b ) + && BDB_IDL_RANGE_FIRST( b ) <= BDB_IDL_FIRST( a ) + && BDB_IDL_RANGE_LAST( b ) >= BDB_IDL_LLAST( a ) ) { + goto done; + } + + /* Fine, do the intersection one element at a time. + * First advance to idmin in both IDLs. + */ + cursora = cursorb = idmin; + ida = bdb_idl_first( a, &cursora ); + idb = bdb_idl_first( b, &cursorb ); + cursorc = 0; + + while( ida <= idmax || idb <= idmax ) { + if( ida == idb ) { + a[++cursorc] = ida; + ida = bdb_idl_next( a, &cursora ); + idb = bdb_idl_next( b, &cursorb ); + } else if ( ida < idb ) { + ida = bdb_idl_next( a, &cursora ); + } else { + idb = bdb_idl_next( b, &cursorb ); + } + } + a[0] = cursorc; +done: + if (swap) + BDB_IDL_CPY( b, a ); + + return 0; +} + + +/* + * idl_union - return a = a union b + */ +int +bdb_idl_union( + ID *a, + ID *b ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0, cursorc; + + if ( BDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( BDB_IDL_IS_ZERO( a ) ) { + BDB_IDL_CPY( a, b ); + return 0; + } + + if ( BDB_IDL_IS_RANGE( a ) || BDB_IDL_IS_RANGE(b) ) { +over: ida = IDL_MIN( BDB_IDL_FIRST(a), BDB_IDL_FIRST(b) ); + idb = IDL_MAX( BDB_IDL_LAST(a), BDB_IDL_LAST(b) ); + a[0] = NOID; + a[1] = ida; + a[2] = idb; + return 0; + } + + ida = bdb_idl_first( a, &cursora ); + idb = bdb_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 > BDB_IDL_UM_MAX ) { + goto over; + } + b[cursorc] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else { + if ( ida == idb ) + ida = bdb_idl_next( a, &cursora ); + idb = bdb_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 +/* + * bdb_idl_notin - return a intersection ~b (or a minus b) + */ +int +bdb_idl_notin( + ID *a, + ID *b, + ID *ids ) +{ + ID ida, idb; + ID cursora = 0, cursorb = 0; + + if( BDB_IDL_IS_ZERO( a ) || + BDB_IDL_IS_ZERO( b ) || + BDB_IDL_IS_RANGE( b ) ) + { + BDB_IDL_CPY( ids, a ); + return 0; + } + + if( BDB_IDL_IS_RANGE( a ) ) { + BDB_IDL_CPY( ids, a ); + return 0; + } + + ida = bdb_idl_first( a, &cursora ), + idb = bdb_idl_first( b, &cursorb ); + + ids[0] = 0; + + while( ida != NOID ) { + if ( idb == NOID ) { + /* we could shortcut this */ + ids[++ids[0]] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else if ( ida < idb ) { + ids[++ids[0]] = ida; + ida = bdb_idl_next( a, &cursora ); + + } else if ( ida > idb ) { + idb = bdb_idl_next( b, &cursorb ); + + } else { + ida = bdb_idl_next( a, &cursora ); + idb = bdb_idl_next( b, &cursorb ); + } + } + + return 0; +} +#endif + +ID bdb_idl_first( ID *ids, ID *cursor ) +{ + ID pos; + + if ( ids[0] == 0 ) { + *cursor = NOID; + return NOID; + } + + if ( BDB_IDL_IS_RANGE( ids ) ) { + if( *cursor < ids[1] ) { + *cursor = ids[1]; + } + return *cursor; + } + + if ( *cursor == 0 ) + pos = 1; + else + pos = bdb_idl_search( ids, *cursor ); + + if( pos > ids[0] ) { + return NOID; + } + + *cursor = pos; + return ids[pos]; +} + +ID bdb_idl_next( ID *ids, ID *cursor ) +{ + if ( BDB_IDL_IS_RANGE( ids ) ) { + if( ids[2] < ++(*cursor) ) { + return NOID; + } + return *cursor; + } + + if ( ++(*cursor) <= ids[0] ) { + return ids[*cursor]; + } + + return NOID; +} + +#ifdef BDB_HIER + +/* 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 bdb_idl_append_one( ID *ids, ID id ) +{ + if (BDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= BDB_IDL_RANGE_FIRST(ids) && id <= BDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < BDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > BDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + if ( ids[0] ) { + ID tmp; + + if (id < ids[1]) { + tmp = ids[1]; + ids[1] = id; + id = tmp; + } + if ( ids[0] > 1 && id < ids[ids[0]] ) { + tmp = ids[ids[0]]; + ids[ids[0]] = id; + id = tmp; + } + } + ids[0]++; + if ( ids[0] >= BDB_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 bdb_idl_append( ID *a, ID *b ) +{ + ID ida, idb, tmp, swap = 0; + + if ( BDB_IDL_IS_ZERO( b ) ) { + return 0; + } + + if ( BDB_IDL_IS_ZERO( a ) ) { + BDB_IDL_CPY( a, b ); + return 0; + } + + if ( b[0] == 1 ) { + return bdb_idl_append_one( a, BDB_IDL_FIRST( b )); + } + + ida = BDB_IDL_LAST( a ); + idb = BDB_IDL_LAST( b ); + if ( BDB_IDL_IS_RANGE( a ) || BDB_IDL_IS_RANGE(b) || + a[0] + b[0] >= BDB_IDL_UM_MAX ) { + a[2] = IDL_MAX( ida, idb ); + a[1] = IDL_MIN( a[1], b[1] ); + a[0] = NOID; + return 0; + } + + if ( 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; + + { + 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 +bdb_idl_sort( ID *ids, ID *tmp ) +{ + int *istack = (int *)tmp; + int i,j,k,l,ir,jstack; + ID a, itmp; + + if ( BDB_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 ebackes@symas.com and hyc@symas.com + * + * This code is O(n) but has a relatively high constant factor. For lists + * up to ~50 Quicksort is slightly faster; up to ~100 they are even. + * Much faster than quicksort for lists longer than ~100. Insertion + * sort is actually superior for lists <50. + */ + +#define BUCKETS (1<<8) +#define SMALL 50 + +void +bdb_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 ( BDB_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 */ + +#endif /* BDB_HIER */ diff --git a/servers/slapd/back-bdb/idl.h b/servers/slapd/back-bdb/idl.h new file mode 100644 index 0000000..1909054 --- /dev/null +++ b/servers/slapd/back-bdb/idl.h @@ -0,0 +1,75 @@ +/* idl.h - ldap bdb back-end ID list header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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 _BDB_IDL_H_ +#define _BDB_IDL_H_ + +/* IDL sizes - likely should be even bigger + * limiting factors: sizeof(ID), thread stack size + */ +#define BDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +#define BDB_IDL_DB_SIZE (1<<BDB_IDL_LOGN) +#define BDB_IDL_UM_SIZE (1<<(BDB_IDL_LOGN+1)) +#define BDB_IDL_UM_SIZEOF (BDB_IDL_UM_SIZE * sizeof(ID)) + +#define BDB_IDL_DB_MAX (BDB_IDL_DB_SIZE-1) + +#define BDB_IDL_UM_MAX (BDB_IDL_UM_SIZE-1) + +#define BDB_IDL_IS_RANGE(ids) ((ids)[0] == NOID) +#define BDB_IDL_RANGE_SIZE (3) +#define BDB_IDL_RANGE_SIZEOF (BDB_IDL_RANGE_SIZE * sizeof(ID)) +#define BDB_IDL_SIZEOF(ids) ((BDB_IDL_IS_RANGE(ids) \ + ? BDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID)) + +#define BDB_IDL_RANGE_FIRST(ids) ((ids)[1]) +#define BDB_IDL_RANGE_LAST(ids) ((ids)[2]) + +#define BDB_IDL_RANGE( ids, f, l ) \ + do { \ + (ids)[0] = NOID; \ + (ids)[1] = (f); \ + (ids)[2] = (l); \ + } while(0) + +#define BDB_IDL_ZERO(ids) \ + do { \ + (ids)[0] = 0; \ + (ids)[1] = 0; \ + (ids)[2] = 0; \ + } while(0) + +#define BDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 ) +#define BDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \ + && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] ) + +#define BDB_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, BDB_IDL_SIZEOF( src ) )) + +#define BDB_IDL_ID( bdb, ids, id ) BDB_IDL_RANGE( ids, id, ((bdb)->bi_lastid) ) +#define BDB_IDL_ALL( bdb, ids ) BDB_IDL_RANGE( ids, 1, ((bdb)->bi_lastid) ) + +#define BDB_IDL_FIRST( ids ) ( (ids)[1] ) +#define BDB_IDL_LLAST( ids ) ( (ids)[(ids)[0]] ) +#define BDB_IDL_LAST( ids ) ( BDB_IDL_IS_RANGE(ids) \ + ? (ids)[2] : (ids)[(ids)[0]] ) + +#define BDB_IDL_N( ids ) ( BDB_IDL_IS_RANGE(ids) \ + ? ((ids)[2]-(ids)[1])+1 : (ids)[0] ) + +LDAP_BEGIN_DECL +LDAP_END_DECL + +#endif diff --git a/servers/slapd/back-bdb/index.c b/servers/slapd/back-bdb/index.c new file mode 100644 index 0000000..9666e46 --- /dev/null +++ b/servers/slapd/back-bdb/index.c @@ -0,0 +1,574 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "lutil_hash.h" + +static char presence_keyval[] = {0,0}; +static struct berval presence_key = BER_BVC(presence_keyval); + +AttrInfo *bdb_index_mask( + Backend *be, + AttributeDescription *desc, + struct berval *atname ) +{ + AttributeType *at; + AttrInfo *ai = bdb_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 = bdb_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 = bdb_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 bdb_index_param( + Backend *be, + AttributeDescription *desc, + int ftype, + DB **dbp, + slap_mask_t *maskp, + struct berval *prefixp ) +{ + AttrInfo *ai; + int rc; + slap_mask_t mask, type = 0; + DB *db; + + ai = bdb_index_mask( be, desc, prefixp ); + + if ( !ai ) { +#ifdef BDB_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; + } + bdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* BDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + } + mask = ai->ai_indexmask; + + rc = bdb_db_cache( be, prefixp, &db ); + + if( rc != LDAP_SUCCESS ) { + return rc; + } + + 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 BDB_MONITOR_IDX + bdb_monitor_idx_add( be->be_private, desc, type ); +#endif /* BDB_MONITOR_IDX */ + + return LDAP_INAPPROPRIATE_MATCHING; + +done: + *dbp = db; + *maskp = mask; + return LDAP_SUCCESS; +} + +static int indexer( + Operation *op, + DB_TXN *txn, + AttributeDescription *ad, + struct berval *atname, + BerVarray vals, + ID id, + int opid, + slap_mask_t mask ) +{ + int rc, i; + DB *db; + struct berval *keys; + + assert( mask != 0 ); + + rc = bdb_db_cache( op->o_bd, atname, &db ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "bdb_index_read: Could not open DB %s\n", + atname->bv_val, 0, 0 ); + return LDAP_OTHER; + } + + if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) { + rc = bdb_key_change( op->o_bd, db, txn, &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 = bdb_key_change( op->o_bd, db, txn, &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 = bdb_key_change( op->o_bd, db, txn, &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 = bdb_key_change( op->o_bd, db, txn, &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: + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + break; + /* Anything else is bad news */ + default: + rc = LDAP_OTHER; + } + return rc; +} + +static int index_at_values( + Operation *op, + DB_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 == BDB_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 = bdb_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, 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 == BDB_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, 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 = bdb_attr_mask( op->o_bd->be_private, desc ); + + if( ai ) { + if ( opid == BDB_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, desc, &desc->ad_cname, + vals, id, ixop, mask ); + + if( rc ) { + return rc; + } + } + } + } + } + + return LDAP_SUCCESS; +} + +int bdb_index_values( + Operation *op, + DB_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 +bdb_index_recset( + struct bdb_info *bdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir ) +{ + int rc, slot; + AttrList *al; + + if( type->sat_sup ) { + /* recurse */ + rc = bdb_index_recset( bdb, 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 = bdb_attr_slot( bdb, type->sat_ad, NULL ); + if ( slot >= 0 ) { + ir[slot].ai = bdb->bi_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 = bdb_attr_slot( bdb, desc, NULL ); + if ( slot >= 0 ) { + ir[slot].ai = bdb->bi_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 bdb_index_recrun( + Operation *op, + struct bdb_info *bdb, + 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<bdb->bi_nattrs; i+=slap_tool_thread_max-1) { + ir = ir0 + i; + if ( !ir->ai ) continue; + while (( al = ir->attrs )) { + ir->attrs = al->next; + rc = indexer( op, NULL, 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 +bdb_index_entry( + Operation *op, + DB_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 = bdb_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 = bdb_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-bdb/init.c b/servers/slapd/back-bdb/init.c new file mode 100644 index 0000000..86a1a97 --- /dev/null +++ b/servers/slapd/back-bdb/init.c @@ -0,0 +1,874 @@ +/* init.c - initialize bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include <lutil.h> +#include <ldap_rq.h> +#include "alock.h" +#include "config.h" + +static const struct bdbi_database { + char *file; + struct berval name; + int type; + int flags; +} bdbi_databases[] = { + { "id2entry" BDB_SUFFIX, BER_BVC("id2entry"), DB_BTREE, 0 }, + { "dn2id" BDB_SUFFIX, BER_BVC("dn2id"), DB_BTREE, 0 }, + { NULL, BER_BVNULL, 0, 0 } +}; + +typedef void * db_malloc(size_t); +typedef void * db_realloc(void *, size_t); + +#define bdb_db_init BDB_SYMBOL(db_init) +#define bdb_db_open BDB_SYMBOL(db_open) +#define bdb_db_close BDB_SYMBOL(db_close) + +static int +bdb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct bdb_info *bdb; + int rc; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_db_init) ": Initializing " BDB_UCTYPE " database\n", + 0, 0, 0 ); + + /* allocate backend-database-specific stuff */ + bdb = (struct bdb_info *) ch_calloc( 1, sizeof(struct bdb_info) ); + + /* DBEnv parameters */ + bdb->bi_dbenv_home = ch_strdup( SLAPD_DEFAULT_DB_DIR ); + bdb->bi_dbenv_xflags = DB_TIME_NOTGRANTED; + bdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE; + + bdb->bi_cache.c_maxsize = DEFAULT_CACHE_SIZE; + bdb->bi_cache.c_minfree = 1; + + bdb->bi_lock_detect = DB_LOCK_DEFAULT; + bdb->bi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + bdb->bi_search_stack = NULL; + + ldap_pvt_thread_mutex_init( &bdb->bi_database_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_lastid_mutex ); +#ifdef BDB_HIER + ldap_pvt_thread_mutex_init( &bdb->bi_modrdns_mutex ); +#endif + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_lru_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_count_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_eifree_mutex ); + ldap_pvt_thread_mutex_init( &bdb->bi_cache.c_dntree.bei_kids_mutex ); + ldap_pvt_thread_rdwr_init ( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_rdwr_init( &bdb->bi_idl_tree_rwlock ); + ldap_pvt_thread_mutex_init( &bdb->bi_idl_tree_lrulock ); + + be->be_private = bdb; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + +#ifndef BDB_MULTIPLE_SUFFIXES + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX; +#endif + + rc = bdb_monitor_db_init( be ); + + return rc; +} + +static int +bdb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +bdb_db_open( BackendDB *be, ConfigReply *cr ) +{ + int rc, i; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct stat stat1, stat2; + u_int32_t flags; + char path[MAXPATHLEN]; + char *dbhome; + Entry *e = NULL; + int do_recover = 0, do_alock_recover = 0; + int alockt, quick = 0; + int do_retry = 1; + + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": need suffix.\n", + 1, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(bdb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val, 0, 0 ); + + /* Check existence of dbenv_home. Any error means trouble */ + rc = stat( bdb->bi_dbenv_home, &stat1 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "cannot access database directory \"%s\" (%d).\n", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, errno ); + return -1; + } + + /* Perform database use arbitration/recovery logic */ + alockt = (slapMode & SLAP_TOOL_READONLY) ? ALOCK_LOCKED : ALOCK_UNIQUE; + if ( slapMode & SLAP_TOOL_QUICK ) { + alockt |= ALOCK_NOSAVE; + quick = 1; + } + + rc = alock_open( &bdb->bi_alock_info, + "slapd", + bdb->bi_dbenv_home, alockt ); + + /* alockt is TRUE if the existing environment was created in Quick mode */ + alockt = (rc & ALOCK_NOSAVE) ? 1 : 0; + rc &= ~ALOCK_NOSAVE; + + if( rc == ALOCK_RECOVER ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "unclean shutdown detected; attempting recovery.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_alock_recover = 1; + do_recover = DB_RECOVER; + } else if( rc == ALOCK_BUSY ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "database already in use.\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } else if( rc != ALOCK_CLEAN ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "alock package is unstable.\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } + if ( rc == ALOCK_CLEAN ) + be->be_flags |= SLAP_DBFLAG_CLEAN; + + /* + * The DB_CONFIG file may have changed. If so, recover the + * database so that new settings are put into effect. Also + * note the possible absence of DB_CONFIG in the log. + */ + if( stat( bdb->bi_db_config_path, &stat1 ) == 0 ) { + if ( !do_recover ) { + char *ptr = lutil_strcopy(path, bdb->bi_dbenv_home); + *ptr++ = LDAP_DIRSEP[0]; + strcpy( ptr, "__db.001" ); + if( stat( path, &stat2 ) == 0 ) { + if( stat2.st_mtime < stat1.st_mtime ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": DB_CONFIG for suffix \"%s\" has changed.\n", + be->be_suffix[0].bv_val, 0, 0 ); + if ( quick ) { + Debug( LDAP_DEBUG_ANY, + "Cannot use Quick mode; perform manual recovery first.\n", + 0, 0, 0 ); + slapMode ^= SLAP_TOOL_QUICK; + rc = -1; + goto fail; + } else { + Debug( LDAP_DEBUG_ANY, + "Performing database recovery to activate new settings.\n", + 0, 0, 0 ); + } + do_recover = DB_RECOVER; + } + } + } + } + else { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": warning - no DB_CONFIG file found " + "in directory %s: (%d).\n" + "Expect poor performance for suffix \"%s\".\n", + bdb->bi_dbenv_home, errno, be->be_suffix[0].bv_val ); + } + + /* Always let slapcat run, regardless of environment state. + * This can be used to cause a cache flush after an unclean + * shutdown. + */ + if ( do_recover && ( slapMode & SLAP_TOOL_READONLY )) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "recovery skipped in read-only mode. " + "Run manual recovery if errors are encountered.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_recover = 0; + do_alock_recover = 0; + quick = alockt; + } + + /* An existing environment in Quick mode has nothing to recover. */ + if ( alockt && do_recover ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "cannot recover, database must be reinitialized.\n", + be->be_suffix[0].bv_val, 0, 0 ); + rc = -1; + goto fail; + } + + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "db_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + +#ifdef HAVE_EBCDIC + strcpy( path, bdb->bi_dbenv_home ); + __atoe( path ); + dbhome = path; +#else + dbhome = bdb->bi_dbenv_home; +#endif + + /* If existing environment is clean but doesn't support + * currently requested modes, remove it. + */ + if ( !do_recover && ( alockt ^ quick )) { +shm_retry: + rc = bdb->bi_dbenv->remove( bdb->bi_dbenv, dbhome, DB_FORCE ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv remove failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + bdb->bi_dbenv = NULL; + goto fail; + } + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "db_env_create failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + + bdb->bi_dbenv->set_errpfx( bdb->bi_dbenv, be->be_suffix[0].bv_val ); + bdb->bi_dbenv->set_errcall( bdb->bi_dbenv, bdb_errcall ); + + bdb->bi_dbenv->set_lk_detect( bdb->bi_dbenv, bdb->bi_lock_detect ); + + if ( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = bdb->bi_dbenv->set_encrypt( bdb->bi_dbenv, bdb->bi_db_crypt_key.bv_val, + DB_ENCRYPT_AES ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv set_encrypt failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + + /* One long-lived TXN per thread, two TXNs per write op */ + bdb->bi_dbenv->set_tx_max( bdb->bi_dbenv, connection_pool_max * 3 ); + + if( bdb->bi_dbenv_xflags != 0 ) { + rc = bdb->bi_dbenv->set_flags( bdb->bi_dbenv, + bdb->bi_dbenv_xflags, 1); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv_set_flags failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + goto fail; + } + } + +#define BDB_TXN_FLAGS (DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN) + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": " + "dbenv_open(%s).\n", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, 0); + + flags = DB_INIT_MPOOL | DB_CREATE | DB_THREAD; + + if ( !quick ) + flags |= BDB_TXN_FLAGS; + + /* If a key was set, use shared memory for the BDB environment */ + if ( bdb->bi_shm_key ) { + bdb->bi_dbenv->set_shm_key( bdb->bi_dbenv, bdb->bi_shm_key ); + flags |= DB_SYSTEM_MEM; + } + rc = (bdb->bi_dbenv->open)( bdb->bi_dbenv, dbhome, + flags | do_recover, bdb->bi_dbenv_mode ); + + if ( rc ) { + /* Regular open failed, probably a missing shm environment. + * Start over, do a recovery. + */ + if ( !do_recover && bdb->bi_shm_key && do_retry ) { + bdb->bi_dbenv->close( bdb->bi_dbenv, 0 ); + rc = db_env_create( &bdb->bi_dbenv, 0 ); + if( rc == 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_db_open) + ": database \"%s\": " + "shared memory env open failed, assuming stale env.\n", + be->be_suffix[0].bv_val, 0, 0 ); + do_retry = 0; + goto shm_retry; + } + } + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\" cannot be %s, err %d. " + "Restore from backup!\n", + be->be_suffix[0].bv_val, do_recover ? "recovered" : "opened", rc ); + goto fail; + } + + if ( do_alock_recover && alock_recover (&bdb->bi_alock_info) != 0 ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": database \"%s\": alock_recover failed\n", + be->be_suffix[0].bv_val, 0, 0 ); + rc = -1; + goto fail; + } + +#ifdef SLAP_ZONE_ALLOC + if ( bdb->bi_cache.c_maxsize ) { + bdb->bi_cache.c_zctx = slap_zn_mem_create( + SLAP_ZONE_INITSIZE, SLAP_ZONE_MAXSIZE, + SLAP_ZONE_DELTA, SLAP_ZONE_SIZE); + } +#endif + + /* dncache defaults to 0 == unlimited + * must be >= entrycache + */ + if ( bdb->bi_cache.c_eimax && bdb->bi_cache.c_eimax < bdb->bi_cache.c_maxsize ) { + bdb->bi_cache.c_eimax = bdb->bi_cache.c_maxsize; + } + + if ( bdb->bi_idl_cache_max_size ) { + bdb->bi_idl_tree = NULL; + bdb->bi_idl_cache_size = 0; + } + + flags = DB_THREAD | bdb->bi_db_opflags; + +#ifdef DB_AUTO_COMMIT + if ( !quick ) + flags |= DB_AUTO_COMMIT; +#endif + + bdb->bi_databases = (struct bdb_db_info **) ch_malloc( + BDB_INDICES * sizeof(struct bdb_db_info *) ); + + /* open (and create) main database */ + for( i = 0; bdbi_databases[i].name.bv_val; i++ ) { + struct bdb_db_info *db; + + db = (struct bdb_db_info *) ch_calloc(1, sizeof(struct bdb_db_info)); + + rc = db_create( &db->bdi_db, bdb->bi_dbenv, 0 ); + if( rc != 0 ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db_create(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + ch_free( db ); + goto fail; + } + + if( !BER_BVISNULL( &bdb->bi_db_crypt_key )) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_ENCRYPT ); + if ( rc ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db set_flags(DB_ENCRYPT)(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + } + + if( bdb->bi_flags & BDB_CHKSUM ) { + rc = db->bdi_db->set_flags( db->bdi_db, DB_CHKSUM ); + if ( rc ) { + snprintf(cr->msg, sizeof(cr->msg), + "database \"%s\": db set_flags(DB_CHKSUM)(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + } + + rc = bdb_db_findsize( bdb, (struct berval *)&bdbi_databases[i].name ); + + if( i == BDB_ID2ENTRY ) { + if ( !rc ) rc = BDB_ID2ENTRY_PAGESIZE; + rc = db->bdi_db->set_pagesize( db->bdi_db, rc ); + + if ( slapMode & SLAP_TOOL_MODE ) + db->bdi_db->mpf->set_priority( db->bdi_db->mpf, + DB_PRIORITY_VERY_LOW ); + + if ( slapMode & SLAP_TOOL_READMAIN ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } + } else { + /* Use FS default size if not configured */ + if ( rc ) + rc = db->bdi_db->set_pagesize( db->bdi_db, rc ); + + rc = db->bdi_db->set_flags( db->bdi_db, + DB_DUP | DB_DUPSORT ); +#ifndef BDB_HIER + if ( slapMode & SLAP_TOOL_READONLY ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } +#else + rc = db->bdi_db->set_dup_compare( db->bdi_db, + bdb_dup_compare ); + if ( slapMode & (SLAP_TOOL_READONLY|SLAP_TOOL_READMAIN) ) { + flags |= DB_RDONLY; + } else { + flags |= DB_CREATE; + } +#endif + } + +#ifdef HAVE_EBCDIC + strcpy( path, bdbi_databases[i].file ); + __atoe( path ); + rc = DB_OPEN( db->bdi_db, + path, + /* bdbi_databases[i].name, */ NULL, + bdbi_databases[i].type, + bdbi_databases[i].flags | flags, + bdb->bi_dbenv_mode ); +#else + rc = DB_OPEN( db->bdi_db, + bdbi_databases[i].file, + /* bdbi_databases[i].name, */ NULL, + bdbi_databases[i].type, + bdbi_databases[i].flags | flags, + bdb->bi_dbenv_mode ); +#endif + + if ( rc != 0 ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "db_open(%s/%s) failed: %s (%d).", + be->be_suffix[0].bv_val, + bdb->bi_dbenv_home, bdbi_databases[i].file, + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + db->bdi_db->close( db->bdi_db, 0 ); + ch_free( db ); + goto fail; + } + + flags &= ~(DB_CREATE | DB_RDONLY); + db->bdi_name = bdbi_databases[i].name; + bdb->bi_databases[i] = db; + } + + bdb->bi_databases[i] = NULL; + bdb->bi_ndatabases = i; + + /* get nextid */ + rc = bdb_last_id( be, NULL ); + if( rc != 0 ) { + snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": " + "last_id(%s) failed: %s (%d).", + be->be_suffix[0].bv_val, bdb->bi_dbenv_home, + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_db_open) ": %s\n", + cr->msg, 0, 0 ); + goto fail; + } + + if ( !quick ) { + int txflag = DB_READ_COMMITTED; + /* avoid deadlocks in server; tools should + * wait since they have no deadlock retry mechanism. + */ + if ( slapMode & SLAP_SERVER_MODE ) + txflag |= DB_TXN_NOWAIT; + TXN_BEGIN(bdb->bi_dbenv, NULL, &bdb->bi_cache.c_txn, txflag); + } + + entry_prealloc( bdb->bi_cache.c_maxsize ); + attr_prealloc( bdb->bi_cache.c_maxsize * 20 ); + + /* setup for empty-DN contexts */ + if ( BER_BVISEMPTY( &be->be_nsuffix[0] )) { + rc = bdb_id2entry( be, NULL, 0, &e ); + } + if ( !e ) { + struct berval gluebv = BER_BVC("glue"); + Operation op = {0}; + Opheader ohdr = {0}; + e = entry_alloc(); + e->e_id = 0; + ber_dupbv( &e->e_name, (struct berval *)&slap_empty_bv ); + ber_dupbv( &e->e_nname, (struct berval *)&slap_empty_bv ); + attr_merge_one( e, slap_schema.si_ad_objectClass, + &gluebv, NULL ); + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, + &gluebv, NULL ); + op.o_hdr = &ohdr; + op.o_bd = be; + op.ora_e = e; + op.o_dn = be->be_rootdn; + op.o_ndn = be->be_rootndn; + slap_add_opattrs( &op, NULL, NULL, 0, 0 ); + } + e->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END; + e->e_private = &bdb->bi_cache.c_dntree; + bdb->bi_cache.c_dntree.bei_e = e; + + /* monitor setup */ + rc = bdb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } + + bdb->bi_flags |= BDB_IS_OPEN; + + return 0; + +fail: + bdb_db_close( be, NULL ); + return rc; +} + +static int +bdb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int rc; + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + struct bdb_db_info *db; + bdb_idl_cache_entry_t *entry, *next_entry; + + /* monitor handling */ + (void)bdb_monitor_db_close( be ); + + { + Entry *e = bdb->bi_cache.c_dntree.bei_e; + if ( e ) { + bdb->bi_cache.c_dntree.bei_e = NULL; + e->e_private = NULL; + bdb_entry_return( e ); + } + } + + bdb->bi_flags &= ~BDB_IS_OPEN; + + ber_bvarray_free( bdb->bi_db_config ); + bdb->bi_db_config = NULL; + + if( bdb->bi_dbenv ) { + /* Free cache locker if we enabled locking. + * TXNs must all be closed before DBs... + */ + if ( !( slapMode & SLAP_TOOL_QUICK ) && bdb->bi_cache.c_txn ) { + TXN_ABORT( bdb->bi_cache.c_txn ); + bdb->bi_cache.c_txn = NULL; + } + bdb_reader_flush( bdb->bi_dbenv ); + } + + while( bdb->bi_databases && bdb->bi_ndatabases-- ) { + db = bdb->bi_databases[bdb->bi_ndatabases]; + rc = db->bdi_db->close( db->bdi_db, 0 ); + /* Lower numbered names are not strdup'd */ + if( bdb->bi_ndatabases >= BDB_NDB ) + free( db->bdi_name.bv_val ); + free( db ); + } + free( bdb->bi_databases ); + bdb->bi_databases = NULL; + + bdb_cache_release_all (&bdb->bi_cache); + + if ( bdb->bi_idl_cache_size ) { + avl_free( bdb->bi_idl_tree, NULL ); + bdb->bi_idl_tree = NULL; + entry = bdb->bi_idl_lru_head; + do { + next_entry = entry->idl_lru_next; + if ( entry->idl ) + free( entry->idl ); + free( entry->kstr.bv_val ); + free( entry ); + entry = next_entry; + } while ( entry != bdb->bi_idl_lru_head ); + bdb->bi_idl_lru_head = bdb->bi_idl_lru_tail = NULL; + } + + /* close db environment */ + if( bdb->bi_dbenv ) { + /* force a checkpoint, but not if we were ReadOnly, + * and not in Quick mode since there are no transactions there. + */ + if ( !( slapMode & ( SLAP_TOOL_QUICK|SLAP_TOOL_READONLY ))) { + rc = TXN_CHECKPOINT( bdb->bi_dbenv, 0, 0, DB_FORCE ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": " + "txn_checkpoint failed: %s (%d).\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + } + } + + rc = bdb->bi_dbenv->close( bdb->bi_dbenv, 0 ); + bdb->bi_dbenv = NULL; + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": " + "close failed: %s (%d)\n", + be->be_suffix[0].bv_val, db_strerror(rc), rc ); + return rc; + } + } + + rc = alock_close( &bdb->bi_alock_info, slapMode & SLAP_TOOL_QUICK ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "bdb_db_close: database \"%s\": alock_close failed\n", + be->be_suffix[0].bv_val, 0, 0 ); + return -1; + } + + return 0; +} + +static int +bdb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* stop and remove checkpoint task */ + if ( bdb->bi_txn_cp_task ) { + struct re_s *re = bdb->bi_txn_cp_task; + bdb->bi_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)bdb_monitor_db_destroy( be ); + + if( bdb->bi_dbenv_home ) ch_free( bdb->bi_dbenv_home ); + if( bdb->bi_db_config_path ) ch_free( bdb->bi_db_config_path ); + + bdb_attr_index_destroy( bdb ); + + ldap_pvt_thread_rdwr_destroy ( &bdb->bi_cache.c_rwlock ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_lru_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_count_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_eifree_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_cache.c_dntree.bei_kids_mutex ); +#ifdef BDB_HIER + ldap_pvt_thread_mutex_destroy( &bdb->bi_modrdns_mutex ); +#endif + ldap_pvt_thread_mutex_destroy( &bdb->bi_lastid_mutex ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_database_mutex ); + ldap_pvt_thread_rdwr_destroy( &bdb->bi_idl_tree_rwlock ); + ldap_pvt_thread_mutex_destroy( &bdb->bi_idl_tree_lrulock ); + + ch_free( bdb ); + be->be_private = NULL; + + return 0; +} + +int +bdb_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(bdb_back_initialize) ": initialize " + BDB_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 = db_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 BDB 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 != DB_VERSION_FULL ) { + /* fail if a versions don't match */ + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_back_initialize) ": " + "BDB library version mismatch:" + " expected " DB_VERSION_STRING "," + " got %s\n", version, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_back_initialize) + ": %s\n", version, 0, 0 ); + } + + db_env_set_func_free( ber_memfree ); + db_env_set_func_malloc( (db_malloc *)ber_memalloc ); + db_env_set_func_realloc( (db_realloc *)ber_memrealloc ); +#if !defined(NO_THREAD) && DB_VERSION_FULL <= 0x04070000 + /* This is a no-op on a NO_THREAD build. Leave the default + * alone so that BDB will sleep on interprocess conflicts. + * Don't bother on BDB 4.7... + */ + db_env_set_func_yield( ldap_pvt_thread_yield ); +#endif + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = bdb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = bdb_db_open; + bi->bi_db_close = bdb_db_close; + bi->bi_db_destroy = bdb_db_destroy; + + bi->bi_op_add = bdb_add; + bi->bi_op_bind = bdb_bind; + bi->bi_op_compare = bdb_compare; + bi->bi_op_delete = bdb_delete; + bi->bi_op_modify = bdb_modify; + bi->bi_op_modrdn = bdb_modrdn; + bi->bi_op_search = bdb_search; + + bi->bi_op_unbind = 0; + + bi->bi_extended = bdb_extended; + + bi->bi_chk_referrals = bdb_referrals; + bi->bi_operational = bdb_operational; + bi->bi_has_subordinates = bdb_hasSubordinates; + bi->bi_entry_release_rw = bdb_entry_release; + bi->bi_entry_get_rw = bdb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = bdb_tool_entry_open; + bi->bi_tool_entry_close = bdb_tool_entry_close; + bi->bi_tool_entry_first = backend_tool_entry_first; + bi->bi_tool_entry_first_x = bdb_tool_entry_first_x; + bi->bi_tool_entry_next = bdb_tool_entry_next; + bi->bi_tool_entry_get = bdb_tool_entry_get; + bi->bi_tool_entry_put = bdb_tool_entry_put; + bi->bi_tool_entry_reindex = bdb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = bdb_tool_dn2id_get; + bi->bi_tool_entry_modify = bdb_tool_entry_modify; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = bdb_back_init_cf( bi ); + + return rc; +} + +#if (SLAPD_BDB == SLAPD_MOD_DYNAMIC && !defined(BDB_HIER)) || \ + (SLAPD_HDB == SLAPD_MOD_DYNAMIC && defined(BDB_HIER)) + +/* conditionally define the init_module() function */ +#ifdef BDB_HIER +SLAP_BACKEND_INIT_MODULE( hdb ) +#else /* !BDB_HIER */ +SLAP_BACKEND_INIT_MODULE( bdb ) +#endif /* !BDB_HIER */ + +#endif /* SLAPD_[BH]DB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-bdb/key.c b/servers/slapd/back-bdb/key.c new file mode 100644 index 0000000..6db8677 --- /dev/null +++ b/servers/slapd/back-bdb/key.c @@ -0,0 +1,104 @@ +/* index.c - routines for dealing with attribute indexes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "idl.h" + +/* read a key */ +int +bdb_key_read( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID *ids, + DBC **saved_cursor, + int get_flag +) +{ + int rc; + DBT key; + + Debug( LDAP_DEBUG_TRACE, "=> key_read\n", 0, 0, 0 ); + + DBTzero( &key ); + bv2DBT(k,&key); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + + rc = bdb_idl_fetch_key( be, db, txn, &key, ids, saved_cursor, get_flag ); + + if( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "<= bdb_index_read: failed (%d)\n", + rc, 0, 0 ); + } else { + Debug( LDAP_DEBUG_TRACE, "<= bdb_index_read %ld candidates\n", + (long) BDB_IDL_N(ids), 0, 0 ); + } + + return rc; +} + +/* Add or remove stuff from index files */ +int +bdb_key_change( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID id, + int op +) +{ + int rc; + DBT key; + + Debug( LDAP_DEBUG_TRACE, "=> key_change(%s,%lx)\n", + op == SLAP_INDEX_ADD_OP ? "ADD":"DELETE", (long) id, 0 ); + + DBTzero( &key ); + bv2DBT(k,&key); + key.ulen = key.size; + key.flags = DB_DBT_USERMEM; + + if (op == SLAP_INDEX_ADD_OP) { + /* Add values */ + +#ifdef BDB_TOOL_IDL_CACHING + if ( slapMode & SLAP_TOOL_QUICK ) + rc = bdb_tool_idl_add( be, db, txn, &key, id ); + else +#endif + rc = bdb_idl_insert_key( be, db, txn, &key, id ); + if ( rc == DB_KEYEXIST ) rc = 0; + } else { + /* Delete values */ + rc = bdb_idl_delete_key( be, db, txn, &key, id ); + if ( rc == DB_NOTFOUND ) rc = 0; + } + + Debug( LDAP_DEBUG_TRACE, "<= key_change %d\n", rc, 0, 0 ); + + return rc; +} diff --git a/servers/slapd/back-bdb/modify.c b/servers/slapd/back-bdb/modify.c new file mode 100644 index 0000000..038deaa --- /dev/null +++ b/servers/slapd/back-bdb/modify.c @@ -0,0 +1,835 @@ +/* modify.c - bdb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +static struct berval scbva[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +static void +bdb_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 = bdb_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 = bdb_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 bdb_modify_internal( + Operation *op, + DB_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, "bdb_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 bdb_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, + "bdb_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, "bdb_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, + "bdb_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, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "bdb_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, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "bdb_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, + "bdb_modify_internal: %d %s\n", + err, *text, 0); + } else { + got_delete = 1; + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "bdb_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, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "bdb_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_SUCCESS ) { + got_delete = 1; + } else if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "bdb_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, + "bdb_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, "bdb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "bdb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + err = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "bdb_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 ) { + bdb_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 ) { + bdb_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; + /* let add know there were deletes */ + if ( a2->a_flags & SLAP_ATTR_IXADD ) + a2->a_flags |= SLAP_ATTR_IXDEL; + vals = op->o_tmpalloc( (ap->a_numvals + 1) * + sizeof(struct berval), op->o_tmpmemctx ); + j = 0; + for ( i=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 = bdb_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; + if ( ap->a_flags & SLAP_ATTR_IXDEL ) { + /* if any values were deleted, we must readd index + * for all remaining values. + */ + ap->a_flags &= ~SLAP_ATTR_IXDEL; + rc = bdb_index_values( op, tid, ap->a_desc, + ap->a_nvals, + e->e_id, SLAP_INDEX_ADD_OP ); + } else { + int found = 0; + /* if this was only an add, we only need to index + * the added values. + */ + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + struct berval *vals; + if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals ) + continue; + found = 1; + switch( ml->sml_op ) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + case LDAP_MOD_INCREMENT: + case SLAP_MOD_SOFTADD: + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( ml->sml_op == LDAP_MOD_INCREMENT ) + vals = ap->a_nvals; + else if ( ml->sml_nvalues ) + vals = ml->sml_nvalues; + else + vals = ml->sml_values; + rc = bdb_index_values( op, tid, ap->a_desc, + vals, e->e_id, SLAP_INDEX_ADD_OP ); + break; + } + if ( rc ) + break; + } + /* This attr was affected by a modify of a subtype, so + * there was no direct match in the modlist. Just readd + * all of its values. + */ + if ( !found ) { + rc = bdb_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 +bdb_modify( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei = NULL; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + Entry dummy = {0}; + + DB_LOCK lock; + + int num_retries = 0; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(bdb_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 ); + } + + if( 0 ) { +retry: /* transaction retry */ + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + dummy.e_attrs = NULL; + } + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": retrying...\n", 0, 0, 0); + + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modify) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* get entry or ancestor */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": dn2entry failed (%d)\n", + rs->sr_err, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_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; + } + } + + e = ei->bei_e; + + /* acquire and lock entry */ + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == DB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if ( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + bdb_unlocked_cache_return_entry_r (&bdb->bi_cache, 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 ); + + if ( rs->sr_ref != default_referral ) { + ber_bvarray_free( rs->sr_ref ); + } + free( (char *)rs->sr_matched ); + rs->sr_ref = NULL; + rs->sr_matched = NULL; + + 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(bdb_modify) ": entry is referral\n", + 0, 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; + } + + 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(bdb_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; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_begin(2) failed: " "%s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modify) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + /* Modify the entry */ + dummy = *e; + rs->sr_err = bdb_modify_internal( op, lt2, op->orm_modlist, + &dummy, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": modify failed (%d)\n", + rs->sr_err, 0, 0 ); + if ( (rs->sr_err == LDAP_INSUFFICIENT_ACCESS) && opinfo.boi_err ) { + rs->sr_err = opinfo.boi_err; + } + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + goto return_results; + } + + /* change the entry itself */ + rs->sr_err = bdb_id2entry_update( op->o_bd, lt2, &dummy ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": id2entry update failed " "(%d)\n", + rs->sr_err, 0, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + rs->sr_text = "entry update failed"; + goto return_results; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) 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(bdb_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; + } + } + } + + if( op->o_noop ) { + if ( ( rs->sr_err = TXN_ABORT( ltid ) ) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + } else { + /* may have changed in bdb_modify_internal() */ + e->e_ocflags = dummy.e_ocflags; + rc = bdb_cache_modify( bdb, e, dummy.e_attrs, ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + dummy.e_attrs = NULL; + + rs->sr_err = TXN_COMMIT( ltid, 0 ); + } + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + db_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(bdb_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( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + +done: + slap_graduate_commit_csn( op ); + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + } + + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w (&bdb->bi_cache, 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-bdb/modrdn.c b/servers/slapd/back-bdb/modrdn.c new file mode 100644 index 0000000..2e18e5e --- /dev/null +++ b/servers/slapd/back-bdb/modrdn.c @@ -0,0 +1,842 @@ +/* modrdn.c - bdb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_modrdn( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_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; + EntryInfo *ei = NULL, *eip = NULL, *nei = NULL, *neip = NULL; + /* LDAP v2 supporting correct attribute handling. */ + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + DB_TXN *ltid = NULL, *lt2; + struct bdb_op_info opinfo = {{{ 0 }}}; + 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 ); + + DB_LOCK lock, plock, nplock; + + int num_retries = 0; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + + int parent_is_glue = 0; + int parent_is_leaf = 0; + +#ifdef LDAP_X_TXN + int settle = 0; +#endif + + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(bdb_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 ); + + if( 0 ) { +retry: /* transaction retry */ + if ( dummy.e_attrs ) { + attrs_free( dummy.e_attrs ); + dummy.e_attrs = NULL; + } + if (e != NULL) { + bdb_unlocked_cache_return_entry_w(&bdb->bi_cache, e); + e = NULL; + } + if (p != NULL) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + if (np != NULL) { + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, np); + np = NULL; + } + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(bdb_modrdn) + ": retrying...\n", 0, 0, 0 ); + + rs->sr_err = TXN_ABORT( ltid ); + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + op->o_do_not_cache = opinfo.boi_acl_cache; + if( rs->sr_err != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + parent_is_glue = 0; + parent_is_leaf = 0; + bdb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, NULL, <id, + bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": txn_begin failed: " + "%s (%d)\n", db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": txn1 id: %x\n", + ltid->id(ltid), 0, 0 ); + + opinfo.boi_oe.oe_key = bdb; + opinfo.boi_txn = ltid; + opinfo.boi_err = 0; + opinfo.boi_acl_cache = op->o_do_not_cache; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &opinfo.boi_oe, oe_next ); + + /* get entry */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, 1, + &lock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + 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; + } + + e = ei->bei_e; + /* FIXME: dn2entry() should return non-glue entry */ + if (( rs->sr_err == DB_NOTFOUND ) || + ( !manageDSAit && e && is_entry_glue( e ))) + { + if( e != NULL ) { + rs->sr_matched = ch_strdup( e->e_dn ); + rs->sr_ref = is_entry_referral( e ) + ? get_entry_referrals( op, e ) + : NULL; + bdb_unlocked_cache_return_entry_r( &bdb->bi_cache, 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 ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + 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; + } + +#ifndef BDB_HIER + rs->sr_err = bdb_cache_children( op, ltid, e ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": non-leaf %s\n", + op->o_req_dn.bv_val, 0, 0); + rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF; + rs->sr_text = "subtree rename not supported"; + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": has_children failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + } + goto return_results; + } + ei->bei_state |= CACHE_ENTRY_NO_KIDS; +#endif + + if (!manageDSAit && is_entry_referral( e ) ) { + /* parent is a referral, don't allow add */ + rs->sr_ref = get_entry_referrals( op, e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_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; + } + + if ( be_issuffix( op->o_bd, &e->e_nname ) ) { +#ifdef BDB_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( &e->e_nname, &p_ndn ); + } + np_ndn = &p_ndn; + eip = ei->bei_parent; + if ( eip && eip->bei_id ) { + /* Make sure parent entry exist and we can write its + * children. + */ + rs->sr_err = bdb_cache_find_id( op, ltid, + eip->bei_id, &eip, 0, &plock ); + + switch( rs->sr_err ) { + case 0: + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + 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; + } + + p = eip->bei_e; + if( p == NULL) { + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) + ": parent does not exist\n", 0, 0, 0); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "old entry's parent does not exist"; + goto return_results; + } + } else { + p = (Entry *)&slap_entry_root; + } + + /* 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 ( !p_ndn.bv_len ) + p = NULL; + + if ( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + 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 old parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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( &e->e_name, &p_dn ); + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": parent dn=%s\n", + p_dn.bv_val, 0, 0 ); + + new_parent_dn = &p_dn; /* New Parent unless newSuperior given */ + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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, "bdb_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 BDB_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 = bdb_dn2entry( op, ltid, np_ndn, + &neip, 0, &nplock ); + + switch( rs->sr_err ) { + case 0: np = neip->bei_e; + case DB_NOTFOUND: + break; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + 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( np == NULL) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": wr to new parent OK np=%p, id=%ld\n", + (void *) np, (long) np->e_id, 0 ); + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, np, children, + NULL, ACL_WADD, NULL ); + + if( ! rs->sr_err ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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; + } + + if ( is_entry_alias( np ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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(bdb_modrdn) + ": entry is referral\n", + 0, 0, 0 ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + } 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 ) { + switch( opinfo.boi_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + + 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(bdb_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, NULL ); + } + + if (!new_ndn.bv_val) { + struct berval bv = {0, NULL}; + dnNormalize( 0, NULL, NULL, &new_dn, &bv, op->o_tmpmemctx ); + ber_dupbv( &new_ndn, &bv ); + /* FIXME: why not call dnNormalize() w/o ctx? */ + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": new ndn=%s\n", + new_ndn.bv_val, 0, 0 ); + + /* Shortcut the search */ + nei = neip ? neip : eip; + rs->sr_err = bdb_cache_find_ndn ( op, ltid, &new_ndn, &nei ); + if ( nei ) bdb_cache_entryinfo_unlock( nei ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case DB_NOTFOUND: + break; + case 0: + /* Allow rename to same DN */ + if ( nei == ei ) + 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(bdb_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; + } + } + } + + /* nested transaction */ + rs->sr_err = TXN_BEGIN( bdb->bi_dbenv, ltid, <2, bdb->bi_db_opflags ); + rs->sr_text = NULL; + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) + ": txn_begin(2) failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_modrdn) ": txn2 id: %x\n", + lt2->id(lt2), 0, 0 ); + + /* delete old DN */ + rs->sr_err = bdb_dn2id_delete( op, lt2, eip, e ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": dn2id del failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + 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 = bdb_dn2id_add( op, lt2, neip ? neip : eip, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": dn2id add failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + 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 = bdb_modify_internal( op, lt2, op->orr_modlist, &dummy, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": modify failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + if ( ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) && opinfo.boi_err ) { + rs->sr_err = opinfo.boi_err; + } + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + goto return_results; + } + + /* id2entry index */ + rs->sr_err = bdb_id2entry_update( op->o_bd, lt2, &dummy ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": id2entry failed: %s (%d)\n", + db_strerror(rs->sr_err), rs->sr_err, 0 ); + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + 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 = bdb_cache_children( op, lt2, p ); + if ( rs->sr_err != DB_NOTFOUND ) { + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + case 0: + break; + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_modrdn) + ": has_children failed: %s (%d)\n", + db_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; + } + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + p = NULL; + } + + if ( TXN_COMMIT( lt2, 0 ) != 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "txn_commit(2) 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(bdb_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( op->o_noop ) { + if(( rs->sr_err=TXN_ABORT( ltid )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + ltid = NULL; + /* Only free attrs if they were dup'd. */ + if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL; + goto return_results; + } + + } else { + rc = bdb_cache_modrdn( bdb, e, &op->orr_nnewrdn, &dummy, neip, + ltid, &lock ); + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } + dummy.e_attrs = NULL; + new_dn.bv_val = NULL; + new_ndn.bv_val = NULL; + + if(( rs->sr_err=TXN_COMMIT( ltid, 0 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + ltid = NULL; + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_oe, OpExtra, oe_next ); + opinfo.boi_oe.oe_key = NULL; + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_modrdn) ": %s : %s (%d)\n", + rs->sr_text, db_strerror(rs->sr_err), rs->sr_err ); + rs->sr_err = LDAP_OTHER; + + goto return_results; + } + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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( rs->sr_err == LDAP_SUCCESS && bdb->bi_txn_cp_kbyte ) { + TXN_CHECKPOINT( bdb->bi_dbenv, + bdb->bi_txn_cp_kbyte, bdb->bi_txn_cp_min, 0 ); + } + + 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_dn.bv_val != NULL ) free( new_dn.bv_val ); + if( new_ndn.bv_val != NULL ) free( new_ndn.bv_val ); + + /* LDAP v3 Support */ + if( np != NULL ) { + /* free new parent and reader lock */ + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, np); + } + + if( p != NULL ) { + /* free parent and reader lock */ + bdb_unlocked_cache_return_entry_r(&bdb->bi_cache, p); + } + + /* free entry */ + if( e != NULL ) { + bdb_unlocked_cache_return_entry_w( &bdb->bi_cache, e); + } + + if( ltid != NULL ) { + TXN_ABORT( ltid ); + } + if ( opinfo.boi_oe.oe_key ) { + LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.boi_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-bdb/monitor.c b/servers/slapd/back-bdb/monitor.c new file mode 100644 index 0000000..8603bcd --- /dev/null +++ b/servers/slapd/back-bdb/monitor.c @@ -0,0 +1,724 @@ +/* monitor.c - monitor bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +#include "../back-monitor/back-monitor.h" + +#include "config.h" + +static ObjectClass *oc_olmBDBDatabase; + +static AttributeDescription *ad_olmBDBEntryCache, + *ad_olmBDBDNCache, *ad_olmBDBIDLCache, + *ad_olmDbDirectory; + +#ifdef BDB_MONITOR_IDX +static int +bdb_monitor_idx_entry_add( + struct bdb_info *bdb, + Entry *e ); + +static AttributeDescription *ad_olmDbNotIndexed; +#endif /* BDB_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 + * BDB 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 + * BDB database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.1 + */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "olmBDBAttributes", "olmDatabaseAttributes:1" }, + { "olmBDBObjectClasses", "olmDatabaseObjectClasses:1" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **ad; +} s_at[] = { + { "( olmBDBAttributes:1 " + "NAME ( 'olmBDBEntryCache' ) " + "DESC 'Number of items in Entry Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBEntryCache }, + + { "( olmBDBAttributes:2 " + "NAME ( 'olmBDBDNCache' ) " + "DESC 'Number of items in DN Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBDNCache }, + + { "( olmBDBAttributes:3 " + "NAME ( 'olmBDBIDLCache' ) " + "DESC 'Number of items in IDL Cache' " + "SUP monitorCounter " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmBDBIDLCache }, + + { "( olmDatabaseAttributes:1 " + "NAME ( 'olmDbDirectory' ) " + "DESC 'Path name of the directory " + "where the database environment resides' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbDirectory }, + +#ifdef BDB_MONITOR_IDX + { "( olmDatabaseAttributes:2 " + "NAME ( 'olmDbNotIndexed' ) " + "DESC 'Missing indexes resulting from candidate selection' " + "SUP monitoredInfo " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + &ad_olmDbNotIndexed }, +#endif /* BDB_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"? */ + { "( olmBDBObjectClasses:1 " + "NAME ( 'olmBDBDatabase' ) " + "SUP top AUXILIARY " + "MAY ( " + "olmBDBEntryCache " + "$ olmBDBDNCache " + "$ olmBDBIDLCache " + "$ olmDbDirectory " +#ifdef BDB_MONITOR_IDX + "$ olmDbNotIndexed " +#endif /* BDB_MONITOR_IDX */ + ") )", + &oc_olmBDBDatabase }, + + { NULL } +}; + +static int +bdb_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + struct bdb_info *bdb = (struct bdb_info *) priv; + Attribute *a; + + char buf[ BUFSIZ ]; + struct berval bv; + + assert( ad_olmBDBEntryCache != NULL ); + + a = attr_find( e->e_attrs, ad_olmBDBEntryCache ); + assert( a != NULL ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_cache.c_cursize ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmBDBDNCache ); + assert( a != NULL ); + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_cache.c_eiused ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + a = attr_find( e->e_attrs, ad_olmBDBIDLCache ); + assert( a != NULL ); + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", bdb->bi_idl_cache_size ); + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + +#ifdef BDB_MONITOR_IDX + bdb_monitor_idx_entry_add( bdb, e ); +#endif /* BDB_MONITOR_IDX */ + + return SLAP_CB_CONTINUE; +} + +#if 0 /* uncomment if required */ +static int +bdb_monitor_modify( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + return SLAP_CB_CONTINUE; +} +#endif + +static int +bdb_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_olmBDBDatabase->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; +} + +#define bdb_monitor_initialize BDB_SYMBOL(monitor_initialize) + +/* + * call from within bdb_initialize() + */ +static int +bdb_monitor_initialize( void ) +{ + int i, code; + ConfigArgs c; + char *argv[ 3 ]; + + static int bdb_monitor_initialized = 0; + + /* set to 0 when successfully initialized; otherwise, remember failure */ + static int bdb_monitor_initialized_failure = 1; + + if ( bdb_monitor_initialized++ ) { + return bdb_monitor_initialized_failure; + } + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + /* register schema here */ + + argv[ 0 ] = "back-bdb/back-hdb 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(bdb_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(bdb_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(bdb_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 ( bdb_monitor_initialized_failure = LDAP_SUCCESS ); +} + +/* + * call from within bdb_db_init() + */ +int +bdb_monitor_db_init( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + if ( bdb_monitor_initialize() == LDAP_SUCCESS ) { + /* monitoring in back-bdb is on by default */ + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + +#ifdef BDB_MONITOR_IDX + bdb->bi_idx = NULL; + ldap_pvt_thread_mutex_init( &bdb->bi_idx_mutex ); +#endif /* BDB_MONITOR_IDX */ + + return 0; +} + +/* + * call from within bdb_db_open() + */ +int +bdb_monitor_db_open( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(bdb_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 + 4 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmBDBDatabase->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_olmBDBEntryCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmBDBDNCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_olmBDBIDLCache; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + { + struct berval bv, nbv; + ber_len_t pathlen = 0, len = 0; + char path[ MAXPATHLEN ] = { '\0' }; + char *fname = bdb->bi_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 = bdb_monitor_update; +#if 0 /* uncomment if required */ + cb->mc_modify = bdb_monitor_modify; +#endif + cb->mc_free = bdb_monitor_free; + cb->mc_private = (void *)bdb; + + /* make sure the database is registered; then add monitor attributes */ + rc = mbe->register_database( be, &bdb->bi_monitor.bdm_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &bdb->bi_monitor.bdm_ndn, a, cb, + NULL, 0, NULL ); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + bdb->bi_monitor.bdm_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * bdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +/* + * call from within bdb_db_close() + */ +int +bdb_monitor_db_close( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + if ( !BER_BVISNULL( &bdb->bi_monitor.bdm_ndn ) ) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && &mi->bi_extra ) { + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &bdb->bi_monitor.bdm_ndn, + (monitor_callback_t *)bdb->bi_monitor.bdm_cb, + NULL, 0, NULL ); + } + + memset( &bdb->bi_monitor, 0, sizeof( bdb->bi_monitor ) ); + } + + return 0; +} + +/* + * call from within bdb_db_destroy() + */ +int +bdb_monitor_db_destroy( BackendDB *be ) +{ +#ifdef BDB_MONITOR_IDX + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* TODO: free tree */ + ldap_pvt_thread_mutex_destroy( &bdb->bi_idx_mutex ); + avl_free( bdb->bi_idx, ch_free ); +#endif /* BDB_MONITOR_IDX */ + + return 0; +} + +#ifdef BDB_MONITOR_IDX + +#define BDB_MONITOR_IDX_TYPES (4) + +typedef struct monitor_idx_t monitor_idx_t; + +struct monitor_idx_t { + AttributeDescription *idx_ad; + unsigned long idx_count[BDB_MONITOR_IDX_TYPES]; +}; + +static int +bdb_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 +bdb_monitor_idx2len( monitor_idx_t *idx ) +{ + int i; + ber_len_t len = 0; + + for ( i = 0; i < BDB_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 +bdb_monitor_idx_add( + struct bdb_info *bdb, + AttributeDescription *desc, + slap_mask_t type ) +{ + monitor_idx_t idx_dummy = { 0 }, + *idx; + int rc = 0, key; + + idx_dummy.idx_ad = desc; + key = bdb_monitor_bitmask2key( type ) - 1; + if ( key >= BDB_MONITOR_IDX_TYPES ) { + /* invalid index type */ + return -1; + } + + ldap_pvt_thread_mutex_lock( &bdb->bi_idx_mutex ); + + idx = (monitor_idx_t *)avl_find( bdb->bi_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( &bdb->bi_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( &bdb->bi_idx_mutex ); + + return rc; +} + +static int +bdb_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[ BDB_MONITOR_IDX_TYPES ][ SLAP_TEXT_BUFLEN ]; + ber_len_t count_len[ BDB_MONITOR_IDX_TYPES ], + idx_len; + int i, num = 0; + + idx_len = bdb_monitor_idx2len( idx ); + + bv.bv_len = 0; + for ( i = 0; i < BDB_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 < BDB_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 +bdb_monitor_idx_entry_add( + struct bdb_info *bdb, + Entry *e ) +{ + BerVarray vals = NULL; + Attribute *a; + + a = attr_find( e->e_attrs, ad_olmDbNotIndexed ); + + ldap_pvt_thread_mutex_lock( &bdb->bi_idx_mutex ); + + avl_apply( bdb->bi_idx, bdb_monitor_idx_apply, + &vals, -1, AVL_INORDER ); + + ldap_pvt_thread_mutex_unlock( &bdb->bi_idx_mutex ); + + if ( vals != NULL ) { + if ( a != NULL ) { + assert( a->a_nvals == a->a_vals ); + + ber_bvarray_free( a->a_vals ); + + } else { + Attribute **ap; + + for ( ap = &e->e_attrs; *ap != NULL; ap = &(*ap)->a_next ) + ; + *ap = attr_alloc( ad_olmDbNotIndexed ); + a = *ap; + } + a->a_vals = vals; + a->a_nvals = a->a_vals; + } + + return 0; +} + +#endif /* BDB_MONITOR_IDX */ diff --git a/servers/slapd/back-bdb/nextid.c b/servers/slapd/back-bdb/nextid.c new file mode 100644 index 0000000..caad4f6 --- /dev/null +++ b/servers/slapd/back-bdb/nextid.c @@ -0,0 +1,80 @@ +/* init.c - initialize bdb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int bdb_next_id( BackendDB *be, ID *out ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + ldap_pvt_thread_mutex_lock( &bdb->bi_lastid_mutex ); + *out = ++bdb->bi_lastid; + ldap_pvt_thread_mutex_unlock( &bdb->bi_lastid_mutex ); + + return 0; +} + +int bdb_last_id( BackendDB *be, DB_TXN *tid ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + int rc; + ID id = 0; + unsigned char idbuf[sizeof(ID)]; + DBT key, data; + DBC *cursor; + + DBTzero( &key ); + key.flags = DB_DBT_USERMEM; + key.data = (char *) idbuf; + key.ulen = sizeof( idbuf ); + + DBTzero( &data ); + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + + /* Get a read cursor */ + rc = bdb->bi_id2entry->bdi_db->cursor( bdb->bi_id2entry->bdi_db, + tid, &cursor, 0 ); + + if (rc == 0) { + rc = cursor->c_get(cursor, &key, &data, DB_LAST); + cursor->c_close(cursor); + } + + switch(rc) { + case DB_NOTFOUND: + rc = 0; + break; + case 0: + BDB_DISK2ID( idbuf, &id ); + break; + + default: + Debug( LDAP_DEBUG_ANY, + "=> bdb_last_id: get failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + goto done; + } + + bdb->bi_lastid = id; + +done: + return rc; +} diff --git a/servers/slapd/back-bdb/operational.c b/servers/slapd/back-bdb/operational.c new file mode 100644 index 0000000..9ca3dd1 --- /dev/null +++ b/servers/slapd/back-bdb/operational.c @@ -0,0 +1,151 @@ +/* operational.c - bdb backend operational attributes function */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +/* + * sets *hasSubordinates to LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE + * if the entry has children or not. + */ +int +bdb_hasSubordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct bdb_op_info *opinfo; + OpExtra *oex; + DB_TXN *rtxn; + int rc; + int release = 0; + + assert( e != NULL ); + + /* NOTE: this should never happen, but it actually happens + * when using back-relay; until we find a better way to + * preserve entry's private information while rewriting it, + * let's disable the hasSubordinate feature for back-relay. + */ + if ( BEI( e ) == NULL ) { + Entry *ee = NULL; + rc = be_entry_get_rw( op, &e->e_nname, NULL, NULL, 0, &ee ); + if ( rc != LDAP_SUCCESS || ee == NULL ) { + rc = LDAP_OTHER; + goto done; + } + e = ee; + release = 1; + if ( BEI( ee ) == NULL ) { + rc = LDAP_OTHER; + goto done; + } + } + + /* Check for a txn in a parent op, otherwise use reader txn */ + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) + break; + } + opinfo = (struct bdb_op_info *) oex; + if ( opinfo && opinfo->boi_txn ) { + rtxn = opinfo->boi_txn; + } else { + rc = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + +retry: + /* FIXME: we can no longer assume the entry's e_private + * field is correctly populated; so we need to reacquire + * it with reader lock */ + rc = bdb_cache_children( op, rtxn, e ); + + switch( rc ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + + case 0: + *hasSubordinates = LDAP_COMPARE_TRUE; + break; + + case DB_NOTFOUND: + *hasSubordinates = LDAP_COMPARE_FALSE; + rc = LDAP_SUCCESS; + break; + + default: + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(bdb_hasSubordinates) + ": has_children failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + rc = LDAP_OTHER; + } + +done:; + if ( release && e != NULL ) be_entry_release_r( op, e ); + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +int +bdb_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 = bdb_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-bdb/proto-bdb.h b/servers/slapd/back-bdb/proto-bdb.h new file mode 100644 index 0000000..f61e881 --- /dev/null +++ b/servers/slapd/back-bdb/proto-bdb.h @@ -0,0 +1,678 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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_BDB_H +#define _PROTO_BDB_H + +LDAP_BEGIN_DECL + +#ifdef BDB_HIER +#define BDB_SYMBOL(x) LDAP_CONCAT(hdb_,x) +#define BDB_UCTYPE "HDB" +#else +#define BDB_SYMBOL(x) LDAP_CONCAT(bdb_,x) +#define BDB_UCTYPE "BDB" +#endif + +/* + * attr.c + */ + +#define bdb_attr_mask BDB_SYMBOL(attr_mask) +#define bdb_attr_flush BDB_SYMBOL(attr_flush) +#define bdb_attr_slot BDB_SYMBOL(attr_slot) +#define bdb_attr_index_config BDB_SYMBOL(attr_index_config) +#define bdb_attr_index_destroy BDB_SYMBOL(attr_index_destroy) +#define bdb_attr_index_free BDB_SYMBOL(attr_index_free) +#define bdb_attr_index_unparse BDB_SYMBOL(attr_index_unparse) +#define bdb_attr_info_free BDB_SYMBOL(attr_info_free) + +AttrInfo *bdb_attr_mask( struct bdb_info *bdb, + AttributeDescription *desc ); + +void bdb_attr_flush( struct bdb_info *bdb ); + +int bdb_attr_slot( struct bdb_info *bdb, + AttributeDescription *desc, int *insert ); + +int bdb_attr_index_config LDAP_P(( struct bdb_info *bdb, + const char *fname, int lineno, + int argc, char **argv, struct config_reply_s *cr )); + +void bdb_attr_index_unparse LDAP_P(( struct bdb_info *bdb, BerVarray *bva )); +void bdb_attr_index_destroy LDAP_P(( struct bdb_info *bdb )); +void bdb_attr_index_free LDAP_P(( struct bdb_info *bdb, + AttributeDescription *ad )); + +void bdb_attr_info_free( AttrInfo *ai ); + +/* + * config.c + */ + +#define bdb_back_init_cf BDB_SYMBOL(back_init_cf) + +int bdb_back_init_cf( BackendInfo *bi ); + +/* + * dbcache.c + */ +#define bdb_db_cache BDB_SYMBOL(db_cache) +#define bdb_db_findsize BDB_SYMBOL(db_findsize) + +int +bdb_db_cache( + Backend *be, + struct berval *name, + DB **db ); + +int +bdb_db_findsize( + struct bdb_info *bdb, + struct berval *name ); + +/* + * dn2entry.c + */ +#define bdb_dn2entry BDB_SYMBOL(dn2entry) + +int bdb_dn2entry LDAP_P(( Operation *op, DB_TXN *tid, + struct berval *dn, EntryInfo **e, int matched, + DB_LOCK *lock )); + +/* + * dn2id.c + */ +#define bdb_dn2id BDB_SYMBOL(dn2id) +#define bdb_dn2id_add BDB_SYMBOL(dn2id_add) +#define bdb_dn2id_delete BDB_SYMBOL(dn2id_delete) +#define bdb_dn2id_children BDB_SYMBOL(dn2id_children) +#define bdb_dn2idl BDB_SYMBOL(dn2idl) + +int bdb_dn2id( + Operation *op, + struct berval *dn, + EntryInfo *ei, + DB_TXN *txn, + DBC **cursor ); + +int bdb_dn2id_add( + Operation *op, + DB_TXN *tid, + EntryInfo *eip, + Entry *e ); + +int bdb_dn2id_delete( + Operation *op, + DB_TXN *tid, + EntryInfo *eip, + Entry *e ); + +int bdb_dn2id_children( + Operation *op, + DB_TXN *tid, + Entry *e ); + +int bdb_dn2idl( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo *ei, + ID *ids, + ID *stack ); + +#ifdef BDB_HIER +#define bdb_dn2id_parent BDB_SYMBOL(dn2id_parent) +#define bdb_dup_compare BDB_SYMBOL(dup_compare) +#define bdb_fix_dn BDB_SYMBOL(fix_dn) + +int bdb_dn2id_parent( + Operation *op, + DB_TXN *txn, + EntryInfo *ei, + ID *idp ); + +int bdb_dup_compare( + DB *db, + const DBT *usrkey, + const DBT *curkey ); + +int bdb_fix_dn( Entry *e, int checkit ); +#endif + + +/* + * error.c + */ +#define bdb_errcall BDB_SYMBOL(errcall) + +#if DB_VERSION_FULL < 0x04030000 +void bdb_errcall( const char *pfx, char * msg ); +#else +#define bdb_msgcall BDB_SYMBOL(msgcall) +void bdb_errcall( const DB_ENV *env, const char *pfx, const char * msg ); +void bdb_msgcall( const DB_ENV *env, const char * msg ); +#endif + +#ifdef HAVE_EBCDIC +#define ebcdic_dberror BDB_SYMBOL(ebcdic_dberror) + +char *ebcdic_dberror( int rc ); +#define db_strerror(x) ebcdic_dberror(x) +#endif + +/* + * filterentry.c + */ +#define bdb_filter_candidates BDB_SYMBOL(filter_candidates) + +int bdb_filter_candidates( + Operation *op, + DB_TXN *txn, + Filter *f, + ID *ids, + ID *tmp, + ID *stack ); + +/* + * id2entry.c + */ +#define bdb_id2entry BDB_SYMBOL(id2entry) +#define bdb_id2entry_add BDB_SYMBOL(id2entry_add) +#define bdb_id2entry_update BDB_SYMBOL(id2entry_update) +#define bdb_id2entry_delete BDB_SYMBOL(id2entry_delete) + +int bdb_id2entry_add( + BackendDB *be, + DB_TXN *tid, + Entry *e ); + +int bdb_id2entry_update( + BackendDB *be, + DB_TXN *tid, + Entry *e ); + +int bdb_id2entry_delete( + BackendDB *be, + DB_TXN *tid, + Entry *e); + +#ifdef SLAP_ZONE_ALLOC +#else +int bdb_id2entry( + BackendDB *be, + DB_TXN *tid, + ID id, + Entry **e); +#endif + +#define bdb_entry_free BDB_SYMBOL(entry_free) +#define bdb_entry_return BDB_SYMBOL(entry_return) +#define bdb_entry_release BDB_SYMBOL(entry_release) +#define bdb_entry_get BDB_SYMBOL(entry_get) + +void bdb_entry_free ( Entry *e ); +#ifdef SLAP_ZONE_ALLOC +int bdb_entry_return( struct bdb_info *bdb, Entry *e, int seqno ); +#else +int bdb_entry_return( Entry *e ); +#endif +BI_entry_release_rw bdb_entry_release; +BI_entry_get_rw bdb_entry_get; + + +/* + * idl.c + */ + +#define bdb_idl_cache_get BDB_SYMBOL(idl_cache_get) +#define bdb_idl_cache_put BDB_SYMBOL(idl_cache_put) +#define bdb_idl_cache_del BDB_SYMBOL(idl_cache_del) +#define bdb_idl_cache_add_id BDB_SYMBOL(idl_cache_add_id) +#define bdb_idl_cache_del_id BDB_SYMBOL(idl_cache_del_id) + +int bdb_idl_cache_get( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids ); + +void +bdb_idl_cache_put( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID *ids, + int rc ); + +void +bdb_idl_cache_del( + struct bdb_info *bdb, + DB *db, + DBT *key ); + +void +bdb_idl_cache_add_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ); + +void +bdb_idl_cache_del_id( + struct bdb_info *bdb, + DB *db, + DBT *key, + ID id ); + +#define bdb_idl_first BDB_SYMBOL(idl_first) +#define bdb_idl_next BDB_SYMBOL(idl_next) +#define bdb_idl_search BDB_SYMBOL(idl_search) +#define bdb_idl_insert BDB_SYMBOL(idl_insert) +#define bdb_idl_delete BDB_SYMBOL(idl_delete) +#define bdb_idl_intersection BDB_SYMBOL(idl_intersection) +#define bdb_idl_union BDB_SYMBOL(idl_union) +#define bdb_idl_sort BDB_SYMBOL(idl_sort) +#define bdb_idl_append BDB_SYMBOL(idl_append) +#define bdb_idl_append_one BDB_SYMBOL(idl_append_one) + +#define bdb_idl_fetch_key BDB_SYMBOL(idl_fetch_key) +#define bdb_idl_insert_key BDB_SYMBOL(idl_insert_key) +#define bdb_idl_delete_key BDB_SYMBOL(idl_delete_key) + +unsigned bdb_idl_search( ID *ids, ID id ); + +int bdb_idl_fetch_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID *ids, + DBC **saved_cursor, + int get_flag ); + +int bdb_idl_insert( ID *ids, ID id ); +int bdb_idl_delete( ID *ids, ID id ); + +int bdb_idl_insert_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ); + +int bdb_idl_delete_key( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ); + +int +bdb_idl_intersection( + ID *a, + ID *b ); + +int +bdb_idl_union( + ID *a, + ID *b ); + +ID bdb_idl_first( ID *ids, ID *cursor ); +ID bdb_idl_next( ID *ids, ID *cursor ); + +void bdb_idl_sort( ID *ids, ID *tmp ); +int bdb_idl_append( ID *a, ID *b ); +int bdb_idl_append_one( ID *ids, ID id ); + + +/* + * index.c + */ +#define bdb_index_mask BDB_SYMBOL(index_mask) +#define bdb_index_param BDB_SYMBOL(index_param) +#define bdb_index_values BDB_SYMBOL(index_values) +#define bdb_index_entry BDB_SYMBOL(index_entry) +#define bdb_index_recset BDB_SYMBOL(index_recset) +#define bdb_index_recrun BDB_SYMBOL(index_recrun) + +extern AttrInfo * +bdb_index_mask LDAP_P(( + Backend *be, + AttributeDescription *desc, + struct berval *name )); + +extern int +bdb_index_param LDAP_P(( + Backend *be, + AttributeDescription *desc, + int ftype, + DB **db, + slap_mask_t *mask, + struct berval *prefix )); + +extern int +bdb_index_values LDAP_P(( + Operation *op, + DB_TXN *txn, + AttributeDescription *desc, + BerVarray vals, + ID id, + int opid )); + +extern int +bdb_index_recset LDAP_P(( + struct bdb_info *bdb, + Attribute *a, + AttributeType *type, + struct berval *tags, + IndexRec *ir )); + +extern int +bdb_index_recrun LDAP_P(( + Operation *op, + struct bdb_info *bdb, + IndexRec *ir, + ID id, + int base )); + +int bdb_index_entry LDAP_P(( Operation *op, DB_TXN *t, int r, Entry *e )); + +#define bdb_index_entry_add(op,t,e) \ + bdb_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e)) +#define bdb_index_entry_del(op,t,e) \ + bdb_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e)) + +/* + * key.c + */ +#define bdb_key_read BDB_SYMBOL(key_read) +#define bdb_key_change BDB_SYMBOL(key_change) + +extern int +bdb_key_read( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID *ids, + DBC **saved_cursor, + int get_flags ); + +extern int +bdb_key_change( + Backend *be, + DB *db, + DB_TXN *txn, + struct berval *k, + ID id, + int op ); + +/* + * nextid.c + */ +#define bdb_next_id BDB_SYMBOL(next_id) +#define bdb_last_id BDB_SYMBOL(last_id) + +int bdb_next_id( BackendDB *be, ID *id ); +int bdb_last_id( BackendDB *be, DB_TXN *tid ); + +/* + * modify.c + */ +#define bdb_modify_internal BDB_SYMBOL(modify_internal) + +int bdb_modify_internal( + Operation *op, + DB_TXN *tid, + Modifications *modlist, + Entry *e, + const char **text, + char *textbuf, + size_t textlen ); + +/* + * monitor.c + */ + +#define bdb_monitor_db_init BDB_SYMBOL(monitor_db_init) +#define bdb_monitor_db_open BDB_SYMBOL(monitor_db_open) +#define bdb_monitor_db_close BDB_SYMBOL(monitor_db_close) +#define bdb_monitor_db_destroy BDB_SYMBOL(monitor_db_destroy) + +int bdb_monitor_db_init( BackendDB *be ); +int bdb_monitor_db_open( BackendDB *be ); +int bdb_monitor_db_close( BackendDB *be ); +int bdb_monitor_db_destroy( BackendDB *be ); + +#ifdef BDB_MONITOR_IDX +#define bdb_monitor_idx_add BDB_SYMBOL(monitor_idx_add) +int +bdb_monitor_idx_add( + struct bdb_info *bdb, + AttributeDescription *desc, + slap_mask_t type ); +#endif /* BDB_MONITOR_IDX */ + +/* + * cache.c + */ +#define bdb_cache_entry_db_unlock BDB_SYMBOL(cache_entry_db_unlock) +#define bdb_cache_return_entry_rw BDB_SYMBOL(cache_return_entry_rw) + +#define bdb_cache_entryinfo_lock(e) \ + ldap_pvt_thread_mutex_lock( &(e)->bei_kids_mutex ) +#define bdb_cache_entryinfo_unlock(e) \ + ldap_pvt_thread_mutex_unlock( &(e)->bei_kids_mutex ) +#define bdb_cache_entryinfo_trylock(e) \ + ldap_pvt_thread_mutex_trylock( &(e)->bei_kids_mutex ) + +/* What a mess. Hopefully the current cache scheme will stabilize + * and we can trim out all of this stuff. + */ +void bdb_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, + int rw, DB_LOCK *lock ); +#define bdb_cache_return_entry_r(bdb, e, l) \ + bdb_cache_return_entry_rw((bdb), (e), 0, (l)) +#define bdb_cache_return_entry_w(bdb, e, l) \ + bdb_cache_return_entry_rw((bdb), (e), 1, (l)) +#if 0 +void bdb_unlocked_cache_return_entry_rw( struct bdb_info *bdb, Entry *e, int rw ); +#else +#define bdb_unlocked_cache_return_entry_rw( a, b, c ) ((void)0) +#endif +#define bdb_unlocked_cache_return_entry_r( c, e ) \ + bdb_unlocked_cache_return_entry_rw((c), (e), 0) +#define bdb_unlocked_cache_return_entry_w( c, e ) \ + bdb_unlocked_cache_return_entry_rw((c), (e), 1) + +#define bdb_cache_add BDB_SYMBOL(cache_add) +#define bdb_cache_children BDB_SYMBOL(cache_children) +#define bdb_cache_delete BDB_SYMBOL(cache_delete) +#define bdb_cache_delete_cleanup BDB_SYMBOL(cache_delete_cleanup) +#define bdb_cache_find_id BDB_SYMBOL(cache_find_id) +#define bdb_cache_find_ndn BDB_SYMBOL(cache_find_ndn) +#define bdb_cache_find_parent BDB_SYMBOL(cache_find_parent) +#define bdb_cache_modify BDB_SYMBOL(cache_modify) +#define bdb_cache_modrdn BDB_SYMBOL(cache_modrdn) +#define bdb_cache_release_all BDB_SYMBOL(cache_release_all) +#define bdb_cache_delete_entry BDB_SYMBOL(cache_delete_entry) +#define bdb_cache_deref BDB_SYMBOL(cache_deref) + +int bdb_cache_children( + Operation *op, + DB_TXN *txn, + Entry *e +); +int bdb_cache_add( + struct bdb_info *bdb, + EntryInfo *pei, + Entry *e, + struct berval *nrdn, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_modrdn( + struct bdb_info *bdb, + Entry *e, + struct berval *nrdn, + Entry *new, + EntryInfo *ein, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_modify( + struct bdb_info *bdb, + Entry *e, + Attribute *newAttrs, + DB_TXN *txn, + DB_LOCK *lock +); +int bdb_cache_find_ndn( + Operation *op, + DB_TXN *txn, + struct berval *ndn, + EntryInfo **res +); + +#define ID_LOCKED 1 +#define ID_NOCACHE 2 +#define ID_NOENTRY 4 +#define ID_CHKPURGE 8 +int bdb_cache_find_id( + Operation *op, + DB_TXN *tid, + ID id, + EntryInfo **eip, + int flag, + DB_LOCK *lock +); +int +bdb_cache_find_parent( + Operation *op, + DB_TXN *txn, + ID id, + EntryInfo **res +); +int bdb_cache_delete( + struct bdb_info *bdb, + Entry *e, + DB_TXN *txn, + DB_LOCK *lock +); +void bdb_cache_delete_cleanup( + Cache *cache, + EntryInfo *ei +); +void bdb_cache_release_all( Cache *cache ); +void bdb_cache_deref( EntryInfo *ei ); + +#ifdef BDB_HIER +int hdb_cache_load( + struct bdb_info *bdb, + EntryInfo *ei, + EntryInfo **res +); +#endif + +#define bdb_cache_entry_db_relock BDB_SYMBOL(cache_entry_db_relock) +int bdb_cache_entry_db_relock( + struct bdb_info *bdb, + DB_TXN *txn, + EntryInfo *ei, + int rw, + int tryOnly, + DB_LOCK *lock ); + +int bdb_cache_entry_db_unlock( + struct bdb_info *bdb, + DB_LOCK *lock ); + +#define bdb_reader_get BDB_SYMBOL(reader_get) +#define bdb_reader_flush BDB_SYMBOL(reader_flush) +int bdb_reader_get( Operation *op, DB_ENV *env, DB_TXN **txn ); +void bdb_reader_flush( DB_ENV *env ); + +/* + * trans.c + */ +#define bdb_trans_backoff BDB_SYMBOL(trans_backoff) + +void +bdb_trans_backoff( int num_retries ); + +/* + * former external.h + */ + +#define bdb_back_initialize BDB_SYMBOL(back_initialize) +#define bdb_db_config BDB_SYMBOL(db_config) +#define bdb_add BDB_SYMBOL(add) +#define bdb_bind BDB_SYMBOL(bind) +#define bdb_compare BDB_SYMBOL(compare) +#define bdb_delete BDB_SYMBOL(delete) +#define bdb_modify BDB_SYMBOL(modify) +#define bdb_modrdn BDB_SYMBOL(modrdn) +#define bdb_search BDB_SYMBOL(search) +#define bdb_extended BDB_SYMBOL(extended) +#define bdb_referrals BDB_SYMBOL(referrals) +#define bdb_operational BDB_SYMBOL(operational) +#define bdb_hasSubordinates BDB_SYMBOL(hasSubordinates) +#define bdb_tool_entry_open BDB_SYMBOL(tool_entry_open) +#define bdb_tool_entry_close BDB_SYMBOL(tool_entry_close) +#define bdb_tool_entry_first_x BDB_SYMBOL(tool_entry_first_x) +#define bdb_tool_entry_next BDB_SYMBOL(tool_entry_next) +#define bdb_tool_entry_get BDB_SYMBOL(tool_entry_get) +#define bdb_tool_entry_put BDB_SYMBOL(tool_entry_put) +#define bdb_tool_entry_reindex BDB_SYMBOL(tool_entry_reindex) +#define bdb_tool_dn2id_get BDB_SYMBOL(tool_dn2id_get) +#define bdb_tool_entry_modify BDB_SYMBOL(tool_entry_modify) +#define bdb_tool_idl_add BDB_SYMBOL(tool_idl_add) + +extern BI_init bdb_back_initialize; + +extern BI_db_config bdb_db_config; + +extern BI_op_add bdb_add; +extern BI_op_bind bdb_bind; +extern BI_op_compare bdb_compare; +extern BI_op_delete bdb_delete; +extern BI_op_modify bdb_modify; +extern BI_op_modrdn bdb_modrdn; +extern BI_op_search bdb_search; +extern BI_op_extended bdb_extended; + +extern BI_chk_referrals bdb_referrals; + +extern BI_operational bdb_operational; + +extern BI_has_subordinates bdb_hasSubordinates; + +/* tools.c */ +extern BI_tool_entry_open bdb_tool_entry_open; +extern BI_tool_entry_close bdb_tool_entry_close; +extern BI_tool_entry_first_x bdb_tool_entry_first_x; +extern BI_tool_entry_next bdb_tool_entry_next; +extern BI_tool_entry_get bdb_tool_entry_get; +extern BI_tool_entry_put bdb_tool_entry_put; +extern BI_tool_entry_reindex bdb_tool_entry_reindex; +extern BI_tool_dn2id_get bdb_tool_dn2id_get; +extern BI_tool_entry_modify bdb_tool_entry_modify; + +int bdb_tool_idl_add( BackendDB *be, DB *db, DB_TXN *txn, DBT *key, ID id ); + +LDAP_END_DECL + +#endif /* _PROTO_BDB_H */ diff --git a/servers/slapd/back-bdb/referral.c b/servers/slapd/back-bdb/referral.c new file mode 100644 index 0000000..ad51b2d --- /dev/null +++ b/servers/slapd/back-bdb/referral.c @@ -0,0 +1,152 @@ +/* referral.c - BDB backend referral handler */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" + +int +bdb_referrals( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + Entry *e = NULL; + EntryInfo *ei; + int rc = LDAP_SUCCESS; + + DB_TXN *rtxn; + DB_LOCK lock; + + 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 = bdb_reader_get(op, bdb->bi_dbenv, &rtxn); + switch(rc) { + case 0: + break; + default: + return LDAP_OTHER; + } + +dn2entry_retry: + /* get entry */ + rc = bdb_dn2entry( op, rtxn, &op->o_req_ndn, &ei, 1, &lock ); + + /* bdb_dn2entry() may legally leave ei == NULL + * if rc != 0 and rc != DB_NOTFOUND + */ + if ( ei ) { + e = ei->bei_e; + } + + switch(rc) { + case DB_NOTFOUND: + case 0: + break; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + return LDAP_BUSY; + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_referrals) + ": dn2entry failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + rs->sr_text = "internal error"; + return LDAP_OTHER; + } + + if ( rc == DB_NOTFOUND ) { + rc = LDAP_SUCCESS; + rs->sr_matched = NULL; + if ( e != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 ); + } + } + + bdb_cache_return_entry_r (bdb, e, &lock); + 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; + } + return rc; + } + + 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(bdb_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 ); + } + + bdb_cache_return_entry_r(bdb, e, &lock); + return rc; +} diff --git a/servers/slapd/back-bdb/search.c b/servers/slapd/back-bdb/search.c new file mode 100644 index 0000000..04d76b2 --- /dev/null +++ b/servers/slapd/back-bdb/search.c @@ -0,0 +1,1388 @@ +/* search.c - search operation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "idl.h" + +static int base_candidate( + BackendDB *be, + Entry *e, + ID *ids ); + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + DB_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, + DB_TXN *txn, + DB_LOCK *lock, + ID *tmp, + ID *visited ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + struct berval ndn; + EntryInfo *ei; + DB_LOCK lockr; + + 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 (BDB_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 && bdb_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 ( bdb_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 = bdb_dn2entry( op, txn, &ndn, &ei, + 0, &lockr ); + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return NULL; + + if ( ei ) { + e = ei->bei_e; + } else { + e = NULL; + } + + if (!e) { + 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. + */ + bdb_cache_return_entry_r( bdb, *matched, lock); + *lock = lockr; + + /* We found a regular entry. Return this to the caller. The + * entry is still locked for Read. + */ + 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. Adds + * the dereferenced entries to the "ids" list. Requires "stack" to be + * able to hold 8 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, + DB_TXN *txn, + ID *ids, + ID *scopes, + ID *stack ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + ID *aliases, *curscop, *subscop, *visited, *newsubs, *oldsubs, *tmp; + ID cursora, ida, cursoro, ido, *subscop2; + Entry *matched, *a; + EntryInfo *ei; + struct berval bv_alias = BER_BVC( "alias" ); + AttributeAssertion aa_alias = ATTRIBUTEASSERTION_INIT; + Filter af; + DB_LOCK locka, lockr; + int first = 1; + + aliases = stack; /* IDL of all aliases in the database */ + curscop = aliases + BDB_IDL_DB_SIZE; /* Aliases in the current scope */ + subscop = curscop + BDB_IDL_DB_SIZE; /* The current scope */ + visited = subscop + BDB_IDL_DB_SIZE; /* IDs we've seen in this search */ + newsubs = visited + BDB_IDL_DB_SIZE; /* New subtrees we've added */ + oldsubs = newsubs + BDB_IDL_DB_SIZE; /* Subtrees added previously */ + tmp = oldsubs + BDB_IDL_DB_SIZE; /* Scratch space for deref_base() */ + + /* A copy of subscop, because subscop gets clobbered by + * the bdb_idl_union/intersection routines + */ + subscop2 = tmp + BDB_IDL_DB_SIZE; + + 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 */ + BDB_IDL_ZERO( aliases ); + rs->sr_err = bdb_filter_candidates( op, txn, &af, aliases, + curscop, visited ); + if (rs->sr_err != LDAP_SUCCESS || BDB_IDL_IS_ZERO( aliases )) { + return rs->sr_err; + } + oldsubs[0] = 1; + oldsubs[1] = e->e_id; + + BDB_IDL_ZERO( ids ); + BDB_IDL_ZERO( visited ); + BDB_IDL_ZERO( newsubs ); + + cursoro = 0; + ido = bdb_idl_first( oldsubs, &cursoro ); + + for (;;) { + /* Set curscop to only the aliases in the current scope. Start with + * all the aliases, obtain the IDL for the current scope, and then + * get the intersection of these two IDLs. Add the current scope + * to the cumulative list of candidates. + */ + BDB_IDL_CPY( curscop, aliases ); + rs->sr_err = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), subscop, + subscop2+BDB_IDL_DB_SIZE ); + + if (first) { + first = 0; + } else { + bdb_cache_return_entry_r (bdb, e, &locka); + } + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + + BDB_IDL_CPY(subscop2, subscop); + rs->sr_err = bdb_idl_intersection(curscop, subscop); + bdb_idl_union( ids, subscop2 ); + + /* Dereference all of the aliases in the current scope. */ + cursora = 0; + for (ida = bdb_idl_first(curscop, &cursora); ida != NOID; + ida = bdb_idl_next(curscop, &cursora)) + { + ei = NULL; +retry1: + rs->sr_err = bdb_cache_find_id(op, txn, + ida, &ei, 0, &lockr ); + if (rs->sr_err != LDAP_SUCCESS) { + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + goto retry1; + continue; + } + a = ei->bei_e; + + /* 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)) { + bdb_cache_return_entry_r (bdb, a, &lockr); + continue; + } + + /* Actually dereference the alias */ + BDB_IDL_ZERO(tmp); + a = deref_base( op, rs, a, &matched, txn, &lockr, + tmp, visited ); + if (a) { + /* If the target was not already in our current candidates, + * make note of it in the newsubs list. Also + * set it in the scopes list so that bdb_search + * can check it. + */ + if (bdb_idl_insert(ids, a->e_id) == 0) { + bdb_idl_insert(newsubs, a->e_id); + bdb_idl_insert(scopes, a->e_id); + } + bdb_cache_return_entry_r( bdb, a, &lockr); + + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + return rs->sr_err; + } else if (matched) { + /* Alias could not be dereferenced, or it deref'd to + * an ID we've already seen. Ignore it. + */ + bdb_cache_return_entry_r( bdb, matched, &lockr ); + 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 = bdb_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 (BDB_IDL_IS_ZERO(newsubs)) break; + BDB_IDL_CPY(oldsubs, newsubs); + BDB_IDL_ZERO(newsubs); + cursoro = 0; + ido = bdb_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. + * Set the name so that the scope's IDL can be retrieved. + */ + ei = NULL; +sameido: + rs->sr_err = bdb_cache_find_id(op, txn, ido, &ei, + 0, &locka ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( rs->sr_err == DB_LOCK_DEADLOCK ) + return rs->sr_err; + if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + goto sameido; + goto nextido; + } + e = ei->bei_e; + } + 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 +bdb_get_nextid(struct bdb_info *bdb, DB_TXN *ltid, ID *cursor) +{ + DBC *curs; + DBT key, data; + ID id, nid; + int rc; + + id = *cursor + 1; + BDB_ID2DISK( id, &nid ); + rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, ltid, &curs, bdb->bi_db_opflags ); + if ( rc ) + return rc; + key.data = &nid; + key.size = key.ulen = sizeof(ID); + key.flags = DB_DBT_USERMEM; + data.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL; + data.dlen = data.ulen = 0; + rc = curs->c_get( curs, &key, &data, DB_SET_RANGE ); + curs->c_close( curs ); + if ( rc ) + return rc; + BDB_DISK2ID( &nid, cursor ); + return 0; +} + +int +bdb_search( Operation *op, SlapReply *rs ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + ID id, cursor; + ID lastid = NOID; + ID candidates[BDB_IDL_UM_SIZE]; + ID scopes[BDB_IDL_DB_SIZE]; + Entry *e = NULL, base, *e_root; + Entry *matched = NULL; + EntryInfo *ei; + AttributeName *attrs; + struct berval realbase = BER_BVNULL; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + int tentries = 0; + unsigned nentries = 0; + int idflag = 0; + + DB_LOCK lock; + struct bdb_op_info *opinfo = NULL; + DB_TXN *ltid = NULL; + OpExtra *oex; + + Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(bdb_search) "\n", 0, 0, 0); + attrs = op->oq_search.rs_attrs; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == bdb ) + break; + } + opinfo = (struct bdb_op_info *) oex; + + manageDSAit = get_manageDSAit( op ); + + if ( opinfo && opinfo->boi_txn ) { + ltid = opinfo->boi_txn; + } else { + rs->sr_err = bdb_reader_get( op, bdb->bi_dbenv, <id ); + + switch(rs->sr_err) { + case 0: + break; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + } + + e_root = bdb->bi_cache.c_dntree.bei_e; + if ( op->o_req_ndn.bv_len == 0 ) { + /* DIT root special case */ + ei = e_root->e_private; + rs->sr_err = LDAP_SUCCESS; + } else { + if ( op->ors_deref & LDAP_DEREF_FINDING ) { + BDB_IDL_ZERO(candidates); + } +dn2entry_retry: + /* get entry with reader lock */ + rs->sr_err = bdb_dn2entry( op, ltid, &op->o_req_ndn, &ei, + 1, &lock ); + } + + switch(rs->sr_err) { + case DB_NOTFOUND: + matched = ei->bei_e; + break; + case 0: + e = ei->bei_e; + break; + case DB_LOCK_DEADLOCK: + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto dn2entry_retry; + } + opinfo->boi_err = rs->sr_err; + /* FALLTHRU */ + case LDAP_BUSY: + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + return LDAP_BUSY; + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; + default: + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return rs->sr_err; + } + + 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, &lock, + candidates, NULL ); + if ( e ) { + build_new_dn( &op->o_req_ndn, &e->e_nname, &stub, + op->o_tmpmemctx ); + bdb_cache_return_entry_r (bdb, e, &lock); + matched = NULL; + goto dn2entry_retry; + } + } else if ( e && is_entry_alias( e )) { + e = deref_base( op, rs, e, &matched, ltid, &lock, + 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 == DB_NOTFOUND ) + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = matched_dn.bv_val; + } + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, matched); +#endif + bdb_cache_return_entry_r (bdb, matched, &lock); + 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 { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, matched); +#endif + 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; + } + return rs->sr_err; + } + + /* 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; + } + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + send_ldap_result( op, rs ); + return rs->sr_err; + } + + if ( !manageDSAit && e != e_root && is_entry_referral( e ) ) { + /* entry is a referral, don't allow add */ + 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; + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e, &lock ); + 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(bdb_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; + return 1; + } + + if ( get_assert( op ) && + ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + send_ldap_result( op, rs ); + return 1; + } + + /* 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; + +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + if ( e != e_root ) { + bdb_cache_return_entry_r(bdb, e, &lock); + } + e = NULL; + + /* select candidates */ + if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) { + rs->sr_err = base_candidate( op->o_bd, &base, candidates ); + + } else { +cand_retry: + BDB_IDL_ZERO( candidates ); + BDB_IDL_ZERO( scopes ); + rs->sr_err = search_candidates( op, rs, &base, + ltid, candidates, scopes ); + if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto cand_retry; + } + opinfo->boi_err = rs->sr_err; + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + return LDAP_BUSY; + } + } + + /* start cursor at beginning of candidates. + */ + cursor = 0; + + if ( candidates[0] == 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_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 && + BDB_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 = BDB_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 = bdb_idl_first( candidates, &cursor ); + if ( id == NOID ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": no paged results candidates\n", + 0, 0, 0 ); + send_paged_response( op, rs, &lastid, 0 ); + + rs->sr_err = LDAP_OTHER; + goto done; + } + nentries = ps->ps_count; + if ( id == (ID)ps->ps_cookie ) + id = bdb_idl_next( candidates, &cursor ); + goto loop_begin; + } + + for ( id = bdb_idl_first( candidates, &cursor ); + id != NOID ; id = bdb_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; + } + + /* If we inspect more entries than will + * fit into the entry cache, stop caching + * any subsequent entries + */ + nentries++; + if ( nentries > bdb->bi_cache.c_maxsize && !idflag ) { + idflag = ID_NOCACHE; + } + +fetch_entry_retry: + /* get the entry with reader lock */ + ei = NULL; + rs->sr_err = bdb_cache_find_id( op, ltid, + id, &ei, idflag, &lock ); + + if (rs->sr_err == LDAP_BUSY) { + rs->sr_text = "ldap server busy"; + send_ldap_result( op, rs ); + goto done; + + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( !opinfo ) { + ltid->flags &= ~TXN_DEADLOCK; + goto fetch_entry_retry; + } +txnfail: + opinfo->boi_err = rs->sr_err; + send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" ); + goto done; + + } else if ( rs->sr_err == DB_LOCK_NOTGRANTED ) + { + goto fetch_entry_retry; + } else if ( rs->sr_err == LDAP_OTHER ) { + rs->sr_text = "internal error"; + send_ldap_result( op, rs ); + goto done; + } + + if ( ei && rs->sr_err == LDAP_SUCCESS ) { + e = ei->bei_e; + } else { + e = NULL; + } + + if ( e == NULL ) { + if( !BDB_IDL_IS_RANGE(candidates) ) { + /* only complain for non-range IDLs */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": candidate %ld not found\n", + (long) id, 0, 0 ); + } else { + /* get the next ID from the DB */ +id_retry: + rs->sr_err = bdb_get_nextid( bdb, ltid, &cursor ); + if ( rs->sr_err == DB_NOTFOUND ) { + break; + } else if ( rs->sr_err == DB_LOCK_DEADLOCK ) { + if ( opinfo ) + goto txnfail; + ltid->flags &= ~TXN_DEADLOCK; + goto id_retry; + } else if ( rs->sr_err == DB_LOCK_NOTGRANTED ) { + goto id_retry; + } + 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? + * + * Note that we don't lock access to the bei_parent pointer. + * Since only leaf nodes can be deleted, the parent of any + * node will always be a valid node. Also since we have + * a Read lock on the data, it cannot be renamed out of the + * scope while we are looking at it, and unless we're using + * BDB_HIER, its parents cannot be moved either. + */ + scopeok = 0; + switch( op->ors_scope ) { + case LDAP_SCOPE_BASE: + /* This is always true, yes? */ + if ( id == base.e_id ) scopeok = 1; + break; + + case LDAP_SCOPE_ONELEVEL: + if ( ei->bei_parent->bei_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: { + EntryInfo *tmp; + for ( tmp = BEI(e); tmp; tmp = tmp->bei_parent ) { + if ( tmp->bei_id == base.e_id ) { + scopeok = 1; + break; + } + } + } 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; + } + + /* scopes is only non-empty for onelevel or subtree */ + if ( !scopeok && BDB_IDL_N(scopes) ) { + unsigned x; + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) { + x = bdb_idl_search( scopes, e->e_id ); + if ( scopes[x] == e->e_id ) scopeok = 1; + } else { + /* subtree, walk up the tree */ + EntryInfo *tmp = BEI(e); + for (;tmp->bei_parent; tmp=tmp->bei_parent) { + x = bdb_idl_search( scopes, tmp->bei_id ); + if ( scopes[x] == tmp->bei_id ) { + scopeok = 1; + break; + } + } + } + } + } + + /* Not in scope, ignore it */ + if ( !scopeok ) + { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": %ld scope not okay\n", + (long) id, 0, 0 ); + goto loop_continue; + } + + /* + * 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 ) ) + { + struct bdb_op_info bois; + struct bdb_lock_info blis; + 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 ); + + /* Must set lockinfo so that entry_release will work */ + if (!opinfo) { + bois.boi_oe.oe_key = bdb; + bois.boi_txn = NULL; + bois.boi_err = 0; + bois.boi_acl_cache = op->o_do_not_cache; + bois.boi_flag = BOI_DONTFREE; + bois.boi_locks = &blis; + blis.bli_next = NULL; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &bois.boi_oe, + oe_next ); + } else { + blis.bli_next = opinfo->boi_locks; + opinfo->boi_locks = &blis; + } + blis.bli_id = e->e_id; + blis.bli_lock = lock; + blis.bli_flag = BLI_DONTFREE; + + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + + send_search_reference( op, rs ); + + if ( blis.bli_flag ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r(bdb, e, &lock); + if ( opinfo ) { + opinfo->boi_locks = blis.bli_next; + } else { + LDAP_SLIST_REMOVE( &op->o_extra, &bois.boi_oe, + OpExtra, oe_next ); + } + } + rs->sr_entry = NULL; + e = NULL; + + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + + goto loop_continue; + } + + if ( !manageDSAit && is_entry_glue( e )) { + 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 ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e, &lock ); + e = NULL; + send_paged_response( op, rs, &lastid, tentries ); + goto done; + } + lastid = id; + } + + if (e) { + struct bdb_op_info bois; + struct bdb_lock_info blis; + + /* Must set lockinfo so that entry_release will work */ + if (!opinfo) { + bois.boi_oe.oe_key = bdb; + bois.boi_txn = NULL; + bois.boi_err = 0; + bois.boi_acl_cache = op->o_do_not_cache; + bois.boi_flag = BOI_DONTFREE; + bois.boi_locks = &blis; + blis.bli_next = NULL; + LDAP_SLIST_INSERT_HEAD( &op->o_extra, &bois.boi_oe, + oe_next ); + } else { + blis.bli_next = opinfo->boi_locks; + opinfo->boi_locks = &blis; + } + blis.bli_id = e->e_id; + blis.bli_lock = lock; + blis.bli_flag = BLI_DONTFREE; + + /* 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 = REP_ENTRY_MUSTRELEASE; + rs->sr_err = LDAP_SUCCESS; + rs->sr_err = send_search_entry( op, rs ); + rs->sr_attrs = NULL; + rs->sr_entry = NULL; + + /* send_search_entry will usually free it. + * an overlay might leave its own copy here; + * bli_flag will be 0 if lock was already released. + */ + if ( blis.bli_flag ) { +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r(bdb, e, &lock); + if ( opinfo ) { + opinfo->boi_locks = blis.bli_next; + } else { + LDAP_SLIST_REMOVE( &op->o_extra, &bois.boi_oe, + OpExtra, oe_next ); + } + } + e = NULL; + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: /* entry sent ok */ + break; + default: /* entry not sent */ + break; + case LDAP_BUSY: + send_ldap_result( op, rs ); + goto done; + case LDAP_UNAVAILABLE: + case LDAP_SIZELIMIT_EXCEEDED: + if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) { + rs->sr_ref = rs->sr_v2ref; + send_ldap_result( op, rs ); + rs->sr_err = LDAP_SUCCESS; + + } else { + rs->sr_err = LDAP_OTHER; + } + goto done; + } + } + + } else { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(bdb_search) + ": %ld does not match filter\n", + (long) id, 0, 0 ); + } + +loop_continue: + if( e != NULL ) { + /* free reader lock */ +#ifdef SLAP_ZONE_ALLOC + slap_zn_runlock(bdb->bi_cache.c_zctx, e); +#endif + bdb_cache_return_entry_r( bdb, e , &lock ); + 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( 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 bdb_info *bdb = (struct bdb_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 = bdb->bi_search_stack; + } + + if ( !ret ) { + ret = ch_malloc( bdb->bi_search_stack_depth * BDB_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 { + bdb->bi_search_stack = ret; + } + } + return ret; +} + +static int search_candidates( + Operation *op, + SlapReply *rs, + Entry *e, + DB_TXN *txn, + ID *ids, + ID *scopes ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + int rc, depth = 1; + Filter f, rf, xf, nf; + ID *stack; + AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT; + Filter sf; + 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)(objectClass=alias)](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 ); + + xf.f_or = op->oq_search.rs_filter; + xf.f_choice = LDAP_FILTER_OR; + xf.f_next = NULL; + + /* 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 = xf.f_or; + xf.f_or = &rf; + depth++; + } + } + + f.f_next = NULL; + f.f_choice = LDAP_FILTER_AND; + f.f_and = &nf; + /* Dummy; we compute scope separately now */ + nf.f_choice = SLAPD_FILTER_COMPUTED; + nf.f_result = LDAP_SUCCESS; + nf.f_next = ( xf.f_or == op->oq_search.rs_filter ) + ? op->oq_search.rs_filter : &xf ; + /* Filter depth increased again, adding dummy clause */ + 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 = nf.f_next; + nf.f_next = &sf; + } + + /* Allocate IDL stack, plus 1 more for former tmp */ + if ( depth+1 > bdb->bi_search_stack_depth ) { + stack = ch_malloc( (depth + 1) * BDB_IDL_UM_SIZE * sizeof( ID ) ); + } else { + stack = search_stack( op ); + } + + if( op->ors_deref & LDAP_DEREF_SEARCHING ) { + rc = search_aliases( op, rs, e, txn, ids, scopes, stack ); + if ( BDB_IDL_IS_ZERO( ids ) && rc == LDAP_SUCCESS ) + rc = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), ids, stack ); + } else { + rc = bdb_dn2idl( op, txn, &e->e_nname, BEI(e), ids, stack ); + } + + if ( rc == LDAP_SUCCESS ) { + rc = bdb_filter_candidates( op, txn, &f, ids, + stack, stack+BDB_IDL_UM_SIZE ); + } + + if ( depth+1 > bdb->bi_search_stack_depth ) { + ch_free( stack ); + } + + if( rc ) { + Debug(LDAP_DEBUG_TRACE, + "bdb_search_candidates: failed (rc=%d)\n", + rc, NULL, NULL ); + + } else { + Debug(LDAP_DEBUG_TRACE, + "bdb_search_candidates: id=%ld first=%ld last=%ld\n", + (long) ids[0], + (long) BDB_IDL_FIRST(ids), + (long) BDB_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-bdb/tools.c b/servers/slapd/back-bdb/tools.c new file mode 100644 index 0000000..2345d65 --- /dev/null +++ b/servers/slapd/back-bdb/tools.c @@ -0,0 +1,1327 @@ +/* tools.c - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "idl.h" + +static DBC *cursor = NULL; +static DBT key, data; +static EntryHeader eh; +static ID nid, previd = NOID; +static char ehbuf[16]; + +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 int index_nattrs; + +static struct berval *tool_base; +static int tool_scope; +static Filter *tool_filter; +static Entry *tool_next_entry; + +#ifdef BDB_TOOL_IDL_CACHING +#define bdb_tool_idl_cmp BDB_SYMBOL(tool_idl_cmp) +#define bdb_tool_idl_flush_one BDB_SYMBOL(tool_idl_flush_one) +#define bdb_tool_idl_flush BDB_SYMBOL(tool_idl_flush) + +static int bdb_tool_idl_flush( BackendDB *be ); + +#define IDBLOCK 1024 + +typedef struct bdb_tool_idl_cache_entry { + struct bdb_tool_idl_cache_entry *next; + ID ids[IDBLOCK]; +} bdb_tool_idl_cache_entry; + +typedef struct bdb_tool_idl_cache { + struct berval kstr; + bdb_tool_idl_cache_entry *head, *tail; + ID first, last; + int count; +} bdb_tool_idl_cache; + +static bdb_tool_idl_cache_entry *bdb_tool_idl_free_list; +#endif /* BDB_TOOL_IDL_CACHING */ + +static ID bdb_tool_ix_id; +static Operation *bdb_tool_ix_op; +static int *bdb_tool_index_threads, bdb_tool_index_tcount; +static void *bdb_tool_index_rec; +static struct bdb_info *bdb_tool_info; +static ldap_pvt_thread_mutex_t bdb_tool_index_mutex; +static ldap_pvt_thread_cond_t bdb_tool_index_cond_main; +static ldap_pvt_thread_cond_t bdb_tool_index_cond_work; + +#if DB_VERSION_FULL >= 0x04060000 +#define USE_TRICKLE 1 +#else +/* Seems to slow things down too much in BDB 4.5 */ +#undef USE_TRICKLE +#endif + +#ifdef USE_TRICKLE +static ldap_pvt_thread_mutex_t bdb_tool_trickle_mutex; +static ldap_pvt_thread_cond_t bdb_tool_trickle_cond; +static ldap_pvt_thread_cond_t bdb_tool_trickle_cond_end; + +static void * bdb_tool_trickle_task( void *ctx, void *ptr ); +static int bdb_tool_trickle_active; +#endif + +static void * bdb_tool_index_task( void *ctx, void *ptr ); + +static int +bdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ); + +static int bdb_tool_threads; + +int bdb_tool_entry_open( + BackendDB *be, int mode ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + + /* initialize key and data thangs */ + DBTzero( &key ); + DBTzero( &data ); + key.flags = DB_DBT_USERMEM; + key.data = &nid; + key.size = key.ulen = sizeof( nid ); + data.flags = DB_DBT_USERMEM; + + if (cursor == NULL) { + int rc = bdb->bi_id2entry->bdi_db->cursor( + bdb->bi_id2entry->bdi_db, bdb->bi_cache.c_txn, &cursor, + bdb->bi_db_opflags ); + if( rc != 0 ) { + return -1; + } + } + + /* Set up for threaded slapindex */ + if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) { + if ( !bdb_tool_info ) { +#ifdef USE_TRICKLE + ldap_pvt_thread_mutex_init( &bdb_tool_trickle_mutex ); + ldap_pvt_thread_cond_init( &bdb_tool_trickle_cond ); + ldap_pvt_thread_cond_init( &bdb_tool_trickle_cond_end ); + ldap_pvt_thread_pool_submit( &connection_pool, bdb_tool_trickle_task, bdb->bi_dbenv ); +#endif + + ldap_pvt_thread_mutex_init( &bdb_tool_index_mutex ); + ldap_pvt_thread_cond_init( &bdb_tool_index_cond_main ); + ldap_pvt_thread_cond_init( &bdb_tool_index_cond_work ); + if ( bdb->bi_nattrs ) { + int i; + bdb_tool_threads = slap_tool_thread_max - 1; + if ( bdb_tool_threads > 1 ) { + bdb_tool_index_threads = ch_malloc( bdb_tool_threads * sizeof( int )); + bdb_tool_index_rec = ch_malloc( bdb->bi_nattrs * sizeof( IndexRec )); + bdb_tool_index_tcount = bdb_tool_threads - 1; + for (i=1; i<bdb_tool_threads; i++) { + int *ptr = ch_malloc( sizeof( int )); + *ptr = i; + ldap_pvt_thread_pool_submit( &connection_pool, + bdb_tool_index_task, ptr ); + } + } + } + bdb_tool_info = bdb; + } + } + + return 0; +} + +int bdb_tool_entry_close( + BackendDB *be ) +{ + if ( bdb_tool_info ) { + slapd_shutdown = 1; +#ifdef USE_TRICKLE + ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); + + /* trickle thread may not have started yet */ + while ( !bdb_tool_trickle_active ) + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, + &bdb_tool_trickle_mutex ); + + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond ); + while ( bdb_tool_trickle_active ) + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond_end, + &bdb_tool_trickle_mutex ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); +#endif + if ( bdb_tool_threads > 1 ) { + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + + /* There might still be some threads starting */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + + bdb_tool_index_tcount = bdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); + + /* Make sure all threads are stopped */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + + ch_free( bdb_tool_index_threads ); + ch_free( bdb_tool_index_rec ); + bdb_tool_index_tcount = bdb_tool_threads - 1; + } + bdb_tool_info = NULL; + slapd_shutdown = 0; + } + + if( eh.bv.bv_val ) { + ch_free( eh.bv.bv_val ); + eh.bv.bv_val = NULL; + } + + if( cursor ) { + cursor->c_close( cursor ); + cursor = NULL; + } + +#ifdef BDB_TOOL_IDL_CACHING + bdb_tool_idl_flush( be ); +#endif + + 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); + } + return -1; + } + + return 0; +} + +ID +bdb_tool_entry_first_x( + BackendDB *be, + struct berval *base, + int scope, + Filter *f ) +{ + tool_base = base; + tool_scope = scope; + tool_filter = f; + + return bdb_tool_entry_next( be ); +} + +ID bdb_tool_entry_next( + BackendDB *be ) +{ + int rc; + ID id; + struct bdb_info *bdb; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + bdb = (struct bdb_info *) be->be_private; + assert( bdb != NULL ); + +next:; + /* Get the header */ + data.ulen = data.dlen = sizeof( ehbuf ); + data.data = ehbuf; + data.flags |= DB_DBT_PARTIAL; + rc = cursor->c_get( cursor, &key, &data, DB_NEXT ); + + if( rc ) { + /* If we're doing linear indexing and there are more attrs to + * index, and we're at the end of the database, start over. + */ + if ( index_nattrs && rc == DB_NOTFOUND ) { + /* optional - do a checkpoint here? */ + bdb_attr_info_free( bdb->bi_attrs[0] ); + bdb->bi_attrs[0] = bdb->bi_attrs[index_nattrs]; + index_nattrs--; + rc = cursor->c_get( cursor, &key, &data, DB_FIRST ); + if ( rc ) { + return NOID; + } + } else { + return NOID; + } + } + + BDB_DISK2ID( key.data, &id ); + previd = id; + + 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 ) { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + } + + rc = bdb_tool_entry_get_int( be, id, &tool_next_entry ); + if ( rc == LDAP_NO_SUCH_OBJECT ) { + goto next; + } + + assert( tool_next_entry != NULL ); + +#ifdef BDB_HIER + /* TODO: needed until BDB_HIER is handled accordingly + * in bdb_tool_entry_get_int() */ + if ( tool_base && !dnIsSuffixScope( &tool_next_entry->e_nname, tool_base, tool_scope ) ) + { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } +#endif + + if ( tool_filter && test_filter( NULL, tool_next_entry, tool_filter ) != LDAP_COMPARE_TRUE ) + { + bdb_entry_release( &op, tool_next_entry, 0 ); + tool_next_entry = NULL; + goto next; + } + } + + return id; +} + +ID bdb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + Operation op = {0}; + Opheader ohdr = {0}; + EntryInfo *ei = NULL; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = bdb_cache_find_ndn( &op, 0, dn, &ei ); + if ( ei ) bdb_cache_entryinfo_unlock( ei ); + if ( rc == DB_NOTFOUND ) + return NOID; + + return ei->bei_id; +} + +static int +bdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep ) +{ + Entry *e = NULL; + char *dptr; + int rc, eoff; + + 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 ) { + data.ulen = data.dlen = sizeof( ehbuf ); + data.data = ehbuf; + data.flags |= DB_DBT_PARTIAL; + + BDB_ID2DISK( id, &nid ); + rc = cursor->c_get( cursor, &key, &data, DB_SET ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + } + + /* Get the header */ + dptr = eh.bv.bv_val; + eh.bv.bv_val = ehbuf; + eh.bv.bv_len = data.size; + rc = entry_header( &eh ); + eoff = eh.data - eh.bv.bv_val; + eh.bv.bv_val = dptr; + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + + /* Get the size */ + data.flags &= ~DB_DBT_PARTIAL; + data.ulen = 0; + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc != DB_BUFFER_SMALL ) { + rc = LDAP_OTHER; + goto done; + } + + /* Allocate a block and retrieve the data */ + eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + data.size; + eh.bv.bv_val = ch_realloc( eh.bv.bv_val, eh.bv.bv_len ); + eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval ); + data.data = eh.data; + data.ulen = data.size; + + /* Skip past already parsed nattr/nvals */ + eh.data += eoff; + + rc = cursor->c_get( cursor, &key, &data, DB_CURRENT ); + if ( rc ) { + rc = LDAP_OTHER; + goto done; + } + +#ifndef BDB_HIER + /* TODO: handle BDB_HIER accordingly */ + if ( tool_base != NULL ) { + struct berval ndn; + entry_decode_dn( &eh, NULL, &ndn ); + + if ( !dnIsSuffixScope( &ndn, tool_base, tool_scope ) ) { + return LDAP_NO_SUCH_OBJECT; + } + } +#endif + +#ifdef SLAP_ZONE_ALLOC + /* FIXME: will add ctx later */ + rc = entry_decode( &eh, &e, NULL ); +#else + rc = entry_decode( &eh, &e ); +#endif + + if( rc == LDAP_SUCCESS ) { + e->e_id = id; +#ifdef BDB_HIER + if ( slapMode & SLAP_TOOL_READONLY ) { + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + EntryInfo *ei = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + rc = bdb_cache_find_parent( &op, bdb->bi_cache.c_txn, id, &ei ); + if ( rc == LDAP_SUCCESS ) { + bdb_cache_entryinfo_unlock( ei ); + e->e_private = ei; + ei->bei_e = e; + bdb_fix_dn( e, 0 ); + ei->bei_e = NULL; + e->e_private = NULL; + } + } +#endif + } +done: + if ( e != NULL ) { + *ep = e; + } + + return rc; +} + +Entry* +bdb_tool_entry_get( BackendDB *be, ID id ) +{ + Entry *e = NULL; + + (void)bdb_tool_entry_get_int( be, id, &e ); + return e; +} + +static int bdb_tool_next_id( + Operation *op, + DB_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; + EntryInfo *ei = NULL, eidummy; + int rc; + + if (ndn.bv_len == 0) { + e->e_id = 0; + return 0; + } + + rc = bdb_cache_find_ndn( op, tid, &ndn, &ei ); + if ( ei ) bdb_cache_entryinfo_unlock( ei ); + if ( rc == DB_NOTFOUND ) { + if ( !be_issuffix( op->o_bd, &ndn ) ) { + ID eid = e->e_id; + dnParent( &dn, &pdn ); + dnParent( &ndn, &npdn ); + e->e_name = pdn; + e->e_nname = npdn; + rc = bdb_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 ) { + eidummy.bei_id = e->e_id; + ei = &eidummy; + } + } + rc = bdb_next_id( op->o_bd, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> bdb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + rc = bdb_dn2id_add( op, tid, ei, e ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "dn2id_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> bdb_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 = ei->bei_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 +bdb_tool_index_add( + Operation *op, + DB_TXN *txn, + Entry *e ) +{ + struct bdb_info *bdb = (struct bdb_info *) op->o_bd->be_private; + + if ( !bdb->bi_nattrs ) + return 0; + + if ( bdb_tool_threads > 1 ) { + IndexRec *ir; + int i, rc; + Attribute *a; + + ir = bdb_tool_index_rec; + memset(ir, 0, bdb->bi_nattrs * sizeof( IndexRec )); + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + rc = bdb_index_recset( bdb, a, a->a_desc->ad_type, + &a->a_desc->ad_tags, ir ); + if ( rc ) + return rc; + } + bdb_tool_ix_id = e->e_id; + bdb_tool_ix_op = op; + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + /* Wait for all threads to be ready */ + while ( bdb_tool_index_tcount > 0 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + } + for ( i=1; i<bdb_tool_threads; i++ ) + bdb_tool_index_threads[i] = LDAP_BUSY; + bdb_tool_index_tcount = bdb_tool_threads - 1; + ldap_pvt_thread_cond_broadcast( &bdb_tool_index_cond_work ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + rc = bdb_index_recrun( op, bdb, ir, e->e_id, 0 ); + if ( rc ) + return rc; + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + for ( i=1; i<bdb_tool_threads; i++ ) { + if ( bdb_tool_index_threads[i] == LDAP_BUSY ) { + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_main, + &bdb_tool_index_mutex ); + i--; + continue; + } + if ( bdb_tool_index_threads[i] ) { + rc = bdb_tool_index_threads[i]; + break; + } + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + return rc; + } else { + return bdb_index_entry_add( op, txn, e ); + } +} + +ID bdb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct bdb_info *bdb; + DB_TXN *tid = NULL; + 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(bdb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn, 0 ); + + bdb = (struct bdb_info *) be->be_private; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &tid, + bdb->bi_db_opflags ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_put) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* add dn2id indices */ + rc = bdb_tool_next_id( &op, tid, e, text, 0 ); + if( rc != 0 ) { + goto done; + } + +#ifdef USE_TRICKLE + if (( slapMode & SLAP_TOOL_QUICK ) && (( e->e_id & 0xfff ) == 0xfff )) { + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond ); + } +#endif + + if ( !bdb->bi_linear_index ) + rc = bdb_tool_index_add( &op, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "index_entry_add failed: %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + + /* id2entry index */ + rc = bdb_id2entry_add( be, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + if ( !( slapMode & SLAP_TOOL_QUICK )) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + } + + } else { + if ( !( slapMode & SLAP_TOOL_QUICK )) { + TXN_ABORT( tid ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + rc == LDAP_OTHER ? "Internal error" : + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + } + e->e_id = NOID; + } + + return e->e_id; +} + +int bdb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct bdb_info *bi = (struct bdb_info *) be->be_private; + int rc; + Entry *e; + DB_TXN *tid = NULL; + Operation op = {0}; + Opheader ohdr = {0}; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(bdb_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 (!bi->bi_attrs) { + return 0; + } + + /* Check for explicit list of attrs to index */ + if ( adv ) { + int i, j, n; + + if ( bi->bi_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 ( bi->bi_attrs[i]->ai_desc != adv[i] ) { + for ( j = i+1; j < bi->bi_nattrs; j++ ) { + if ( bi->bi_attrs[j]->ai_desc == adv[i] ) { + AttrInfo *ai = bi->bi_attrs[i]; + bi->bi_attrs[i] = bi->bi_attrs[j]; + bi->bi_attrs[j] = ai; + break; + } + } + if ( j == bi->bi_nattrs ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_tool_entry_reindex) + ": no index configured for %s\n", + adv[i]->ad_cname.bv_val, 0, 0 ); + return -1; + } + } + } + bi->bi_nattrs = i; + } + + /* Get the first attribute to index */ + if (bi->bi_linear_index && !index_nattrs) { + index_nattrs = bi->bi_nattrs - 1; + bi->bi_nattrs = 1; + } + + e = bdb_tool_entry_get( be, id ); + + if( e == NULL ) { + Debug( LDAP_DEBUG_ANY, + LDAP_XSTRING(bdb_tool_entry_reindex) + ": could not locate id=%ld\n", + (long) id, 0, 0 ); + return -1; + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_BEGIN( bi->bi_dbenv, NULL, &tid, bi->bi_db_opflags ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) ": " + "txn_begin failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + goto done; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_reindex) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + /* + * just (re)add them for now + * assume that some other routine (not yet implemented) + * will zap index databases + * + */ + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) "( %ld, \"%s\" )\n", + (long) id, e->e_dn, 0 ); + + rc = bdb_tool_index_add( &op, tid, e ); + +done: + if( rc == 0 ) { + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) + ": txn_commit failed: %s (%d)\n", + db_strerror(rc), rc, 0 ); + e->e_id = NOID; + } + } + + } else { + if (! (slapMode & SLAP_TOOL_QUICK)) { + TXN_ABORT( tid ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_reindex) + ": txn_aborted! %s (%d)\n", + db_strerror(rc), rc, 0 ); + } + e->e_id = NOID; + } + bdb_entry_release( &op, e, 0 ); + + return rc; +} + +ID bdb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + int rc; + struct bdb_info *bdb; + DB_TXN *tid = NULL; + 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(bdb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn, 0 ); + + bdb = (struct bdb_info *) be->be_private; + + if (! (slapMode & SLAP_TOOL_QUICK)) { + if( cursor ) { + cursor->c_close( cursor ); + cursor = NULL; + } + rc = TXN_BEGIN( bdb->bi_dbenv, NULL, &tid, + bdb->bi_db_opflags ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_begin failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(bdb_tool_entry_modify) ": txn id: %x\n", + tid->id(tid), 0, 0 ); + } + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + /* id2entry index */ + rc = bdb_id2entry_update( be, tid, e ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "id2entry_add failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + if (! (slapMode & SLAP_TOOL_QUICK)) { + rc = TXN_COMMIT( tid, 0 ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": " + "%s\n", text->bv_val, 0, 0 ); + e->e_id = NOID; + } + } + + } else { + if (! (slapMode & SLAP_TOOL_QUICK)) { + TXN_ABORT( tid ); + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + db_strerror(rc), rc ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(bdb_tool_entry_modify) ": %s\n", + text->bv_val, 0, 0 ); + } + e->e_id = NOID; + } + + return e->e_id; +} + +#ifdef BDB_TOOL_IDL_CACHING +static int +bdb_tool_idl_cmp( const void *v1, const void *v2 ) +{ + const bdb_tool_idl_cache *c1 = v1, *c2 = v2; + int rc; + + if (( rc = c1->kstr.bv_len - c2->kstr.bv_len )) return rc; + return memcmp( c1->kstr.bv_val, c2->kstr.bv_val, c1->kstr.bv_len ); +} + +static int +bdb_tool_idl_flush_one( void *v1, void *arg ) +{ + bdb_tool_idl_cache *ic = v1; + DB *db = arg; + struct bdb_info *bdb = bdb_tool_info; + bdb_tool_idl_cache_entry *ice; + DBC *curs; + DBT key, data; + int i, rc; + ID id, nid; + + /* Freshly allocated, ignore it */ + if ( !ic->head && ic->count <= BDB_IDL_DB_SIZE ) { + return 0; + } + + rc = db->cursor( db, NULL, &curs, 0 ); + if ( rc ) + return -1; + + DBTzero( &key ); + DBTzero( &data ); + + bv2DBT( &ic->kstr, &key ); + + data.size = data.ulen = sizeof( ID ); + data.flags = DB_DBT_USERMEM; + data.data = &nid; + + rc = curs->c_get( curs, &key, &data, DB_SET ); + /* If key already exists and we're writing a range... */ + if ( rc == 0 && ic->count > BDB_IDL_DB_SIZE ) { + /* If it's not currently a range, must delete old info */ + if ( nid ) { + /* Skip lo */ + while ( curs->c_get( curs, &key, &data, DB_NEXT_DUP ) == 0 ) + curs->c_del( curs, 0 ); + + nid = 0; + /* Store range marker */ + curs->c_put( curs, &key, &data, DB_KEYFIRST ); + } else { + + /* Skip lo */ + rc = curs->c_get( curs, &key, &data, DB_NEXT_DUP ); + + /* Get hi */ + rc = curs->c_get( curs, &key, &data, DB_NEXT_DUP ); + + /* Delete hi */ + curs->c_del( curs, 0 ); + } + BDB_ID2DISK( ic->last, &nid ); + curs->c_put( curs, &key, &data, DB_KEYLAST ); + rc = 0; + } else if ( rc && rc != DB_NOTFOUND ) { + rc = -1; + } else if ( ic->count > BDB_IDL_DB_SIZE ) { + /* range, didn't exist before */ + nid = 0; + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + if ( rc == 0 ) { + BDB_ID2DISK( ic->first, &nid ); + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + if ( rc == 0 ) { + BDB_ID2DISK( ic->last, &nid ); + rc = curs->c_put( curs, &key, &data, DB_KEYLAST ); + } + } + if ( rc ) { + rc = -1; + } + } else { + int n; + + /* Just a normal write */ + rc = 0; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) { + int end; + if ( ice->next ) { + end = IDBLOCK; + } else { + end = ic->count & (IDBLOCK-1); + if ( !end ) + end = IDBLOCK; + } + for ( i=0; i<end; i++ ) { + if ( !ice->ids[i] ) continue; + BDB_ID2DISK( ice->ids[i], &nid ); + rc = curs->c_put( curs, &key, &data, DB_NODUPDATA ); + if ( rc ) { + if ( rc == DB_KEYEXIST ) { + rc = 0; + continue; + } + rc = -1; + break; + } + } + if ( rc ) { + rc = -1; + break; + } + } + if ( ic->head ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + ic->tail->next = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ic->head; + bdb->bi_idl_cache_size -= n; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + } + } + if ( ic != db->app_private ) { + ch_free( ic ); + } else { + ic->head = ic->tail = NULL; + } + curs->c_close( curs ); + return rc; +} + +static int +bdb_tool_idl_flush_db( DB *db, bdb_tool_idl_cache *ic ) +{ + Avlnode *root = db->app_private; + int rc; + + db->app_private = ic; + rc = avl_apply( root, bdb_tool_idl_flush_one, db, -1, AVL_INORDER ); + avl_free( root, NULL ); + db->app_private = NULL; + if ( rc != -1 ) + rc = 0; + return rc; +} + +static int +bdb_tool_idl_flush( BackendDB *be ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + DB *db; + Avlnode *root; + int i, rc = 0; + + for ( i=BDB_NDB; i < bdb->bi_ndatabases; i++ ) { + db = bdb->bi_databases[i]->bdi_db; + if ( !db->app_private ) continue; + rc = bdb_tool_idl_flush_db( db, NULL ); + if ( rc ) + break; + } + if ( !rc ) { + bdb->bi_idl_cache_size = 0; + } + return rc; +} + +int bdb_tool_idl_add( + BackendDB *be, + DB *db, + DB_TXN *txn, + DBT *key, + ID id ) +{ + struct bdb_info *bdb = (struct bdb_info *) be->be_private; + bdb_tool_idl_cache *ic, itmp; + bdb_tool_idl_cache_entry *ice; + int rc; + + if ( !bdb->bi_idl_cache_max_size ) + return bdb_idl_insert_key( be, db, txn, key, id ); + + DBT2bv( key, &itmp.kstr ); + + ic = avl_find( (Avlnode *)db->app_private, &itmp, bdb_tool_idl_cmp ); + + /* No entry yet, create one */ + if ( !ic ) { + DBC *curs; + DBT data; + ID nid; + int rc; + + ic = ch_malloc( sizeof( bdb_tool_idl_cache ) + itmp.kstr.bv_len ); + ic->kstr.bv_len = itmp.kstr.bv_len; + ic->kstr.bv_val = (char *)(ic+1); + AC_MEMCPY( ic->kstr.bv_val, itmp.kstr.bv_val, ic->kstr.bv_len ); + ic->head = ic->tail = NULL; + ic->last = 0; + ic->count = 0; + avl_insert( (Avlnode **)&db->app_private, ic, bdb_tool_idl_cmp, + avl_dup_error ); + + /* load existing key count here */ + rc = db->cursor( db, NULL, &curs, 0 ); + if ( rc ) return rc; + + data.ulen = sizeof( ID ); + data.flags = DB_DBT_USERMEM; + data.data = &nid; + rc = curs->c_get( curs, key, &data, DB_SET ); + if ( rc == 0 ) { + if ( nid == 0 ) { + ic->count = BDB_IDL_DB_SIZE+1; + } else { + db_recno_t count; + + curs->c_count( curs, &count, 0 ); + ic->count = count; + BDB_DISK2ID( &nid, &ic->first ); + } + } + curs->c_close( curs ); + } + /* are we a range already? */ + if ( ic->count > BDB_IDL_DB_SIZE ) { + ic->last = id; + return 0; + /* Are we at the limit, and converting to a range? */ + } else if ( ic->count == BDB_IDL_DB_SIZE ) { + int n; + for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) + /* counting */ ; + if ( n ) { + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + ic->tail->next = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ic->head; + bdb->bi_idl_cache_size -= n; + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + } + ic->head = ic->tail = NULL; + ic->last = id; + ic->count++; + return 0; + } + /* No free block, create that too */ + if ( !ic->tail || ( ic->count & (IDBLOCK-1)) == 0) { + ice = NULL; + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + if ( bdb->bi_idl_cache_size >= bdb->bi_idl_cache_max_size ) { + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + rc = bdb_tool_idl_flush_db( db, ic ); + if ( rc ) + return rc; + avl_insert( (Avlnode **)&db->app_private, ic, bdb_tool_idl_cmp, + avl_dup_error ); + ldap_pvt_thread_mutex_lock( &bdb->bi_idl_tree_lrulock ); + } + bdb->bi_idl_cache_size++; + if ( bdb_tool_idl_free_list ) { + ice = bdb_tool_idl_free_list; + bdb_tool_idl_free_list = ice->next; + } + ldap_pvt_thread_mutex_unlock( &bdb->bi_idl_tree_lrulock ); + if ( !ice ) { + ice = ch_malloc( sizeof( bdb_tool_idl_cache_entry )); + } + memset( ice, 0, sizeof( *ice )); + if ( !ic->head ) { + ic->head = ice; + } else { + ic->tail->next = ice; + } + ic->tail = ice; + if ( !ic->count ) + ic->first = id; + } + ice = ic->tail; + ice->ids[ ic->count & (IDBLOCK-1) ] = id; + ic->count++; + + return 0; +} +#endif + +#ifdef USE_TRICKLE +static void * +bdb_tool_trickle_task( void *ctx, void *ptr ) +{ + DB_ENV *env = ptr; + int wrote; + + ldap_pvt_thread_mutex_lock( &bdb_tool_trickle_mutex ); + bdb_tool_trickle_active = 1; + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond_end ); + while ( 1 ) { + ldap_pvt_thread_cond_wait( &bdb_tool_trickle_cond, + &bdb_tool_trickle_mutex ); + if ( slapd_shutdown ) + break; + env->memp_trickle( env, 30, &wrote ); + } + bdb_tool_trickle_active = 0; + ldap_pvt_thread_cond_signal( &bdb_tool_trickle_cond_end ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_trickle_mutex ); + + return NULL; +} +#endif + +static void * +bdb_tool_index_task( void *ctx, void *ptr ) +{ + int base = *(int *)ptr; + + free( ptr ); + while ( 1 ) { + ldap_pvt_thread_mutex_lock( &bdb_tool_index_mutex ); + bdb_tool_index_tcount--; + if ( !bdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); + ldap_pvt_thread_cond_wait( &bdb_tool_index_cond_work, + &bdb_tool_index_mutex ); + if ( slapd_shutdown ) { + bdb_tool_index_tcount--; + if ( !bdb_tool_index_tcount ) + ldap_pvt_thread_cond_signal( &bdb_tool_index_cond_main ); + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + break; + } + ldap_pvt_thread_mutex_unlock( &bdb_tool_index_mutex ); + + bdb_tool_index_threads[base] = bdb_index_recrun( bdb_tool_ix_op, + bdb_tool_info, bdb_tool_index_rec, bdb_tool_ix_id, base ); + } + + return NULL; +} diff --git a/servers/slapd/back-bdb/trans.c b/servers/slapd/back-bdb/trans.c new file mode 100644 index 0000000..92da8be --- /dev/null +++ b/servers/slapd/back-bdb/trans.c @@ -0,0 +1,56 @@ +/* trans.c - bdb backend transaction routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2000-2021 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-bdb.h" +#include "lber_pvt.h" +#include "lutil.h" + + +/* Congestion avoidance code + * for Deadlock Rollback + */ + +void +bdb_trans_backoff( int num_retries ) +{ + int i; + int delay = 0; + int pow_retries = 1; + unsigned long key = 0; + unsigned long max_key = -1; + struct timeval timeout; + + lutil_entropy( (unsigned char *) &key, sizeof( unsigned long )); + + for ( i = 0; i < num_retries; i++ ) { + if ( i >= 5 ) break; + pow_retries *= 4; + } + + delay = 16384 * (key * (double) pow_retries / (double) max_key); + delay = delay ? delay : 1; + + Debug( LDAP_DEBUG_TRACE, "delay = %d, num_retries = %d\n", delay, num_retries, 0 ); + + timeout.tv_sec = delay / 1000000; + timeout.tv_usec = delay % 1000000; + select( 0, NULL, NULL, NULL, &timeout ); +} |