diff options
Diffstat (limited to '')
-rw-r--r-- | servers/slapd/back-ndb/Makefile.in | 59 | ||||
-rw-r--r-- | servers/slapd/back-ndb/TODO | 6 | ||||
-rw-r--r-- | servers/slapd/back-ndb/add.cpp | 347 | ||||
-rw-r--r-- | servers/slapd/back-ndb/attrsets.conf | 36 | ||||
-rw-r--r-- | servers/slapd/back-ndb/back-ndb.h | 168 | ||||
-rw-r--r-- | servers/slapd/back-ndb/bind.cpp | 165 | ||||
-rw-r--r-- | servers/slapd/back-ndb/compare.cpp | 169 | ||||
-rw-r--r-- | servers/slapd/back-ndb/config.cpp | 333 | ||||
-rw-r--r-- | servers/slapd/back-ndb/delete.cpp | 322 | ||||
-rw-r--r-- | servers/slapd/back-ndb/init.cpp | 449 | ||||
-rw-r--r-- | servers/slapd/back-ndb/modify.cpp | 704 | ||||
-rw-r--r-- | servers/slapd/back-ndb/modrdn.cpp | 558 | ||||
-rw-r--r-- | servers/slapd/back-ndb/ndbio.cpp | 1677 | ||||
-rw-r--r-- | servers/slapd/back-ndb/proto-ndb.h | 166 | ||||
-rw-r--r-- | servers/slapd/back-ndb/search.cpp | 854 | ||||
-rw-r--r-- | servers/slapd/back-ndb/tools.cpp | 544 |
16 files changed, 6557 insertions, 0 deletions
diff --git a/servers/slapd/back-ndb/Makefile.in b/servers/slapd/back-ndb/Makefile.in new file mode 100644 index 0000000..4df6898 --- /dev/null +++ b/servers/slapd/back-ndb/Makefile.in @@ -0,0 +1,59 @@ +# Makefile.in for back-ndb +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2008-2022 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. +## +## ACKNOWLEDGEMENTS: +## This work was initially developed by Howard Chu for inclusion +## in OpenLDAP Software. This work was sponsored by MySQL. + +SRCS = init.cpp tools.cpp config.cpp ndbio.cpp \ + add.cpp bind.cpp compare.cpp delete.cpp modify.cpp modrdn.cpp search.cpp + +OBJS = init.lo tools.lo config.lo ndbio.lo \ + add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-ndb" +BUILD_MOD = @BUILD_NDB@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_NDB@_DEFS) +MOD_LIBS = $(SLAPD_NDB_LIBS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_ndb + +XINCPATH = -I.. -I$(srcdir)/.. @SLAPD_NDB_INCS@ +XDEFS = $(MODULES_CPPFLAGS) + +AC_CXX = g++ +CXX = $(AC_CXX) +LTCXX_MOD = $(LIBTOOL) $(LTONLY_MOD) --mode=compile \ + $(CXX) $(LT_CFLAGS) $(LT_CPPFLAGS) $(MOD_DEFS) -c + +all-local-lib: ../.backend + +.SUFFIXES: .c .o .lo .cpp + +.cpp.lo: + $(LTCXX_MOD) $< + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-ndb/TODO b/servers/slapd/back-ndb/TODO new file mode 100644 index 0000000..0393954 --- /dev/null +++ b/servers/slapd/back-ndb/TODO @@ -0,0 +1,6 @@ +LDAP features not currently supported: + +tagged attributes +aliases +substring indexing +subtree rename diff --git a/servers/slapd/back-ndb/add.cpp b/servers/slapd/back-ndb/add.cpp new file mode 100644 index 0000000..f89cad4 --- /dev/null +++ b/servers/slapd/back-ndb/add.cpp @@ -0,0 +1,347 @@ +/* add.cpp - ldap NDB back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +extern "C" int +ndb_back_add(Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry p = {0}; + Attribute poc; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + struct berval pdn, pndn; + + int num_retries = 0; + int success; + + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(ndb_back_add) ": %s\n", + op->oq_add.rs_e->e_name.bv_val, 0, 0); + + ctrls[num_ctrls] = 0; + NA.txn = NULL; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->oq_add.rs_e, NULL, + get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen ); + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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(ndb_back_add) ": entry failed op attrs add: " + "%s (%d)\n", rs->sr_text, rs->sr_err, 0 ); + goto return_results; + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + /* + * Get the parent dn and see if the corresponding entry exists. + */ + if ( be_issuffix( op->o_bd, &op->oq_add.rs_e->e_nname ) ) { + pdn = slap_empty_bv; + pndn = slap_empty_bv; + } else { + dnParent( &op->ora_e->e_name, &pdn ); + dnParent( &op->ora_e->e_nname, &pndn ); + } + p.e_name = op->ora_e->e_name; + p.e_nname = op->ora_e->e_nname; + + op->ora_e->e_id = NOID; + rdns.nr_num = 0; + NA.rdns = &rdns; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + ndb_trans_backoff( ++num_retries ); + } + + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or parent */ + NA.e = &p; + NA.ocs = NULL; + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + rs->sr_err = LDAP_ALREADY_EXISTS; + goto return_results; + case LDAP_NO_SUCH_OBJECT: + break; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + 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 ( NA.ocs ) { + int i; + for ( i=0; !BER_BVISNULL( &NA.ocs[i] ); i++ ); + poc.a_numvals = i; + poc.a_desc = slap_schema.si_ad_objectClass; + poc.a_vals = NA.ocs; + poc.a_nvals = poc.a_vals; + poc.a_next = NULL; + p.e_attrs = &poc; + } + + if ( ber_bvstrcasecmp( &pndn, &matched ) ) { + rs->sr_matched = matched.bv_val; + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": parent " + "does not exist\n", 0, 0, 0 ); + + rs->sr_text = "parent does not exist"; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + if ( p.e_attrs && is_entry_referral( &p )) { +is_ref: p.e_attrs = NULL; + ndb_entry_get_data( op, &NA, 0 ); + rs->sr_ref = get_entry_referrals( op, &p ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags = REP_REF_MUSTBEFREED; + attrs_free( p.e_attrs ); + p.e_attrs = NULL; + } + goto return_results; + } + + p.e_name = pdn; + p.e_nname = pndn; + rs->sr_err = access_allowed( op, &p, + children, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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 ( NA.ocs ) { + if ( is_entry_subentry( &p )) { + /* parent is a subentry, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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 ) ) { + /* parent is an alias, don't allow add */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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 = p.e_name.bv_val; + goto is_ref; + } + } + + rs->sr_err = access_allowed( op, op->ora_e, + entry, NULL, ACL_WADD, NULL ); + + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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, op->ora_e, op->ora_modlist)) { + 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;; + } + + + /* acquire entry ID */ + if ( op->ora_e->e_id == NOID ) { + rs->sr_err = ndb_next_id( op->o_bd, NA.ndb, &op->ora_e->e_id ); + if( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": next_id failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + } + + if ( matched.bv_val ) + rdns.nr_num++; + NA.e = op->ora_e; + /* dn2id index */ + rs->sr_err = ndb_entry_put_info( op->o_bd, &NA, 0 ); + if ( rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": ndb_entry_put_info failed (%d)\n", + rs->sr_err, 0, 0 ); + rs->sr_text = "internal error"; + goto return_results; + } + + /* id2entry index */ + rs->sr_err = ndb_entry_put_data( op->o_bd, &NA ); + if ( rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": ndb_entry_put_data failed (%d) %s(%d)\n", + rs->sr_err, NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_text = "internal error"; + 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->oq_add.rs_e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_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=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + } else { + if(( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if ( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": %s : %s (%d)\n", + rs->sr_text, NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_add) ": added%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + op->oq_add.rs_e->e_id, op->oq_add.rs_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 ); + slap_graduate_commit_csn( op ); + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + 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-ndb/attrsets.conf b/servers/slapd/back-ndb/attrsets.conf new file mode 100644 index 0000000..f135e0a --- /dev/null +++ b/servers/slapd/back-ndb/attrsets.conf @@ -0,0 +1,36 @@ +# Definition of useful attribute sets +# from X.521 section 5 +# +# TelecommunicationAttributeSet ATTRIBUTE ::= { +# facsimileTelephoneNumber | +# internationalISDNNumber | +# telephoneNumber | +# teletexTerminalIdentifier | +# telexNumber | +# preferredDeliveryMethod | +# destinationIndicator | +# registeredAddress | +# x121Address } +# +# PostalAttributeSet ATTRIBUTE ::= { +# physicalDeliveryOfficeName | +# postalAddress | +# postalCode | +# postOfficeBox | +# streetAddress } +# +# LocaleAttributeSet ATTRIBUTE ::= { +# localityName | +# stateOrProvinceName | +# streetAddress } +# +# OrganizationalAttributeSet ATTRIBUTE ::= { +# description | +# LocaleAttributeSet | +# PostalAttributeSet | +# TelecommunicationAttributeSet | +# businessCategory | +# seeAlso | +# searchGuide | +# userPassword } + diff --git a/servers/slapd/back-ndb/back-ndb.h b/servers/slapd/back-ndb/back-ndb.h new file mode 100644 index 0000000..c92c3f1 --- /dev/null +++ b/servers/slapd/back-ndb/back-ndb.h @@ -0,0 +1,168 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#ifndef SLAPD_NDB_H +#define SLAPD_NDB_H + +#include "slap.h" + +#include <mysql.h> +#include <NdbApi.hpp> + +LDAP_BEGIN_DECL + +/* The general design is to use one relational table per objectclass. This is + * complicated by objectclass inheritance and auxiliary classes though. + * + * Attributes must only occur in a single table. For objectclasses that inherit + * from other classes, attributes defined in the superior class are only stored + * in the superior class' table. When multiple unrelated classes define the same + * attributes, an attributeSet should be defined instead, containing all of the + * common attributes. + * + * The no_set table lists which other attributeSets apply to the current + * objectClass. The no_attrs table lists all of the non-inherited attributes of + * the class, including those residing in an attributeSet. + * + * Usually the table is named identically to the objectClass, but it can also + * be explicitly named something else if needed. + */ +#define NDB_MAX_OCSETS 8 + +struct ndb_attrinfo; + +typedef struct ndb_ocinfo { + struct berval no_name; /* objectclass cname */ + struct berval no_table; + ObjectClass *no_oc; + struct ndb_ocinfo *no_sets[NDB_MAX_OCSETS]; + struct ndb_attrinfo **no_attrs; + int no_flag; + int no_nsets; + int no_nattrs; +} NdbOcInfo; + +#define NDB_INFO_ATLEN 0x01 +#define NDB_INFO_ATSET 0x02 +#define NDB_INFO_INDEX 0x04 +#define NDB_INFO_ATBLOB 0x08 + +typedef struct ndb_attrinfo { + struct berval na_name; /* attribute cname */ + AttributeDescription *na_desc; + AttributeType *na_attr; + NdbOcInfo *na_oi; + int na_flag; + int na_len; + int na_column; + int na_ixcol; +} NdbAttrInfo; + +typedef struct ListNode { + struct ListNode *ln_next; + void *ln_data; +} ListNode; + +#define NDB_IS_OPEN(ni) (ni->ni_cluster != NULL) + +struct ndb_info { + /* NDB connection */ + char *ni_connectstr; + char *ni_dbname; + Ndb_cluster_connection **ni_cluster; + + /* MySQL connection parameters */ + MYSQL ni_sql; + char *ni_hostname; + char *ni_username; + char *ni_password; + char *ni_socket; + unsigned long ni_clflag; + unsigned int ni_port; + + /* Search filter processing */ + int ni_search_stack_depth; + void *ni_search_stack; + +#define DEFAULT_SEARCH_STACK_DEPTH 16 +#define MINIMUM_SEARCH_STACK_DEPTH 8 + + /* Schema config */ + NdbOcInfo *ni_opattrs; + ListNode *ni_attridxs; + ListNode *ni_attrlens; + ListNode *ni_attrsets; + ListNode *ni_attrblobs; + ldap_pvt_thread_rdwr_t ni_ai_rwlock; + Avlnode *ni_ai_tree; + ldap_pvt_thread_rdwr_t ni_oc_rwlock; + Avlnode *ni_oc_tree; + int ni_nconns; /* number of connections to open */ + int ni_nextconn; /* next conn to use */ + ldap_pvt_thread_mutex_t ni_conn_mutex; +}; + +#define NDB_MAX_RDNS 16 +#define NDB_RDN_LEN 128 +#define NDB_MAX_OCS 64 + +#define DN2ID_TABLE "OL_dn2id" +#define EID_COLUMN 0U +#define VID_COLUMN 1U +#define OCS_COLUMN 1U +#define RDN_COLUMN 2U +#define IDX_COLUMN (2U+NDB_MAX_RDNS) + +#define NEXTID_TABLE "OL_nextid" + +#define NDB_OC_BUFLEN 1026 /* 1024 data plus 2 len bytes */ + +#define INDEX_NAME "OL_index" + +typedef struct NdbRdns { + short nr_num; + char nr_buf[NDB_MAX_RDNS][NDB_RDN_LEN+1]; +} NdbRdns; + +typedef struct NdbOcs { + int no_ninfo; + int no_ntext; + int no_nitext; /* number of implicit classes */ + NdbOcInfo *no_info[NDB_MAX_OCS]; + struct berval no_text[NDB_MAX_OCS]; + struct berval no_itext[NDB_MAX_OCS]; /* implicit classes */ +} NdbOcs; + +typedef struct NdbArgs { + Ndb *ndb; + NdbTransaction *txn; + Entry *e; + NdbRdns *rdns; + struct berval *ocs; + int erdns; +} NdbArgs; + +#define NDB_NO_SUCH_OBJECT 626 +#define NDB_ALREADY_EXISTS 630 + +LDAP_END_DECL + +#include "proto-ndb.h" + +#endif diff --git a/servers/slapd/back-ndb/bind.cpp b/servers/slapd/back-ndb/bind.cpp new file mode 100644 index 0000000..f8d2df7 --- /dev/null +++ b/servers/slapd/back-ndb/bind.cpp @@ -0,0 +1,165 @@ +/* bind.cpp - ndb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "back-ndb.h" + +extern "C" int +ndb_back_bind( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Attribute *a; + + AttributeDescription *password = slap_schema.si_ad_userPassword; + + NdbArgs NA; + + Debug( LDAP_DEBUG_ARGS, + "==> " LDAP_XSTRING(ndb_back_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 */ + break; + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + +dn2entry_retry: + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_bind) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + /* get entry */ + { + NdbRdns rdns; + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.ocs = NULL; + rs->sr_err = ndb_entry_get_info( op, &NA, 0, NULL ); + } + switch(rs->sr_err) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + case LDAP_BUSY: + rs->sr_text = "ldap_server_busy"; + goto done; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; +#endif + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto done; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + 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: + NA.txn->close(); + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + if ( rs->sr_err ) { + send_ldap_result( op, rs ); + } + /* front end will send result on success (rs->sr_err==0) */ + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/compare.cpp b/servers/slapd/back-ndb/compare.cpp new file mode 100644 index 0000000..4fc7401 --- /dev/null +++ b/servers/slapd/back-ndb/compare.cpp @@ -0,0 +1,169 @@ +/* compare.cpp - ndb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +int +ndb_back_compare( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Attribute *a; + int manageDSAit = get_manageDSAit( op ); + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + + rdns.nr_num = 0; + NA.rdns = &rdns; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + +dn2entry_retry: + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_compare) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + NA.ocs = NULL; + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto dn2entry_retry; +#endif + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + 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; + rs->sr_flags |= REP_REF_MUSTBEFREED; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, 0, 0 ); + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + if ( !access_allowed( op, &e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + rs->sr_err = LDAP_ASSERTION_FAILED; + } + goto return_results; + } + + if ( !access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc, + &op->oq_compare.rs_ava->aa_value, ACL_COMPARE, NULL ) ) + { + /* return error 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_err = LDAP_INSUFFICIENT_ACCESS; + } + goto return_results; + } + + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + + for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc ); + a != NULL; + a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) ) + { + rs->sr_err = LDAP_COMPARE_FALSE; + + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->oq_compare.rs_ava->aa_value, NULL, + op->o_tmpmemctx ) == 0 ) + { + rs->sr_err = LDAP_COMPARE_TRUE; + break; + } + } + +return_results: + NA.txn->close(); + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + send_ldap_result( op, rs ); + + switch ( rs->sr_err ) { + case LDAP_COMPARE_FALSE: + case LDAP_COMPARE_TRUE: + rs->sr_err = LDAP_SUCCESS; + break; + } + + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/config.cpp b/servers/slapd/back-ndb/config.cpp new file mode 100644 index 0000000..8b79b8b --- /dev/null +++ b/servers/slapd/back-ndb/config.cpp @@ -0,0 +1,333 @@ +/* config.cpp - ndb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" +#include "lutil.h" + +#include "back-ndb.h" + +#include "slap-config.h" + +extern "C" { + static ConfigDriver ndb_cf_gen; +}; + +enum { + NDB_ATLEN = 1, + NDB_ATSET, + NDB_INDEX, + NDB_ATBLOB +}; + +static ConfigTable ndbcfg[] = { + { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_hostname), + "( OLcfgDbAt:6.1 NAME 'olcDbHost' " + "DESC 'Hostname of SQL server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_dbname), + "( OLcfgDbAt:6.2 NAME 'olcDbName' " + "DESC 'Name of SQL database' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_username), + "( OLcfgDbAt:6.3 NAME 'olcDbUser' " + "DESC 'Username for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbpass", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_password), + "( OLcfgDbAt:6.4 NAME 'olcDbPass' " + "DESC 'Password for SQL session' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbport", "port", 2, 2, 0, ARG_UINT|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_port), + "( OLcfgDbAt:6.5 NAME 'olcDbPort' " + "DESC 'Port number of SQL server' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "dbsocket", "path", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_socket), + "( OLcfgDbAt:6.6 NAME 'olcDbSocket' " + "DESC 'Local socket path of SQL server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbflag", "flag", 2, 2, 0, ARG_LONG|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_clflag), + "( OLcfgDbAt:6.7 NAME 'olcDbFlag' " + "DESC 'Flags for SQL session' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "dbconnect", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_connectstr), + "( OLcfgDbAt:6.8 NAME 'olcDbConnect' " + "DESC 'Hostname of NDB server' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "dbconnections", "number", 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(struct ndb_info, ni_nconns), + "( OLcfgDbAt:6.9 NAME 'olcDbConnections' " + "DESC 'Number of cluster connections to open' " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "attrlen", "attr> <len", 3, 3, 0, ARG_MAGIC|NDB_ATLEN, + (void *)ndb_cf_gen, + "( OLcfgDbAt:6.10 NAME 'olcNdbAttrLen' " + "DESC 'Column length of a specific attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "attrset", "set> <attrs", 3, 3, 0, ARG_MAGIC|NDB_ATSET, + (void *)ndb_cf_gen, + "( OLcfgDbAt:6.11 NAME 'olcNdbAttrSet' " + "DESC 'Set of common attributes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "index", "attr", 2, 2, 0, ARG_MAGIC|NDB_INDEX, + (void *)ndb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' " + "DESC 'Attribute to index' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "attrblob", "attr", 2, 2, 0, ARG_MAGIC|NDB_ATBLOB, + (void *)ndb_cf_gen, "( OLcfgDbAt:6.12 NAME 'olcNdbAttrBlob' " + "DESC 'Attribute to treat as a BLOB' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "directory", "dir", 2, 2, 0, ARG_IGNORED, + NULL, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' " + "DESC 'Dummy keyword' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +static ConfigOCs ndbocs[] = { + { + "( OLcfgDbOc:6.2 " + "NAME 'olcNdbConfig' " + "DESC 'NDB backend configuration' " + "SUP olcDatabaseConfig " + "MUST ( olcDbHost $ olcDbName $ olcDbConnect ) " + "MAY ( olcDbUser $ olcDbPass $ olcDbPort $ olcDbSocket $ " + "olcDbFlag $ olcDbConnections $ olcNdbAttrLen $ " + "olcDbIndex $ olcNdbAttrSet $ olcNdbAttrBlob ) )", + Cft_Database, ndbcfg }, + { NULL, Cft_Abstract, NULL } +}; + +static int +ndb_cf_gen( ConfigArgs *c ) +{ + struct ndb_info *ni = (struct ndb_info *)c->be->be_private; + int i, rc; + NdbAttrInfo *ai; + NdbOcInfo *oci; + ListNode *ln, **l2; + struct berval bv, *bva; + + if ( c->op == SLAP_CONFIG_EMIT ) { + char buf[BUFSIZ]; + rc = 0; + bv.bv_val = buf; + switch( c->type ) { + case NDB_ATLEN: + if ( ni->ni_attrlens ) { + for ( ln = ni->ni_attrlens; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + bv.bv_len = snprintf( buf, sizeof(buf), + "%s %d", ai->na_name.bv_val, + ai->na_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + + case NDB_ATSET: + if ( ni->ni_attrsets ) { + char *ptr, *end = buf+sizeof(buf); + for ( ln = ni->ni_attrsets; ln; ln=ln->ln_next ) { + oci = (NdbOcInfo *)ln->ln_data; + ptr = lutil_strcopy( buf, oci->no_name.bv_val ); + *ptr++ = ' '; + for ( i=0; i<oci->no_nattrs; i++ ) { + if ( end - ptr < oci->no_attrs[i]->na_name.bv_len+1 ) + break; + if ( i ) + *ptr++ = ','; + ptr = lutil_strcopy(ptr, + oci->no_attrs[i]->na_name.bv_val ); + } + bv.bv_len = ptr - buf; + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + + case NDB_INDEX: + if ( ni->ni_attridxs ) { + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + value_add_one( &c->rvalue_vals, &ai->na_name ); + } + } else { + rc = 1; + } + break; + + case NDB_ATBLOB: + if ( ni->ni_attrblobs ) { + for ( ln = ni->ni_attrblobs; ln; ln=ln->ln_next ) { + ai = (NdbAttrInfo *)ln->ln_data; + value_add_one( &c->rvalue_vals, &ai->na_name ); + } + } else { + rc = 1; + } + break; + + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */ + rc = 0; + switch( c->type ) { + case NDB_INDEX: + if ( c->valx == -1 ) { + + /* delete all */ + + } else { + + } + break; + } + return rc; + } + + switch( c->type ) { + case NDB_ATLEN: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attrlens; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr len already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_len = atoi( c->argv[2] ); + ai->na_flag |= NDB_INFO_ATLEN; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrlens; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_INDEX: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attridxs; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr index already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_flag |= NDB_INFO_INDEX; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attridxs; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_ATSET: + ber_str2bv( c->argv[1], 0, 0, &bv ); + bva = ndb_str2bvarray( c->argv[2], strlen( c->argv[2] ), ',', NULL ); + rc = ndb_aset_get( ni, &bv, bva, &oci ); + ber_bvarray_free( bva ); + if ( rc ) { + if ( rc == LDAP_ALREADY_EXISTS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: attrset %s already defined", + c->log, c->argv[1] ); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s: invalid attrset %s (%d)", + c->log, c->argv[1], rc ); + } + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = oci; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrsets; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + case NDB_ATBLOB: + ber_str2bv( c->argv[1], 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + if ( !ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid attr %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + for ( ln = ni->ni_attrblobs; ln; ln = ln->ln_next ) { + if ( ln->ln_data == (void *)ai ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: attr blob already set for %s", + c->log, c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg, 0, 0 ); + return -1; + } + } + ai->na_flag |= NDB_INFO_ATBLOB; + ln = (ListNode *)ch_malloc( sizeof(ListNode)); + ln->ln_data = ai; + ln->ln_next = NULL; + for ( l2 = &ni->ni_attrblobs; *l2; l2 = &(*l2)->ln_next ); + *l2 = ln; + break; + + } + return 0; +} + +extern "C" +int ndb_back_init_cf( BackendInfo *bi ) +{ + bi->bi_cf_ocs = ndbocs; + + return config_register_schema( ndbcfg, ndbocs ); +} diff --git a/servers/slapd/back-ndb/delete.cpp b/servers/slapd/back-ndb/delete.cpp new file mode 100644 index 0000000..46b33f6 --- /dev/null +++ b/servers/slapd/back-ndb/delete.cpp @@ -0,0 +1,322 @@ +/* delete.cpp - ndb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "lutil.h" +#include "back-ndb.h" + +static struct berval glue_bv = BER_BVC("glue"); + +int +ndb_back_delete( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + Entry p = {0}; + int manageDSAit = get_manageDSAit( op ); + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + int num_retries = 0; + + int rc; + + LDAPControl **preread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(ndb_back_delete) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + 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 ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) { + dnParent( &op->o_req_dn, &p.e_name ); + dnParent( &op->o_req_ndn, &p.e_nname ); + } + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.ocs = NULL; + NA.e = &e; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + Debug( LDAP_DEBUG_TRACE, + "==> " LDAP_XSTRING(ndb_back_delete) ": retrying...\n", + 0, 0, 0 ); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA.ocs ) { + ber_bvarray_free( NA.ocs ); + NA.ocs = NULL; + } + ndb_trans_backoff( ++num_retries ); + } + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 1, &matched ); + switch( rs->sr_err ) { + case 0: + case LDAP_NO_SUCH_OBJECT: + break; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + 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 == LDAP_NO_SUCH_OBJECT || + ( !manageDSAit && bvmatch( NA.ocs, &glue_bv ))) { + Debug( LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_delete) ": no such object %s\n", + op->o_req_dn.bv_val, 0, 0); + + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + } else { + rs->sr_matched = p.e_name.bv_val; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + goto return_results; + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, &p, + children, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_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; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + + rs->sr_err = access_allowed( op, &e, + entry, NULL, ACL_WDEL, NULL ); + + if ( !rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_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(ndb_back_delete) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + 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(ndb_back_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; + } + } + } + + /* Can't do it if we have kids */ + rs->sr_err = ndb_has_children( &NA, &rc ); + if ( rs->sr_err ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_delete) + ": has_children failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rc == LDAP_COMPARE_TRUE ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_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"; + goto return_results; + } + + /* delete info */ + rs->sr_err = ndb_entry_del_info( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": del_info failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_text = "DN index delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + /* delete data */ + rs->sr_err = ndb_entry_del_data( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_delete) ": del_data failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_text = "entry delete failed"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + + if( op->o_noop ) { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "commit failed"; + + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_delete) ": deleted%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + /* free entry */ + if( e.e_attrs != NULL ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + 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 ); + } + return rs->sr_err; +} diff --git a/servers/slapd/back-ndb/init.cpp b/servers/slapd/back-ndb/init.cpp new file mode 100644 index 0000000..d8f6276 --- /dev/null +++ b/servers/slapd/back-ndb/init.cpp @@ -0,0 +1,449 @@ +/* init.cpp - initialize ndb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#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-ndb.h" +#include <lutil.h> +#include "slap-config.h" + +extern "C" { + static BI_db_init ndb_db_init; + static BI_db_close ndb_db_close; + static BI_db_open ndb_db_open; + static BI_db_destroy ndb_db_destroy; +} + +static struct berval ndb_optable = BER_BVC("OL_opattrs"); + +static struct berval ndb_opattrs[] = { + BER_BVC("structuralObjectClass"), + BER_BVC("entryUUID"), + BER_BVC("creatorsName"), + BER_BVC("createTimestamp"), + BER_BVC("entryCSN"), + BER_BVC("modifiersName"), + BER_BVC("modifyTimestamp"), + BER_BVNULL +}; + +static int ndb_oplens[] = { + 0, /* structuralOC, default */ + 36, /* entryUUID */ + 0, /* creatorsName, default */ + 26, /* createTimestamp */ + 40, /* entryCSN */ + 0, /* modifiersName, default */ + 26, /* modifyTimestamp */ + -1 +}; + +static Uint32 ndb_lastrow[1]; +NdbInterpretedCode *ndb_lastrow_code; + +static int +ndb_db_init( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni; + int rc = 0; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_db_init) ": Initializing ndb database\n", + 0, 0, 0 ); + + /* allocate backend-database-specific stuff */ + ni = (struct ndb_info *) ch_calloc( 1, sizeof(struct ndb_info) ); + + be->be_private = ni; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + ni->ni_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH; + + ldap_pvt_thread_rdwr_init( &ni->ni_ai_rwlock ); + ldap_pvt_thread_rdwr_init( &ni->ni_oc_rwlock ); + ldap_pvt_thread_mutex_init( &ni->ni_conn_mutex ); + +#ifdef DO_MONITORING + rc = ndb_monitor_db_init( be ); +#endif + + return rc; +} + +static int +ndb_db_close( BackendDB *be, ConfigReply *cr ); + +static int +ndb_db_open( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + char sqlbuf[BUFSIZ], *ptr; + int rc, i; + + if ( be->be_suffix == NULL ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: need suffix" ); + Debug( LDAP_DEBUG_ANY, "%s\n", + cr->msg, 0, 0 ); + return -1; + } + + Debug( LDAP_DEBUG_ARGS, + LDAP_XSTRING(ndb_db_open) ": \"%s\"\n", + be->be_suffix[0].bv_val, 0, 0 ); + + if ( ni->ni_nconns < 1 ) + ni->ni_nconns = 1; + + ni->ni_cluster = (Ndb_cluster_connection **)ch_calloc( ni->ni_nconns, sizeof( Ndb_cluster_connection *)); + for ( i=0; i<ni->ni_nconns; i++ ) { + ni->ni_cluster[i] = new Ndb_cluster_connection( ni->ni_connectstr ); + rc = ni->ni_cluster[i]->connect( 20, 5, 1 ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ni_cluster[%d]->connect failed (%d)", + i, rc ); + goto fail; + } + } + for ( i=0; i<ni->ni_nconns; i++ ) { + rc = ni->ni_cluster[i]->wait_until_ready( 30, 30 ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ni_cluster[%d]->wait failed (%d)", + i, rc ); + goto fail; + } + } + + mysql_init( &ni->ni_sql ); + if ( !mysql_real_connect( &ni->ni_sql, ni->ni_hostname, ni->ni_username, ni->ni_password, + "", ni->ni_port, ni->ni_socket, ni->ni_clflag )) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: mysql_real_connect failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + rc = -1; + goto fail; + } + + sprintf( sqlbuf, "CREATE DATABASE IF NOT EXISTS %s", ni->ni_dbname ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE DATABASE %s failed, %s (%d)", + ni->ni_dbname, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + sprintf( sqlbuf, "USE %s", ni->ni_dbname ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: USE DATABASE %s failed, %s (%d)", + ni->ni_dbname, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + ptr = sqlbuf; + ptr += sprintf( ptr, "CREATE TABLE IF NOT EXISTS " DN2ID_TABLE " (" + "eid bigint unsigned NOT NULL, " + "object_classes VARCHAR(1024) NOT NULL, " + "a0 VARCHAR(128) NOT NULL DEFAULT '', " + "a1 VARCHAR(128) NOT NULL DEFAULT '', " + "a2 VARCHAR(128) NOT NULL DEFAULT '', " + "a3 VARCHAR(128) NOT NULL DEFAULT '', " + "a4 VARCHAR(128) NOT NULL DEFAULT '', " + "a5 VARCHAR(128) NOT NULL DEFAULT '', " + "a6 VARCHAR(128) NOT NULL DEFAULT '', " + "a7 VARCHAR(128) NOT NULL DEFAULT '', " + "a8 VARCHAR(128) NOT NULL DEFAULT '', " + "a9 VARCHAR(128) NOT NULL DEFAULT '', " + "a10 VARCHAR(128) NOT NULL DEFAULT '', " + "a11 VARCHAR(128) NOT NULL DEFAULT '', " + "a12 VARCHAR(128) NOT NULL DEFAULT '', " + "a13 VARCHAR(128) NOT NULL DEFAULT '', " + "a14 VARCHAR(128) NOT NULL DEFAULT '', " + "a15 VARCHAR(128) NOT NULL DEFAULT '', " + "PRIMARY KEY (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15), " + "UNIQUE KEY eid (eid) USING HASH" ); + /* Create index columns */ + if ( ni->ni_attridxs ) { + ListNode *ln; + int newcol = 0; + + *ptr++ = ','; + *ptr++ = ' '; + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + NdbAttrInfo *ai = (NdbAttrInfo *)ln->ln_data; + ptr += sprintf( ptr, "`%s` VARCHAR(%d), ", + ai->na_name.bv_val, ai->na_len ); + } + ptr = lutil_strcopy(ptr, "KEY " INDEX_NAME " (" ); + + for ( ln = ni->ni_attridxs; ln; ln=ln->ln_next ) { + NdbAttrInfo *ai = (NdbAttrInfo *)ln->ln_data; + if ( newcol ) *ptr++ = ','; + *ptr++ = '`'; + ptr = lutil_strcopy( ptr, ai->na_name.bv_val ); + *ptr++ = '`'; + ai->na_ixcol = newcol + 18; + newcol++; + } + *ptr++ = ')'; + } + strcpy( ptr, ") ENGINE=ndb" ); + rc = mysql_query( &ni->ni_sql, sqlbuf ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE TABLE " DN2ID_TABLE " failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + rc = mysql_query( &ni->ni_sql, "CREATE TABLE IF NOT EXISTS " NEXTID_TABLE " (" + "a bigint unsigned AUTO_INCREMENT PRIMARY KEY ) ENGINE=ndb" ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: CREATE TABLE " NEXTID_TABLE " failed, %s (%d)", + mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + goto fail; + } + + { + NdbOcInfo *oci; + + rc = ndb_aset_get( ni, &ndb_optable, ndb_opattrs, &oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_get( %s ) failed (%d)", + ndb_optable.bv_val, rc ); + goto fail; + } + for ( i=0; ndb_oplens[i] >= 0; i++ ) { + if ( ndb_oplens[i] ) + oci->no_attrs[i]->na_len = ndb_oplens[i]; + } + rc = ndb_aset_create( ni, oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_create( %s ) failed (%d)", + ndb_optable.bv_val, rc ); + goto fail; + } + ni->ni_opattrs = oci; + } + /* Create attribute sets */ + { + ListNode *ln; + + for ( ln = ni->ni_attrsets; ln; ln=ln->ln_next ) { + NdbOcInfo *oci = (NdbOcInfo *)ln->ln_data; + rc = ndb_aset_create( ni, oci ); + if ( rc ) { + snprintf( cr->msg, sizeof( cr->msg ), + "ndb_db_open: ndb_aset_create( %s ) failed (%d)", + oci->no_name.bv_val, rc ); + goto fail; + } + } + } + /* Initialize any currently used objectClasses */ + { + Ndb *ndb; + const NdbDictionary::Dictionary *myDict; + + ndb = new Ndb( ni->ni_cluster[0], ni->ni_dbname ); + ndb->init(1024); + + myDict = ndb->getDictionary(); + ndb_oc_read( ni, myDict ); + delete ndb; + } + +#ifdef DO_MONITORING + /* monitor setup */ + rc = ndb_monitor_db_open( be ); + if ( rc != 0 ) { + goto fail; + } +#endif + + return 0; + +fail: + Debug( LDAP_DEBUG_ANY, "%s\n", + cr->msg, 0, 0 ); + ndb_db_close( be, NULL ); + return rc; +} + +static int +ndb_db_close( BackendDB *be, ConfigReply *cr ) +{ + int i; + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + mysql_close( &ni->ni_sql ); + if ( ni->ni_cluster ) { + for ( i=0; i<ni->ni_nconns; i++ ) { + if ( ni->ni_cluster[i] ) { + delete ni->ni_cluster[i]; + ni->ni_cluster[i] = NULL; + } + } + ch_free( ni->ni_cluster ); + ni->ni_cluster = NULL; + } + +#ifdef DO_MONITORING + /* monitor handling */ + (void)ndb_monitor_db_close( be ); +#endif + + return 0; +} + +static int +ndb_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + +#ifdef DO_MONITORING + /* monitor handling */ + (void)ndb_monitor_db_destroy( be ); +#endif + + ldap_pvt_thread_mutex_destroy( &ni->ni_conn_mutex ); + ldap_pvt_thread_rdwr_destroy( &ni->ni_ai_rwlock ); + ldap_pvt_thread_rdwr_destroy( &ni->ni_oc_rwlock ); + + ch_free( ni ); + be->be_private = NULL; + + return 0; +} + +extern "C" int +ndb_back_initialize( + BackendInfo *bi ) +{ + static char *controls[] = { + LDAP_CONTROL_ASSERT, + LDAP_CONTROL_MANAGEDSAIT, + LDAP_CONTROL_NOOP, + LDAP_CONTROL_PAGEDRESULTS, + LDAP_CONTROL_PRE_READ, + LDAP_CONTROL_POST_READ, + LDAP_CONTROL_SUBENTRIES, + LDAP_CONTROL_X_PERMISSIVE_MODIFY, + LDAP_CONTROL_TXN_SPEC, + NULL + }; + + int rc = 0; + + /* initialize the underlying database system */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_initialize) ": initialize ndb backend\n", 0, 0, 0 ); + + ndb_init(); + + ndb_lastrow_code = new NdbInterpretedCode( NULL, ndb_lastrow, 1 ); + ndb_lastrow_code->interpret_exit_last_row(); + ndb_lastrow_code->finalise(); + + bi->bi_flags |= + SLAP_BFLAG_INCREMENT | + SLAP_BFLAG_SUBENTRIES | + SLAP_BFLAG_ALIASES | + SLAP_BFLAG_REFERRALS; + + bi->bi_controls = controls; + + bi->bi_open = 0; + bi->bi_close = 0; + bi->bi_config = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = ndb_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = ndb_db_open; + bi->bi_db_close = ndb_db_close; + bi->bi_db_destroy = ndb_db_destroy; + + bi->bi_op_add = ndb_back_add; + bi->bi_op_bind = ndb_back_bind; + bi->bi_op_compare = ndb_back_compare; + bi->bi_op_delete = ndb_back_delete; + bi->bi_op_modify = ndb_back_modify; + bi->bi_op_modrdn = ndb_back_modrdn; + bi->bi_op_search = ndb_back_search; + + bi->bi_op_unbind = 0; + +#if 0 + bi->bi_extended = ndb_extended; + + bi->bi_chk_referrals = ndb_referrals; +#endif + bi->bi_operational = ndb_operational; + bi->bi_has_subordinates = ndb_has_subordinates; + bi->bi_entry_release_rw = 0; + bi->bi_entry_get_rw = ndb_entry_get; + + /* + * hooks for slap tools + */ + bi->bi_tool_entry_open = ndb_tool_entry_open; + bi->bi_tool_entry_close = ndb_tool_entry_close; + bi->bi_tool_entry_first = ndb_tool_entry_first; + bi->bi_tool_entry_next = ndb_tool_entry_next; + bi->bi_tool_entry_get = ndb_tool_entry_get; + bi->bi_tool_entry_put = ndb_tool_entry_put; +#if 0 + bi->bi_tool_entry_reindex = ndb_tool_entry_reindex; + bi->bi_tool_sync = 0; + bi->bi_tool_dn2id_get = ndb_tool_dn2id_get; + bi->bi_tool_entry_modify = ndb_tool_entry_modify; +#endif + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = 0; + + rc = ndb_back_init_cf( bi ); + + return rc; +} + +#if SLAPD_NDB == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +extern "C" { int init_module( int argc, char *argv[] ); } + +SLAP_BACKEND_INIT_MODULE( ndb ) + +#endif /* SLAPD_NDB == SLAPD_MOD_DYNAMIC */ + diff --git a/servers/slapd/back-ndb/modify.cpp b/servers/slapd/back-ndb/modify.cpp new file mode 100644 index 0000000..243e9d2 --- /dev/null +++ b/servers/slapd/back-ndb/modify.cpp @@ -0,0 +1,704 @@ +/* modify.cpp - ndb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "back-ndb.h" + +/* This is a copy from slapd/mods.c, but with compaction tweaked + * to swap values from the tail into deleted slots, to reduce the + * overall update traffic. + */ +static int +ndb_modify_delete( + Entry *e, + Modification *mod, + int permissive, + const char **text, + char *textbuf, size_t textlen, + int *idx ) +{ + Attribute *a; + MatchingRule *mr = mod->sm_desc->ad_type->sat_equality; + struct berval *cvals; + int *id2 = NULL; + int i, j, rc = 0, num; + unsigned flags; + char dummy = '\0'; + + /* For ordered vals, we have no choice but to preserve order */ + if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) + return modify_delete_vindex( e, mod, permissive, text, + textbuf, textlen, idx ); + + /* + * If permissive is set, then the non-existence of an + * attribute is not treated as an error. + */ + + /* delete the entire attribute */ + if ( mod->sm_values == NULL ) { + rc = attr_delete( &e->e_attrs, mod->sm_desc ); + + if( permissive ) { + rc = LDAP_SUCCESS; + } else if( rc != LDAP_SUCCESS ) { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + return rc; + } + + /* FIXME: Catch old code that doesn't set sm_numvals. + */ + if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) { + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ); + assert( mod->sm_numvals == i ); + } + if ( !idx ) { + id2 = (int *)ch_malloc( mod->sm_numvals * sizeof( int )); + idx = id2; + } + + if( mr == NULL || !mr->smr_match ) { + /* disallow specific attributes from being deleted if + no equality rule */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no equality matching rule", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_INAPPROPRIATE_MATCHING; + goto return_result; + } + + /* delete specific values - find the attribute first */ + if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) { + if( permissive ) { + rc = LDAP_SUCCESS; + goto return_result; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto return_result; + } + + if ( mod->sm_nvalues ) { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX + | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH + | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH; + cvals = mod->sm_nvalues; + } else { + flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX; + cvals = mod->sm_values; + } + + /* Locate values to delete */ + for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ) { + unsigned sort; + rc = attr_valfind( a, flags, &cvals[i], &sort, NULL ); + if ( rc == LDAP_SUCCESS ) { + idx[i] = sort; + } else if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) { + if ( permissive ) { + idx[i] = -1; + continue; + } + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such value", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } else { + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: matching rule failed", + mod->sm_desc->ad_cname.bv_val ); + goto return_result; + } + } + + num = a->a_numvals; + + /* Delete the values */ + for ( i = 0; i < mod->sm_numvals; i++ ) { + /* Skip permissive values that weren't found */ + if ( idx[i] < 0 ) + continue; + /* Skip duplicate delete specs */ + if ( a->a_vals[idx[i]].bv_val == &dummy ) + continue; + /* delete value and mark it as gone */ + free( a->a_vals[idx[i]].bv_val ); + a->a_vals[idx[i]].bv_val = &dummy; + if( a->a_nvals != a->a_vals ) { + free( a->a_nvals[idx[i]].bv_val ); + a->a_nvals[idx[i]].bv_val = &dummy; + } + a->a_numvals--; + } + + /* compact array */ + for ( i=0; i<num; i++ ) { + if ( a->a_vals[i].bv_val != &dummy ) + continue; + for ( --num; num > i && a->a_vals[num].bv_val == &dummy; num-- ) + ; + a->a_vals[i] = a->a_vals[num]; + if ( a->a_nvals != a->a_vals ) + a->a_nvals[i] = a->a_nvals[num]; + } + + BER_BVZERO( &a->a_vals[num] ); + if (a->a_nvals != a->a_vals) { + BER_BVZERO( &a->a_nvals[num] ); + } + + /* if no values remain, delete the entire attribute */ + if ( !a->a_numvals ) { + if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) { + /* Can never happen */ + *text = textbuf; + snprintf( textbuf, textlen, + "modify/delete: %s: no such attribute", + mod->sm_desc->ad_cname.bv_val ); + rc = LDAP_NO_SUCH_ATTRIBUTE; + } + } +return_result: + if ( id2 ) + ch_free( id2 ); + return rc; +} + +int ndb_modify_internal( + Operation *op, + NdbArgs *NA, + const char **text, + char *textbuf, + size_t textlen ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Modification *mod; + Modifications *ml; + Modifications *modlist = op->orm_modlist; + NdbAttrInfo **modai, *atmp; + const NdbDictionary::Dictionary *myDict; + const NdbDictionary::Table *myTable; + int got_oc = 0, nmods = 0, nai = 0, i, j; + int rc, indexed = 0; + Attribute *old = NULL; + + Debug( LDAP_DEBUG_TRACE, "ndb_modify_internal: 0x%08lx: %s\n", + NA->e->e_id, NA->e->e_dn, 0); + + if ( !acl_check_modlist( op, NA->e, modlist )) { + return LDAP_INSUFFICIENT_ACCESS; + } + + old = attrs_dup( NA->e->e_attrs ); + + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + mod = &ml->sml_mod; + nmods++; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: add %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_DELETE: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: delete %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = ndb_modify_delete( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen, NULL ); + assert( rc != LDAP_TYPE_OR_VALUE_EXISTS ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_REPLACE: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: replace %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_replace_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case LDAP_MOD_INCREMENT: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: increment %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + rc = modify_increment_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_SOFTADD: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: softadd %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + mod->sm_op = LDAP_MOD_ADD; + + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTADD; + + if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) { + rc = LDAP_SUCCESS; + } + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_SOFTDEL: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: softdel %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + mod->sm_op = LDAP_MOD_DELETE; + + rc = modify_delete_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTDEL; + + if ( rc == LDAP_NO_SUCH_ATTRIBUTE) { + rc = LDAP_SUCCESS; + } + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: add_if_not_present %s\n", + mod->sm_desc->ad_cname.bv_val, 0, 0); + if ( attr_find( NA->e->e_attrs, mod->sm_desc ) ) { + rc = LDAP_SUCCESS; + break; + } + + mod->sm_op = LDAP_MOD_ADD; + + rc = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + + if( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "ndb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + rc = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + rc, *text, 0); + } + + if ( rc != LDAP_SUCCESS ) { + attrs_free( old ); + return rc; + } + + /* If objectClass was modified, reset the flags */ + if ( mod->sm_desc == slap_schema.si_ad_objectClass ) { + NA->e->e_ocflags = 0; + got_oc = 1; + } + } + + /* check that the entry still obeys the schema */ + rc = entry_schema_check( op, NA->e, NULL, get_relax(op), 0, NULL, + text, textbuf, textlen ); + if ( rc != LDAP_SUCCESS || op->o_noop ) { + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "entry failed schema check: %s\n", + *text, 0, 0 ); + } + attrs_free( old ); + return rc; + } + + if ( got_oc ) { + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + if ( rc ) { + attrs_free( old ); + return rc; + } + } + + /* apply modifications to DB */ + modai = (NdbAttrInfo **)op->o_tmpalloc( nmods * sizeof(NdbAttrInfo*), op->o_tmpmemctx ); + + /* Get the unique list of modified attributes */ + ldap_pvt_thread_rdwr_rlock( &ni->ni_ai_rwlock ); + for ( ml = modlist; ml != NULL; ml = ml->sml_next ) { + /* Already took care of objectclass */ + if ( ml->sml_desc == slap_schema.si_ad_objectClass ) + continue; + for ( i=0; i<nai; i++ ) { + if ( ml->sml_desc->ad_type == modai[i]->na_attr ) + break; + } + /* This attr was already updated */ + if ( i < nai ) + continue; + modai[nai] = ndb_ai_find( ni, ml->sml_desc->ad_type ); + if ( modai[nai]->na_flag & NDB_INFO_INDEX ) + indexed++; + nai++; + } + ldap_pvt_thread_rdwr_runlock( &ni->ni_ai_rwlock ); + + /* If got_oc, this was already done above */ + if ( indexed && !got_oc) { + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + if ( rc ) { + attrs_free( old ); + return rc; + } + } + + myDict = NA->ndb->getDictionary(); + + /* sort modai so that OcInfo's are contiguous */ + { + int j, k; + for ( i=0; i<nai; i++ ) { + for ( j=i+1; j<nai; j++ ) { + if ( modai[i]->na_oi == modai[j]->na_oi ) + continue; + for ( k=j+1; k<nai; k++ ) { + if ( modai[i]->na_oi == modai[k]->na_oi ) { + atmp = modai[j]; + modai[j] = modai[k]; + modai[k] = atmp; + break; + } + } + /* there are no more na_oi's that match modai[i] */ + if ( k == nai ) { + i = j; + } + } + } + } + + /* One call per table... */ + for ( i=0; i<nai; i += j ) { + atmp = modai[i]; + for ( j=i+1; j<nai; j++ ) + if ( atmp->na_oi != modai[j]->na_oi ) + break; + j -= i; + myTable = myDict->getTable( atmp->na_oi->no_table.bv_val ); + if ( !myTable ) + continue; + rc = ndb_oc_attrs( NA->txn, myTable, NA->e, atmp->na_oi, &modai[i], j, old ); + if ( rc ) break; + } + attrs_free( old ); + return rc; +} + + +int +ndb_back_modify( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + Entry e = {0}; + int manageDSAit = get_manageDSAit( op ); + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + + int num_retries = 0; + + NdbArgs NA; + NdbRdns rdns; + struct berval matched; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(ndb_back_modify) ": %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + ctrls[num_ctrls] = NULL; + + slap_mods_opattrs( op, &op->orm_modlist, 1 ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.e = &e; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": retrying...\n", 0, 0, 0); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + } + ndb_trans_backoff( ++num_retries ); + } + NA.ocs = NULL; + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* get entry or ancestor */ + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_ARGS, + "<=- ndb_back_modify: no such object %s\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_matched = matched.bv_val; + if (NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* acquire and lock entry */ + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + + 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(ndb_back_modify) ": entry is referral\n", + 0, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter*)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(ndb_back_modify) ": pre-read " + "failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* Modify the entry */ + rs->sr_err = ndb_modify_internal( op, &NA, &rs->sr_text, textbuf, textlen ); + + if( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": modify failed (%d)\n", + rs->sr_err, 0, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + 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, &e, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_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=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modify) ": updated%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + if ( e.e_attrs != NULL ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + 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( 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-ndb/modrdn.cpp b/servers/slapd/back-ndb/modrdn.cpp new file mode 100644 index 0000000..ecc3c8e --- /dev/null +++ b/servers/slapd/back-ndb/modrdn.cpp @@ -0,0 +1,558 @@ +/* modrdn.cpp - ndb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "back-ndb.h" + +int +ndb_back_modrdn( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + AttributeDescription *children = slap_schema.si_ad_children; + AttributeDescription *entry = slap_schema.si_ad_entry; + struct berval new_dn = BER_BVNULL, new_ndn = BER_BVNULL; + Entry e = {0}; + Entry e2 = {0}; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof textbuf; + + struct berval *np_dn = NULL; /* newSuperior dn */ + struct berval *np_ndn = NULL; /* newSuperior ndn */ + + int manageDSAit = get_manageDSAit( op ); + int num_retries = 0; + + NdbArgs NA, NA2; + NdbRdns rdns, rdn2; + struct berval matched; + + LDAPControl **preread_ctrl = NULL; + LDAPControl **postread_ctrl = NULL; + LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS]; + int num_ctrls = 0; + + int rc; + + Debug( LDAP_DEBUG_ARGS, "==>" LDAP_XSTRING(ndb_back_modrdn) "(%s,%s,%s)\n", + op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val, + op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" ); + + ctrls[num_ctrls] = NULL; + + slap_mods_opattrs( op, &op->orr_modlist, 1 ); + + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + + /* Get our NDB handle */ + rs->sr_err = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + NA.rdns = &rdns; + NA.e = &e; + NA2.ndb = NA.ndb; + NA2.e = &e2; + NA2.rdns = &rdn2; + + if( 0 ) { +retry: /* transaction retry */ + NA.txn->close(); + NA.txn = NULL; + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(ndb_back_modrdn) + ": retrying...\n", 0, 0, 0 ); + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + goto return_results; + } + if ( NA2.ocs ) { + ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx ); + } + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + } + ndb_trans_backoff( ++num_retries ); + } + NA.ocs = NULL; + NA2.ocs = NULL; + + /* begin transaction */ + NA.txn = NA.ndb->startTransaction(); + rs->sr_text = NULL; + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + NA2.txn = NA.txn; + + /* get entry */ + rs->sr_err = ndb_entry_get_info( op, &NA, 1, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_ARGS, + "<=- ndb_back_modrdn: no such object %s\n", + op->o_req_dn.bv_val, 0, 0 ); + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_BUSY: + rs->sr_text = "ldap server busy"; + goto return_results; + default: + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + + /* acquire and lock entry */ + rs->sr_err = ndb_entry_get_data( op, &NA, 1 ); + if ( rs->sr_err ) + goto return_results; + + if ( !manageDSAit && is_entry_glue( &e )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto return_results; + } + + if ( get_assert( op ) && + ( test_filter( op, &e, (Filter *)get_assertion( op )) != LDAP_COMPARE_TRUE )) + { + rs->sr_err = LDAP_ASSERTION_FAILED; + goto return_results; + } + + /* check write on old entry */ + rs->sr_err = access_allowed( op, &e, entry, NULL, ACL_WRITE, NULL ); + if ( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, "no access to entry\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old entry"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + /* Can't do it if we have kids */ + rs->sr_err = ndb_has_children( &NA, &rc ); + if ( rs->sr_err ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": has_children failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto return_results; + } + if ( rc == LDAP_COMPARE_TRUE ) { + Debug(LDAP_DEBUG_ARGS, + "<=- " LDAP_XSTRING(ndb_back_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"; + goto return_results; + } + + if (!manageDSAit && is_entry_referral( &e ) ) { + /* entry is a referral, don't allow modrdn */ + rs->sr_ref = get_entry_referrals( op, &e ); + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn) + ": entry %s is referral\n", e.e_dn, 0, 0 ); + + rs->sr_err = LDAP_REFERRAL, + rs->sr_matched = op->o_req_dn.bv_val; + rs->sr_flags = REP_REF_MUSTBEFREED; + goto return_results; + } + + if ( be_issuffix( op->o_bd, &e.e_nname ) ) { + /* There can only be one suffix entry */ + rs->sr_err = LDAP_NAMING_VIOLATION; + rs->sr_text = "cannot rename suffix entry"; + goto return_results; + } else { + dnParent( &e.e_nname, &e2.e_nname ); + dnParent( &e.e_name, &e2.e_name ); + } + + /* check parent for "children" acl */ + rs->sr_err = access_allowed( op, &e2, + children, NULL, + op->oq_modrdn.rs_newSup == NULL ? + ACL_WRITE : ACL_WDEL, + NULL ); + + if ( ! rs->sr_err ) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + Debug( LDAP_DEBUG_TRACE, "no access to parent\n", 0, + 0, 0 ); + rs->sr_text = "no write access to old parent's children"; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": wr to children " + "of entry %s OK\n", e2.e_name.bv_val, 0, 0 ); + + if ( op->oq_modrdn.rs_newSup != NULL ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": new parent \"%s\" requested...\n", + op->oq_modrdn.rs_newSup->bv_val, 0, 0 ); + + /* newSuperior == oldParent? */ + if( dn_match( &e2.e_nname, 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, e2.e_name.bv_val, 0 ); + op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */ + } + } + + if ( op->oq_modrdn.rs_newSup != NULL ) { + if ( op->oq_modrdn.rs_newSup->bv_len ) { + rdn2.nr_num = 0; + 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? */ + + e2.e_name = *np_dn; + e2.e_nname = *np_ndn; + rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": newSup(ndn=%s) not here!\n", + np_ndn->bv_val, 0, 0); + rs->sr_text = "new superior not found"; + goto return_results; +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + 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 ( NA2.ocs ) { + Attribute a; + int i; + + for ( i=0; !BER_BVISNULL( &NA2.ocs[i] ); i++); + a.a_numvals = i; + a.a_desc = slap_schema.si_ad_objectClass; + a.a_vals = NA2.ocs; + a.a_nvals = NA2.ocs; + a.a_next = NULL; + e2.e_attrs = &a; + + if ( is_entry_alias( &e2 )) { + /* parent is an alias, don't allow move */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_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( &e2 ) ) { + /* parent is a referral, don't allow move */ + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": entry is referral\n", + 0, 0, 0 ); + rs->sr_text = "new superior is a referral"; + rs->sr_err = LDAP_OTHER; + goto return_results; + } + } + } + + /* check newSuperior for "children" acl */ + rs->sr_err = access_allowed( op, &e2, children, + NULL, ACL_WADD, NULL ); + if( ! rs->sr_err ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": no wr to newSup children\n", + 0, 0, 0 ); + rs->sr_text = "no write access to new superior's children"; + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + goto return_results; + } + + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": wr to new parent OK id=%ld\n", + (long) e2.e_id, 0, 0 ); + } + + /* Build target dn and make sure target entry doesn't exist already. */ + if (!new_dn.bv_val) { + build_new_dn( &new_dn, &e2.e_name, &op->oq_modrdn.rs_newrdn, NULL ); + } + + if (!new_ndn.bv_val) { + build_new_dn( &new_ndn, &e2.e_nname, &op->oq_modrdn.rs_nnewrdn, NULL ); + } + + Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(ndb_back_modrdn) ": new ndn=%s\n", + new_ndn.bv_val, 0, 0 ); + + /* Allow rename to same DN */ + if ( !bvmatch ( &new_ndn, &e.e_nname )) { + rdn2.nr_num = 0; + e2.e_name = new_dn; + e2.e_nname = new_ndn; + NA2.ocs = &matched; + rs->sr_err = ndb_entry_get_info( op, &NA2, 1, NULL ); + NA2.ocs = NULL; + switch( rs->sr_err ) { +#if 0 + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; +#endif + case LDAP_NO_SUCH_OBJECT: + break; + case 0: + 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(ndb_back_modrdn) + ": pre-read failed!\n", 0, 0, 0 ); + if ( op->o_preread & SLAP_CONTROL_CRITICAL ) { + /* FIXME: is it correct to abort + * operation if control fails? */ + goto return_results; + } + } + } + + /* delete old DN */ + rs->sr_err = ndb_entry_del_info( op->o_bd, &NA ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": dn2id del failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index delete fail"; + goto return_results; + } + + /* copy entry fields */ + e2.e_attrs = e.e_attrs; + e2.e_id = e.e_id; + + /* add new DN */ + rs->sr_err = ndb_entry_put_info( op->o_bd, &NA2, 0 ); + if ( rs->sr_err != 0 ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": dn2id add failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + rs->sr_err = LDAP_OTHER; + rs->sr_text = "DN index add failed"; + goto return_results; + } + + /* modify entry */ + rs->sr_err = ndb_modify_internal( op, &NA2, + &rs->sr_text, textbuf, textlen ); + if( rs->sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_modrdn) + ": modify failed: %s (%d)\n", + NA.txn->getNdbError().message, NA.txn->getNdbError().code, 0 ); +#if 0 + switch( rs->sr_err ) { + case DB_LOCK_DEADLOCK: + case DB_LOCK_NOTGRANTED: + goto retry; + } +#endif + goto return_results; + } + + e.e_attrs = e2.e_attrs; + + if( op->o_postread ) { + if( postread_ctrl == NULL ) { + postread_ctrl = &ctrls[num_ctrls++]; + ctrls[num_ctrls] = NULL; + } + if( slap_read_controls( op, rs, &e2, + &slap_post_read_bv, postread_ctrl ) ) + { + Debug( LDAP_DEBUG_TRACE, + "<=- " LDAP_XSTRING(ndb_back_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=NA.txn->execute( NdbTransaction::Rollback, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_abort (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit, + NdbOperation::AbortOnError, 1 )) != 0 ) { + rs->sr_text = "txn_commit failed"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + } + + if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) ": txn_%s failed: %s (%d)\n", + op->o_noop ? "abort (no-op)" : "commit", + NA.txn->getNdbError().message, NA.txn->getNdbError().code ); + rs->sr_err = LDAP_OTHER; + goto return_results; + } + NA.txn->close(); + NA.txn = NULL; + + Debug(LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_modrdn) + ": rdn modified%s id=%08lx dn=\"%s\"\n", + op->o_noop ? " (no-op)" : "", + e.e_id, op->o_req_dn.bv_val ); + + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + if( num_ctrls ) rs->sr_ctrls = ctrls; + +return_results: + if ( NA2.ocs ) { + ber_bvarray_free_x( NA2.ocs, op->o_tmpmemctx ); + NA2.ocs = NULL; + } + + if ( NA.ocs ) { + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + NA.ocs = NULL; + } + + if ( e.e_attrs ) { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + } + + if( NA.txn != NULL ) { + NA.txn->execute( Rollback ); + NA.txn->close(); + } + + send_ldap_result( op, rs ); + 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 ); + + 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-ndb/ndbio.cpp b/servers/slapd/back-ndb/ndbio.cpp new file mode 100644 index 0000000..53559c5 --- /dev/null +++ b/servers/slapd/back-ndb/ndbio.cpp @@ -0,0 +1,1677 @@ +/* ndbio.cpp - get/set/del data for NDB */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> +#include <lutil.h> + +#include "back-ndb.h" + +/* For reference only */ +typedef struct MedVar { + Int16 len; /* length is always little-endian */ + char buf[1024]; +} MedVar; + +extern "C" { + static int ndb_name_cmp( const void *v1, const void *v2 ); + static int ndb_oc_dup_err( void *v1, void *v2 ); +}; + +static int +ndb_name_cmp( const void *v1, const void *v2 ) +{ + NdbOcInfo *oc1 = (NdbOcInfo *)v1, *oc2 = (NdbOcInfo *)v2; + return ber_bvstrcasecmp( &oc1->no_name, &oc2->no_name ); +} + +static int +ndb_oc_dup_err( void *v1, void *v2 ) +{ + NdbOcInfo *oc = (NdbOcInfo *)v2; + + oc->no_oc = (ObjectClass *)v1; + return -1; +} + +/* Find an existing NdbAttrInfo */ +extern "C" NdbAttrInfo * +ndb_ai_find( struct ndb_info *ni, AttributeType *at ) +{ + NdbAttrInfo atmp; + atmp.na_name = at->sat_cname; + + return (NdbAttrInfo *)avl_find( ni->ni_ai_tree, &atmp, ndb_name_cmp ); +} + +/* Find or create an NdbAttrInfo */ +extern "C" NdbAttrInfo * +ndb_ai_get( struct ndb_info *ni, struct berval *aname ) +{ + NdbAttrInfo atmp, *ai; + atmp.na_name = *aname; + + ai = (NdbAttrInfo *)avl_find( ni->ni_ai_tree, &atmp, ndb_name_cmp ); + if ( !ai ) { + const char *text; + AttributeDescription *ad = NULL; + + if ( slap_bv2ad( aname, &ad, &text )) + return NULL; + + ai = (NdbAttrInfo *)ch_malloc( sizeof( NdbAttrInfo )); + ai->na_desc = ad; + ai->na_attr = ai->na_desc->ad_type; + ai->na_name = ai->na_attr->sat_cname; + ai->na_oi = NULL; + ai->na_flag = 0; + ai->na_ixcol = 0; + ai->na_len = ai->na_attr->sat_atype.at_syntax_len; + /* Reasonable default */ + if ( !ai->na_len ) { + if ( ai->na_attr->sat_syntax == slap_schema.si_syn_distinguishedName ) + ai->na_len = 1024; + else + ai->na_len = 128; + } + /* Arbitrary limit */ + if ( ai->na_len > 1024 ) + ai->na_len = 1024; + avl_insert( &ni->ni_ai_tree, ai, ndb_name_cmp, avl_dup_error ); + } + return ai; +} + +static int +ndb_ai_check( struct ndb_info *ni, NdbOcInfo *oci, AttributeType **attrs, char **ptr, int *col, + int create ) +{ + NdbAttrInfo *ai; + int i; + + for ( i=0; attrs[i]; i++ ) { + if ( attrs[i] == slap_schema.si_ad_objectClass->ad_type ) + continue; + /* skip attrs that are in a superior */ + if ( oci->no_oc && oci->no_oc->soc_sups ) { + int j, k, found=0; + ObjectClass *oc; + for ( j=0; oci->no_oc->soc_sups[j]; j++ ) { + oc = oci->no_oc->soc_sups[j]; + if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) + continue; + if ( oc->soc_required ) { + for ( k=0; oc->soc_required[k]; k++ ) { + if ( attrs[i] == oc->soc_required[k] ) { + found = 1; + break; + } + } + if ( found ) break; + } + if ( oc->soc_allowed ) { + for ( k=0; oc->soc_allowed[k]; k++ ) { + if ( attrs[i] == oc->soc_allowed[k] ) { + found = 1; + break; + } + } + if ( found ) break; + } + } + if ( found ) + continue; + } + + ai = ndb_ai_get( ni, &attrs[i]->sat_cname ); + if ( !ai ) { + /* can never happen */ + return LDAP_OTHER; + } + + /* An attrset may have already been connected */ + if (( oci->no_flag & NDB_INFO_ATSET ) && ai->na_oi == oci ) + continue; + + /* An indexed attr is defined before its OC is */ + if ( !ai->na_oi ) { + ai->na_oi = oci; + ai->na_column = (*col)++; + } + + oci->no_attrs[oci->no_nattrs++] = ai; + + /* An attrset attr may already be defined */ + if ( ai->na_oi != oci ) { + int j; + for ( j=0; j<oci->no_nsets; j++ ) + if ( oci->no_sets[j] == ai->na_oi ) break; + if ( j >= oci->no_nsets ) { + /* FIXME: data loss if more sets are in use */ + if ( oci->no_nsets < NDB_MAX_OCSETS ) { + oci->no_sets[oci->no_nsets++] = ai->na_oi; + } + } + continue; + } + + if ( create ) { + if ( ai->na_flag & NDB_INFO_ATBLOB ) { + *ptr += sprintf( *ptr, ", `%s` BLOB", ai->na_attr->sat_cname.bv_val ); + } else { + *ptr += sprintf( *ptr, ", `%s` VARCHAR(%d)", ai->na_attr->sat_cname.bv_val, + ai->na_len ); + } + } + } + return 0; +} + +static int +ndb_oc_create( struct ndb_info *ni, NdbOcInfo *oci, int create ) +{ + char buf[4096], *ptr; + int i, rc = 0, col; + + if ( create ) { + ptr = buf + sprintf( buf, + "CREATE TABLE `%s` (eid bigint unsigned NOT NULL, vid int unsigned NOT NULL", + oci->no_table.bv_val ); + } + + col = 0; + if ( oci->no_oc->soc_required ) { + for ( i=0; oci->no_oc->soc_required[i]; i++ ); + col += i; + } + if ( oci->no_oc->soc_allowed ) { + for ( i=0; oci->no_oc->soc_allowed[i]; i++ ); + col += i; + } + /* assume all are present */ + oci->no_attrs = (struct ndb_attrinfo **)ch_malloc( col * sizeof(struct ndb_attrinfo *)); + + col = 2; + ldap_pvt_thread_rdwr_wlock( &ni->ni_ai_rwlock ); + if ( oci->no_oc->soc_required ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_required, &ptr, &col, create ); + } + if ( !rc && oci->no_oc->soc_allowed ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_allowed, &ptr, &col, create ); + } + ldap_pvt_thread_rdwr_wunlock( &ni->ni_ai_rwlock ); + + /* shrink down to just the needed size */ + oci->no_attrs = (struct ndb_attrinfo **)ch_realloc( oci->no_attrs, + oci->no_nattrs * sizeof(struct ndb_attrinfo *)); + + if ( create ) { + ptr = lutil_strcopy( ptr, ", PRIMARY KEY(eid, vid) ) ENGINE=ndb PARTITION BY KEY(eid)" ); + rc = mysql_real_query( &ni->ni_sql, buf, ptr - buf ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "ndb_oc_create: CREATE TABLE %s failed, %s (%d)\n", + oci->no_table.bv_val, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + } + } + return rc; +} + +/* Read table definitions from the DB and populate ObjectClassInfo */ +extern "C" int +ndb_oc_read( struct ndb_info *ni, const NdbDictionary::Dictionary *myDict ) +{ + const NdbDictionary::Table *myTable; + const NdbDictionary::Column *myCol; + NdbOcInfo *oci, octmp; + NdbAttrInfo *ai; + ObjectClass *oc; + NdbDictionary::Dictionary::List myList; + struct berval bv; + int i, j, rc, col; + + rc = myDict->listObjects( myList, NdbDictionary::Object::UserTable ); + /* Populate our objectClass structures */ + for ( i=0; i<myList.count; i++ ) { + /* Ignore other DBs */ + if ( strcmp( myList.elements[i].database, ni->ni_dbname )) + continue; + /* Ignore internal tables */ + if ( !strncmp( myList.elements[i].name, "OL_", 3 )) + continue; + ber_str2bv( myList.elements[i].name, 0, 0, &octmp.no_name ); + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) + continue; + + oc = oc_bvfind( &octmp.no_name ); + if ( !oc ) { + /* undefined - shouldn't happen */ + continue; + } + myTable = myDict->getTable( myList.elements[i].name ); + oci = (NdbOcInfo *)ch_malloc( sizeof( NdbOcInfo )+oc->soc_cname.bv_len+1 ); + oci->no_table.bv_val = (char *)(oci+1); + strcpy( oci->no_table.bv_val, oc->soc_cname.bv_val ); + oci->no_table.bv_len = oc->soc_cname.bv_len; + oci->no_name = oci->no_table; + oci->no_oc = oc; + oci->no_flag = 0; + oci->no_nsets = 0; + oci->no_nattrs = 0; + col = 0; + /* Make space for all attrs, even tho sups will be dropped */ + if ( oci->no_oc->soc_required ) { + for ( j=0; oci->no_oc->soc_required[j]; j++ ); + col = j; + } + if ( oci->no_oc->soc_allowed ) { + for ( j=0; oci->no_oc->soc_allowed[j]; j++ ); + col += j; + } + oci->no_attrs = (struct ndb_attrinfo **)ch_malloc( col * sizeof(struct ndb_attrinfo *)); + avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, avl_dup_error ); + + col = myTable->getNoOfColumns(); + /* Skip 0 and 1, eid and vid */ + for ( j = 2; j<col; j++ ) { + myCol = myTable->getColumn( j ); + ber_str2bv( myCol->getName(), 0, 0, &bv ); + ai = ndb_ai_get( ni, &bv ); + /* shouldn't happen */ + if ( !ai ) + continue; + ai->na_oi = oci; + ai->na_column = j; + ai->na_len = myCol->getLength(); + if ( myCol->getType() == NdbDictionary::Column::Blob ) + ai->na_flag |= NDB_INFO_ATBLOB; + } + } + /* Link to any attrsets */ + for ( i=0; i<myList.count; i++ ) { + /* Ignore other DBs */ + if ( strcmp( myList.elements[i].database, ni->ni_dbname )) + continue; + /* Ignore internal tables */ + if ( !strncmp( myList.elements[i].name, "OL_", 3 )) + continue; + ber_str2bv( myList.elements[i].name, 0, 0, &octmp.no_name ); + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + /* shouldn't happen */ + if ( !oci ) + continue; + col = 2; + if ( oci->no_oc->soc_required ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_required, NULL, &col, 0 ); + } + if ( oci->no_oc->soc_allowed ) { + rc = ndb_ai_check( ni, oci, oci->no_oc->soc_allowed, NULL, &col, 0 ); + } + /* shrink down to just the needed size */ + oci->no_attrs = (struct ndb_attrinfo **)ch_realloc( oci->no_attrs, + oci->no_nattrs * sizeof(struct ndb_attrinfo *)); + } + return 0; +} + +static int +ndb_oc_list( struct ndb_info *ni, const NdbDictionary::Dictionary *myDict, + struct berval *oname, int implied, NdbOcs *out ) +{ + const NdbDictionary::Table *myTable; + NdbOcInfo *oci, octmp; + ObjectClass *oc; + int i, rc; + + /* shortcut top */ + if ( ber_bvstrcasecmp( oname, &slap_schema.si_oc_top->soc_cname )) { + octmp.no_name = *oname; + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) { + oc = oci->no_oc; + } else { + oc = oc_bvfind( oname ); + if ( !oc ) { + /* undefined - shouldn't happen */ + return LDAP_INVALID_SYNTAX; + } + } + if ( oc->soc_sups ) { + int i; + + for ( i=0; oc->soc_sups[i]; i++ ) { + rc = ndb_oc_list( ni, myDict, &oc->soc_sups[i]->soc_cname, 1, out ); + if ( rc ) return rc; + } + } + } else { + oc = slap_schema.si_oc_top; + } + /* Only insert once */ + for ( i=0; i<out->no_ntext; i++ ) + if ( out->no_text[i].bv_val == oc->soc_cname.bv_val ) + break; + if ( i == out->no_ntext ) { + for ( i=0; i<out->no_nitext; i++ ) + if ( out->no_itext[i].bv_val == oc->soc_cname.bv_val ) + break; + if ( i == out->no_nitext ) { + if ( implied ) + out->no_itext[out->no_nitext++] = oc->soc_cname; + else + out->no_text[out->no_ntext++] = oc->soc_cname; + } + } + + /* ignore top, etc... */ + if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) + return 0; + + if ( !oci ) { + ldap_pvt_thread_rdwr_runlock( &ni->ni_oc_rwlock ); + oci = (NdbOcInfo *)ch_malloc( sizeof( NdbOcInfo )+oc->soc_cname.bv_len+1 ); + oci->no_table.bv_val = (char *)(oci+1); + strcpy( oci->no_table.bv_val, oc->soc_cname.bv_val ); + oci->no_table.bv_len = oc->soc_cname.bv_len; + oci->no_name = oci->no_table; + oci->no_oc = oc; + oci->no_flag = 0; + oci->no_nsets = 0; + oci->no_nattrs = 0; + ldap_pvt_thread_rdwr_wlock( &ni->ni_oc_rwlock ); + if ( avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, ndb_oc_dup_err )) { + octmp.no_oc = oci->no_oc; + ch_free( oci ); + oci = (NdbOcInfo *)octmp.no_oc; + } + /* see if the oc table already exists in the DB */ + myTable = myDict->getTable( oci->no_table.bv_val ); + rc = ndb_oc_create( ni, oci, myTable == NULL ); + ldap_pvt_thread_rdwr_wunlock( &ni->ni_oc_rwlock ); + ldap_pvt_thread_rdwr_rlock( &ni->ni_oc_rwlock ); + if ( rc ) return rc; + } + /* Only insert once */ + for ( i=0; i<out->no_ninfo; i++ ) + if ( out->no_info[i] == oci ) + break; + if ( i == out->no_ninfo ) + out->no_info[out->no_ninfo++] = oci; + return 0; +} + +extern "C" int +ndb_aset_get( struct ndb_info *ni, struct berval *sname, struct berval *attrs, NdbOcInfo **ret ) +{ + NdbOcInfo *oci, octmp; + int i, rc; + + octmp.no_name = *sname; + oci = (NdbOcInfo *)avl_find( ni->ni_oc_tree, &octmp, ndb_name_cmp ); + if ( oci ) + return LDAP_ALREADY_EXISTS; + + for ( i=0; !BER_BVISNULL( &attrs[i] ); i++ ) { + if ( !at_bvfind( &attrs[i] )) + return LDAP_NO_SUCH_ATTRIBUTE; + } + i++; + + oci = (NdbOcInfo *)ch_calloc( 1, sizeof( NdbOcInfo ) + sizeof( ObjectClass ) + + i*sizeof(AttributeType *) + sname->bv_len+1 ); + oci->no_oc = (ObjectClass *)(oci+1); + oci->no_oc->soc_required = (AttributeType **)(oci->no_oc+1); + oci->no_table.bv_val = (char *)(oci->no_oc->soc_required+i); + + for ( i=0; !BER_BVISNULL( &attrs[i] ); i++ ) + oci->no_oc->soc_required[i] = at_bvfind( &attrs[i] ); + + strcpy( oci->no_table.bv_val, sname->bv_val ); + oci->no_table.bv_len = sname->bv_len; + oci->no_name = oci->no_table; + oci->no_oc->soc_cname = oci->no_name; + oci->no_flag = NDB_INFO_ATSET; + + if ( !ber_bvcmp( sname, &slap_schema.si_oc_extensibleObject->soc_cname )) + oci->no_oc->soc_kind = slap_schema.si_oc_extensibleObject->soc_kind; + + rc = ndb_oc_create( ni, oci, 0 ); + if ( !rc ) + rc = avl_insert( &ni->ni_oc_tree, oci, ndb_name_cmp, avl_dup_error ); + if ( rc ) { + ch_free( oci ); + } else { + *ret = oci; + } + return rc; +} + +extern "C" int +ndb_aset_create( struct ndb_info *ni, NdbOcInfo *oci ) +{ + char buf[4096], *ptr; + NdbAttrInfo *ai; + int i; + + ptr = buf + sprintf( buf, + "CREATE TABLE IF NOT EXISTS `%s` (eid bigint unsigned NOT NULL, vid int unsigned NOT NULL", + oci->no_table.bv_val ); + + for ( i=0; i<oci->no_nattrs; i++ ) { + if ( oci->no_attrs[i]->na_oi != oci ) + continue; + ai = oci->no_attrs[i]; + ptr += sprintf( ptr, ", `%s` VARCHAR(%d)", ai->na_attr->sat_cname.bv_val, + ai->na_len ); + if ( ai->na_flag & NDB_INFO_INDEX ) { + ptr += sprintf( ptr, ", INDEX (`%s`)", ai->na_attr->sat_cname.bv_val ); + } + } + ptr = lutil_strcopy( ptr, ", PRIMARY KEY(eid, vid) ) ENGINE=ndb PARTITION BY KEY(eid)" ); + i = mysql_real_query( &ni->ni_sql, buf, ptr - buf ); + if ( i ) { + Debug( LDAP_DEBUG_ANY, + "ndb_aset_create: CREATE TABLE %s failed, %s (%d)\n", + oci->no_table.bv_val, mysql_error(&ni->ni_sql), mysql_errno(&ni->ni_sql) ); + } + return i; +} + +static int +ndb_oc_check( BackendDB *be, Ndb *ndb, + struct berval *ocsin, NdbOcs *out ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + + int i, rc = 0; + + out->no_ninfo = 0; + out->no_ntext = 0; + out->no_nitext = 0; + + /* Find all objectclasses and their superiors. List + * the superiors first. + */ + + ldap_pvt_thread_rdwr_rlock( &ni->ni_oc_rwlock ); + for ( i=0; !BER_BVISNULL( &ocsin[i] ); i++ ) { + rc = ndb_oc_list( ni, myDict, &ocsin[i], 0, out ); + if ( rc ) break; + } + ldap_pvt_thread_rdwr_runlock( &ni->ni_oc_rwlock ); + return rc; +} + +#define V_INS 1 +#define V_DEL 2 +#define V_REP 3 + +static int ndb_flush_blobs; + +/* set all the unique attrs of this objectclass into the table + */ +extern "C" int +ndb_oc_attrs( + NdbTransaction *txn, + const NdbDictionary::Table *myTable, + Entry *e, + NdbOcInfo *no, + NdbAttrInfo **attrs, + int nattrs, + Attribute *old +) +{ + char buf[65538], *ptr; + Attribute **an, **ao, *a; + NdbOperation *myop; + int i, j, max = 0; + int changed, rc; + Uint64 eid = e->e_id; + + if ( !nattrs ) + return 0; + + an = (Attribute **)ch_malloc( 2 * nattrs * sizeof(Attribute *)); + ao = an + nattrs; + + /* Turn lists of attrs into arrays for easier access */ + for ( i=0; i<nattrs; i++ ) { + if ( attrs[i]->na_oi != no ) { + an[i] = NULL; + ao[i] = NULL; + continue; + } + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_objectClass ) + continue; + if ( a->a_desc->ad_type == attrs[i]->na_attr ) { + /* Don't process same attr twice */ + if ( a->a_flags & SLAP_ATTR_IXADD ) + a = NULL; + else + a->a_flags |= SLAP_ATTR_IXADD; + break; + } + } + an[i] = a; + if ( a && a->a_numvals > max ) + max = a->a_numvals; + for ( a=old; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_objectClass ) + continue; + if ( a->a_desc->ad_type == attrs[i]->na_attr ) + break; + } + ao[i] = a; + if ( a && a->a_numvals > max ) + max = a->a_numvals; + } + + for ( i=0; i<max; i++ ) { + myop = NULL; + for ( j=0; j<nattrs; j++ ) { + if ( !an[j] && !ao[j] ) + continue; + changed = 0; + if ( an[j] && an[j]->a_numvals > i ) { + /* both old and new are present, compare for changes */ + if ( ao[j] && ao[j]->a_numvals > i ) { + if ( ber_bvcmp( &ao[j]->a_nvals[i], &an[j]->a_nvals[i] )) + changed = V_REP; + } else { + changed = V_INS; + } + } else { + if ( ao[j] && ao[j]->a_numvals > i ) + changed = V_DEL; + } + if ( changed ) { + if ( !myop ) { + rc = LDAP_OTHER; + myop = txn->getNdbOperation( myTable ); + if ( !myop ) { + goto done; + } + if ( old ) { + if ( myop->writeTuple()) { + goto done; + } + } else { + if ( myop->insertTuple()) { + goto done; + } + } + if ( myop->equal( EID_COLUMN, eid )) { + goto done; + } + if ( myop->equal( VID_COLUMN, i )) { + goto done; + } + } + if ( attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + NdbBlob *myBlob = myop->getBlobHandle( attrs[j]->na_column ); + rc = LDAP_OTHER; + if ( !myBlob ) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: getBlobHandle failed %s (%d)\n", + myop->getNdbError().message, myop->getNdbError().code, 0 ); + goto done; + } + if ( slapMode & SLAP_TOOL_MODE ) + ndb_flush_blobs = 1; + if ( changed & V_INS ) { + if ( myBlob->setValue( an[j]->a_vals[i].bv_val, an[j]->a_vals[i].bv_len )) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: blob->setValue failed %s (%d)\n", + myBlob->getNdbError().message, myBlob->getNdbError().code, 0 ); + goto done; + } + } else { + if ( myBlob->setValue( NULL, 0 )) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: blob->setValue failed %s (%d)\n", + myBlob->getNdbError().message, myBlob->getNdbError().code, 0 ); + goto done; + } + } + } else { + if ( changed & V_INS ) { + if ( an[j]->a_vals[i].bv_len > attrs[j]->na_len ) { + Debug( LDAP_DEBUG_ANY, "ndb_oc_attrs: attribute %s too long for column\n", + attrs[j]->na_name.bv_val, 0, 0 ); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + ptr = buf; + *ptr++ = an[j]->a_vals[i].bv_len & 0xff; + if ( attrs[j]->na_len > 255 ) { + /* MedVar */ + *ptr++ = an[j]->a_vals[i].bv_len >> 8; + } + memcpy( ptr, an[j]->a_vals[i].bv_val, an[j]->a_vals[i].bv_len ); + ptr = buf; + } else { + ptr = NULL; + } + if ( myop->setValue( attrs[j]->na_column, ptr )) { + rc = LDAP_OTHER; + goto done; + } + } + } + } + } + rc = LDAP_SUCCESS; +done: + ch_free( an ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "ndb_oc_attrs: failed %s (%d)\n", + myop->getNdbError().message, myop->getNdbError().code, 0 ); + } + return rc; +} + +static int +ndb_oc_put( + const NdbDictionary::Dictionary *myDict, + NdbTransaction *txn, NdbOcInfo *no, Entry *e ) +{ + const NdbDictionary::Table *myTable; + int i, rc; + + for ( i=0; i<no->no_nsets; i++ ) { + rc = ndb_oc_put( myDict, txn, no->no_sets[i], e ); + if ( rc ) + return rc; + } + + myTable = myDict->getTable( no->no_table.bv_val ); + if ( !myTable ) + return LDAP_OTHER; + + return ndb_oc_attrs( txn, myTable, e, no, no->no_attrs, no->no_nattrs, NULL ); +} + +/* This is now only used for Adds. Modifies call ndb_oc_attrs directly. */ +extern "C" int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + Attribute *aoc; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + NdbOcs myOcs; + int i, rc; + + /* Get the entry's objectClass attribute */ + aoc = attr_find( NA->e->e_attrs, slap_schema.si_ad_objectClass ); + if ( !aoc ) + return LDAP_OTHER; + + ndb_oc_check( be, NA->ndb, aoc->a_nvals, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + + /* Walk thru objectclasses, find all the attributes belonging to a class */ + for ( i=0; i<myOcs.no_ninfo; i++ ) { + rc = ndb_oc_put( myDict, NA->txn, myOcs.no_info[i], NA->e ); + if ( rc ) return rc; + } + + /* slapadd tries to batch multiple entries per txn, but entry data is + * transient and blob data is required to remain valid for the whole txn. + * So we need to flush blobs before their source data disappears. + */ + if (( slapMode & SLAP_TOOL_MODE ) && ndb_flush_blobs ) + NA->txn->execute( NdbTransaction::NoCommit ); + + return 0; +} + +static void +ndb_oc_get( Operation *op, NdbOcInfo *no, int *j, int *nocs, NdbOcInfo ***oclist ) +{ + int i; + NdbOcInfo **ol2; + + for ( i=0; i<no->no_nsets; i++ ) { + ndb_oc_get( op, no->no_sets[i], j, nocs, oclist ); + } + + /* Don't insert twice */ + ol2 = *oclist; + for ( i=0; i<*j; i++ ) + if ( ol2[i] == no ) + return; + + if ( *j >= *nocs ) { + *nocs *= 2; + ol2 = (NdbOcInfo **)op->o_tmprealloc( *oclist, *nocs * sizeof(NdbOcInfo *), op->o_tmpmemctx ); + *oclist = ol2; + } + ol2 = *oclist; + ol2[(*j)++] = no; +} + +/* Retrieve attribute data for given entry. The entry's DN and eid should + * already be populated. + */ +extern "C" int +ndb_entry_get_data( + Operation *op, + NdbArgs *NA, + int update +) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable; + NdbIndexScanOperation **myop = NULL; + Uint64 eid; + + Attribute *a; + NdbOcs myOcs; + NdbOcInfo *oci, **oclist = NULL; + char abuf[65536], *ptr, **attrs = NULL; + struct berval bv[2]; + int *ocx = NULL; + + /* FIXME: abuf should be dynamically allocated */ + + int i, j, k, nocs, nattrs, rc = LDAP_OTHER; + + eid = NA->e->e_id; + + ndb_oc_check( op->o_bd, NA->ndb, NA->ocs, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + nocs = myOcs.no_ninfo; + + oclist = (NdbOcInfo **)op->o_tmpcalloc( 1, nocs * sizeof(NdbOcInfo *), op->o_tmpmemctx ); + + for ( i=0, j=0; i<myOcs.no_ninfo; i++ ) { + ndb_oc_get( op, myOcs.no_info[i], &j, &nocs, &oclist ); + } + + nocs = j; + nattrs = 0; + for ( i=0; i<nocs; i++ ) + nattrs += oclist[i]->no_nattrs; + + ocx = (int *)op->o_tmpalloc( nocs * sizeof(int), op->o_tmpmemctx ); + + attrs = (char **)op->o_tmpalloc( nattrs * sizeof(char *), op->o_tmpmemctx ); + + myop = (NdbIndexScanOperation **)op->o_tmpalloc( nattrs * sizeof(NdbIndexScanOperation *), op->o_tmpmemctx ); + + k = 0; + ptr = abuf; + for ( i=0; i<nocs; i++ ) { + oci = oclist[i]; + + myop[i] = NA->txn->getNdbIndexScanOperation( "PRIMARY", oci->no_table.bv_val ); + if ( !myop[i] ) + goto leave; + if ( myop[i]->readTuples( update ? NdbOperation::LM_Exclusive : NdbOperation::LM_CommittedRead )) + goto leave; + if ( myop[i]->setBound( 0U, NdbIndexScanOperation::BoundEQ, &eid )) + goto leave; + + for ( j=0; j<oci->no_nattrs; j++ ) { + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + NdbBlob *bi = myop[i]->getBlobHandle( oci->no_attrs[j]->na_column ); + attrs[k++] = (char *)bi; + } else { + attrs[k] = ptr; + *ptr++ = 0; + if ( oci->no_attrs[j]->na_len > 255 ) + *ptr++ = 0; + ptr += oci->no_attrs[j]->na_len + 1; + myop[i]->getValue( oci->no_attrs[j]->na_column, attrs[k++] ); + } + } + ocx[i] = k; + } + /* Must use IgnoreError, because an entry with multiple objectClasses may not + * actually have attributes defined in each class / table. + */ + if ( NA->txn->execute( NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) + goto leave; + + /* count results */ + for ( i=0; i<nocs; i++ ) { + if (( j = myop[i]->nextResult(true) )) { + if ( j < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: first nextResult(%d) failed: %s (%d)\n", + i, myop[i]->getNdbError().message, myop[i]->getNdbError().code ); + } + myop[i] = NULL; + } + } + + nattrs = 0; + k = 0; + for ( i=0; i<nocs; i++ ) { + oci = oclist[i]; + for ( j=0; j<oci->no_nattrs; j++ ) { + unsigned char *buf; + int len; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + if ( !myop[i] ) { + attrs[k] = NULL; + } else if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + void *vi = attrs[k]; + NdbBlob *bi = (NdbBlob *)vi; + int isNull; + bi->getNull( isNull ); + if ( !isNull ) { + nattrs++; + } else { + attrs[k] = NULL; + } + } else { + buf = (unsigned char *)attrs[k]; + len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + len |= (buf[1] << 8); + } + if ( len ) { + nattrs++; + } else { + attrs[k] = NULL; + } + } + k++; + } + } + + a = attrs_alloc( nattrs+1 ); + NA->e->e_attrs = a; + + a->a_desc = slap_schema.si_ad_objectClass; + a->a_vals = NULL; + ber_bvarray_dup_x( &a->a_vals, NA->ocs, NULL ); + a->a_nvals = a->a_vals; + a->a_numvals = myOcs.no_ntext; + + BER_BVZERO( &bv[1] ); + + do { + a = NA->e->e_attrs->a_next; + k = 0; + for ( i=0; i<nocs; k=ocx[i], i++ ) { + oci = oclist[i]; + for ( j=0; j<oci->no_nattrs; j++ ) { + unsigned char *buf; + struct berval nbv; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + buf = (unsigned char *)attrs[k++]; + if ( !buf ) + continue; + if ( !myop[i] ) { + a=a->a_next; + continue; + } + if ( oci->no_attrs[j]->na_flag & NDB_INFO_ATBLOB ) { + void *vi = (void *)buf; + NdbBlob *bi = (NdbBlob *)vi; + Uint64 len; + Uint32 len2; + int isNull; + bi->getNull( isNull ); + if ( isNull ) { + a = a->a_next; + continue; + } + bi->getLength( len ); + bv[0].bv_len = len; + bv[0].bv_val = (char *)ch_malloc( len+1 ); + len2 = len; + if ( bi->readData( bv[0].bv_val, len2 )) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: blob readData failed: %s (%d), len %d\n", + bi->getNdbError().message, bi->getNdbError().code, len2 ); + } + bv[0].bv_val[len] = '\0'; + ber_bvarray_add_x( &a->a_vals, bv, NULL ); + } else { + bv[0].bv_len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + bv[0].bv_len |= (buf[1] << 8); + bv[0].bv_val = (char *)buf+2; + buf[1] = 0; + } else { + bv[0].bv_val = (char *)buf+1; + } + buf[0] = 0; + if ( bv[0].bv_len == 0 ) { + a = a->a_next; + continue; + } + bv[0].bv_val[bv[0].bv_len] = '\0'; + value_add_one( &a->a_vals, bv ); + } + a->a_desc = oci->no_attrs[j]->na_desc; + attr_normalize_one( a->a_desc, bv, &nbv, NULL ); + a->a_numvals++; + if ( !BER_BVISNULL( &nbv )) { + ber_bvarray_add_x( &a->a_nvals, &nbv, NULL ); + } else if ( !a->a_nvals ) { + a->a_nvals = a->a_vals; + } + a = a->a_next; + } + } + k = 0; + for ( i=0; i<nocs; i++ ) { + if ( !myop[i] ) + continue; + if ((j = myop[i]->nextResult(true))) { + if ( j < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "ndb_entry_get_data: last nextResult(%d) failed: %s (%d)\n", + i, myop[i]->getNdbError().message, myop[i]->getNdbError().code ); + } + myop[i] = NULL; + } else { + k = 1; + } + } + } while ( k ); + + rc = 0; +leave: + if ( myop ) { + op->o_tmpfree( myop, op->o_tmpmemctx ); + } + if ( attrs ) { + op->o_tmpfree( attrs, op->o_tmpmemctx ); + } + if ( ocx ) { + op->o_tmpfree( ocx, op->o_tmpmemctx ); + } + if ( oclist ) { + op->o_tmpfree( oclist, op->o_tmpmemctx ); + } + + return rc; +} + +static int +ndb_oc_del( + NdbTransaction *txn, Uint64 eid, NdbOcInfo *no ) +{ + NdbIndexScanOperation *myop; + int i, rc; + + for ( i=0; i<no->no_nsets; i++ ) { + rc = ndb_oc_del( txn, eid, no->no_sets[i] ); + if ( rc ) return rc; + } + + myop = txn->getNdbIndexScanOperation( "PRIMARY", no->no_table.bv_val ); + if ( !myop ) + return LDAP_OTHER; + if ( myop->readTuples( NdbOperation::LM_Exclusive )) + return LDAP_OTHER; + if ( myop->setBound( 0U, NdbIndexScanOperation::BoundEQ, &eid )) + return LDAP_OTHER; + + txn->execute(NoCommit); + while ( myop->nextResult(true) == 0) { + do { + myop->deleteCurrentTuple(); + } while (myop->nextResult(false) == 0); + txn->execute(NoCommit); + } + + return 0; +} + +extern "C" int +ndb_entry_del_data( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + Uint64 eid = NA->e->e_id; + int i; + NdbOcs myOcs; + + ndb_oc_check( be, NA->ndb, NA->ocs, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + + for ( i=0; i<myOcs.no_ninfo; i++ ) { + if ( ndb_oc_del( NA->txn, eid, myOcs.no_info[i] )) + return LDAP_OTHER; + } + + return 0; +} + +extern "C" int +ndb_dn2rdns( + struct berval *dn, + NdbRdns *rdns +) +{ + char *beg, *end; + int i, len; + + /* Walk thru RDNs */ + end = dn->bv_val + dn->bv_len; + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + for ( beg = end-1; beg > dn->bv_val; beg-- ) { + if (*beg == ',') { + beg++; + break; + } + } + if ( beg >= dn->bv_val ) { + len = end - beg; + /* RDN is too long */ + if ( len > NDB_RDN_LEN ) + return LDAP_CONSTRAINT_VIOLATION; + memcpy( rdns->nr_buf[i]+1, beg, len ); + } else { + break; + } + rdns->nr_buf[i][0] = len; + end = beg - 1; + } + /* Too many RDNs in DN */ + if ( i == NDB_MAX_RDNS && beg > dn->bv_val ) { + return LDAP_CONSTRAINT_VIOLATION; + } + rdns->nr_num = i; + return 0; +} + +static int +ndb_rdns2keys( + NdbOperation *myop, + NdbRdns *rdns +) +{ + int i; + char dummy[2] = {0,0}; + + /* Walk thru RDNs */ + for ( i=0; i<rdns->nr_num; i++ ) { + if ( myop->equal( i+RDN_COLUMN, rdns->nr_buf[i] )) + return LDAP_OTHER; + } + for ( ; i<NDB_MAX_RDNS; i++ ) { + if ( myop->equal( i+RDN_COLUMN, dummy )) + return LDAP_OTHER; + } + return 0; +} + +/* Store the DN2ID_TABLE fields */ +extern "C" int +ndb_entry_put_info( + BackendDB *be, + NdbArgs *NA, + int update +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop; + NdbAttrInfo *ai; + Attribute *aoc, *a; + + /* Get the entry's objectClass attribute; it's ok to be + * absent on a fresh insert + */ + aoc = attr_find( NA->e->e_attrs, slap_schema.si_ad_objectClass ); + if ( update && !aoc ) + return LDAP_OBJECT_CLASS_VIOLATION; + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( update ) { + if ( myop->updateTuple()) + return LDAP_OTHER; + } else { + if ( myop->insertTuple()) + return LDAP_OTHER; + } + + if ( ndb_rdns2keys( myop, NA->rdns )) + return LDAP_OTHER; + + /* Set entry ID */ + { + Uint64 eid = NA->e->e_id; + if ( myop->setValue( EID_COLUMN, eid )) + return LDAP_OTHER; + } + + /* Set list of objectClasses */ + /* List is <sp> <class> <sp> <class> <sp> ... so that + * searches for " class " will yield accurate results + */ + if ( aoc ) { + char *ptr, buf[sizeof(MedVar)]; + NdbOcs myOcs; + int i; + + ndb_oc_check( be, NA->ndb, aoc->a_nvals, &myOcs ); + ptr = buf+2; + *ptr++ = ' '; + for ( i=0; i<myOcs.no_ntext; i++ ) { + /* data loss... */ + if ( ptr + myOcs.no_text[i].bv_len + 1 >= &buf[sizeof(buf)] ) + break; + ptr = lutil_strcopy( ptr, myOcs.no_text[i].bv_val ); + *ptr++ = ' '; + } + + /* implicit classes */ + if ( myOcs.no_nitext ) { + *ptr++ = '@'; + *ptr++ = ' '; + for ( i=0; i<myOcs.no_nitext; i++ ) { + /* data loss... */ + if ( ptr + myOcs.no_itext[i].bv_len + 1 >= &buf[sizeof(buf)] ) + break; + ptr = lutil_strcopy( ptr, myOcs.no_itext[i].bv_val ); + *ptr++ = ' '; + } + } + + i = ptr - buf - 2; + buf[0] = i & 0xff; + buf[1] = i >> 8; + if ( myop->setValue( OCS_COLUMN, buf )) + return LDAP_OTHER; + } + + /* Set any indexed attrs */ + for ( a = NA->e->e_attrs; a; a=a->a_next ) { + ai = ndb_ai_find( ni, a->a_desc->ad_type ); + if ( ai && ( ai->na_flag & NDB_INFO_INDEX )) { + char *ptr, buf[sizeof(MedVar)]; + int len; + + ptr = buf+1; + len = a->a_vals[0].bv_len; + /* FIXME: data loss */ + if ( len > ai->na_len ) + len = ai->na_len; + buf[0] = len & 0xff; + if ( ai->na_len > 255 ) { + *ptr++ = len >> 8; + } + memcpy( ptr, a->a_vals[0].bv_val, len ); + if ( myop->setValue( ai->na_ixcol, buf )) + return LDAP_OTHER; + } + } + + return 0; +} + +extern "C" struct berval * +ndb_str2bvarray( + char *str, + int len, + char delim, + void *ctx +) +{ + struct berval *list, tmp; + char *beg; + int i, num; + + while ( *str == delim ) { + str++; + len--; + } + + while ( str[len-1] == delim ) { + str[--len] = '\0'; + } + + for ( i = 1, beg = str;; i++ ) { + beg = strchr( beg, delim ); + if ( !beg ) + break; + if ( beg >= str + len ) + break; + beg++; + } + + num = i; + list = (struct berval *)slap_sl_malloc( (num+1)*sizeof(struct berval), ctx); + + for ( i = 0, beg = str; i<num; i++ ) { + tmp.bv_val = beg; + beg = strchr( beg, delim ); + if ( beg >= str + len ) + beg = NULL; + if ( beg ) { + tmp.bv_len = beg - tmp.bv_val; + } else { + tmp.bv_len = len - (tmp.bv_val - str); + } + ber_dupbv_x( &list[i], &tmp, ctx ); + beg++; + } + + BER_BVZERO( &list[i] ); + return list; +} + +extern "C" struct berval * +ndb_ref2oclist( + const char *ref, + void *ctx +) +{ + char *implied; + + /* MedVar */ + int len = ref[0] | (ref[1] << 8); + + /* don't return the implied classes */ + implied = (char *)memchr( ref+2, '@', len ); + if ( implied ) { + len = implied - ref - 2; + *implied = '\0'; + } + + return ndb_str2bvarray( (char *)ref+2, len, ' ', ctx ); +} + +/* Retrieve the DN2ID_TABLE fields. Can call with NULL ocs if just verifying + * the existence of a DN. + */ +extern "C" int +ndb_entry_get_info( + Operation *op, + NdbArgs *NA, + int update, + struct berval *matched +) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop[NDB_MAX_RDNS]; + NdbRecAttr *eid[NDB_MAX_RDNS], *oc[NDB_MAX_RDNS]; + char idbuf[NDB_MAX_RDNS][2*sizeof(ID)]; + char ocbuf[NDB_MAX_RDNS][NDB_OC_BUFLEN]; + + if ( matched ) { + BER_BVZERO( matched ); + } + if ( !myTable ) { + return LDAP_OTHER; + } + + myop[0] = NA->txn->getNdbOperation( myTable ); + if ( !myop[0] ) { + return LDAP_OTHER; + } + + if ( myop[0]->readTuple( update ? NdbOperation::LM_Exclusive : NdbOperation::LM_CommittedRead )) { + return LDAP_OTHER; + } + + if ( !NA->rdns->nr_num && ndb_dn2rdns( &NA->e->e_name, NA->rdns )) { + return LDAP_NO_SUCH_OBJECT; + } + + if ( ndb_rdns2keys( myop[0], NA->rdns )) { + return LDAP_OTHER; + } + + eid[0] = myop[0]->getValue( EID_COLUMN, idbuf[0] ); + if ( !eid[0] ) { + return LDAP_OTHER; + } + + ocbuf[0][0] = 0; + ocbuf[0][1] = 0; + if ( !NA->ocs ) { + oc[0] = myop[0]->getValue( OCS_COLUMN, ocbuf[0] ); + if ( !oc[0] ) { + return LDAP_OTHER; + } + } + + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + + switch( myop[0]->getNdbError().code ) { + case 0: + if ( !eid[0]->isNULL() && ( NA->e->e_id = eid[0]->u_64_value() )) { + /* If we didn't care about OCs, or we got them */ + if ( NA->ocs || ocbuf[0][0] || ocbuf[0][1] ) { + /* If wanted, return them */ + if ( !NA->ocs ) + NA->ocs = ndb_ref2oclist( ocbuf[0], op->o_tmpmemctx ); + break; + } + } + /* FALLTHRU */ + case NDB_NO_SUCH_OBJECT: /* no such tuple: look for closest parent */ + if ( matched ) { + int i, j, k; + char dummy[2] = {0,0}; + + /* get to last RDN, then back up 1 */ + k = NA->rdns->nr_num - 1; + + for ( i=0; i<k; i++ ) { + myop[i] = NA->txn->getNdbOperation( myTable ); + if ( !myop[i] ) + return LDAP_OTHER; + if ( myop[i]->readTuple( NdbOperation::LM_CommittedRead )) + return LDAP_OTHER; + for ( j=0; j<=i; j++ ) { + if ( myop[i]->equal( j+RDN_COLUMN, NA->rdns->nr_buf[j] )) + return LDAP_OTHER; + } + for ( ;j<NDB_MAX_RDNS; j++ ) { + if ( myop[i]->equal( j+RDN_COLUMN, dummy )) + return LDAP_OTHER; + } + eid[i] = myop[i]->getValue( EID_COLUMN, idbuf[i] ); + if ( !eid[i] ) { + return LDAP_OTHER; + } + ocbuf[i][0] = 0; + ocbuf[i][1] = 0; + if ( !NA->ocs ) { + oc[i] = myop[0]->getValue( OCS_COLUMN, ocbuf[i] ); + if ( !oc[i] ) { + return LDAP_OTHER; + } + } + } + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + for ( --i; i>=0; i-- ) { + if ( myop[i]->getNdbError().code == 0 ) { + for ( j=0; j<=i; j++ ) + matched->bv_len += NA->rdns->nr_buf[j][0]; + NA->erdns = NA->rdns->nr_num; + NA->rdns->nr_num = j; + matched->bv_len += i; + matched->bv_val = NA->e->e_name.bv_val + + NA->e->e_name.bv_len - matched->bv_len; + if ( !eid[i]->isNULL() ) + NA->e->e_id = eid[i]->u_64_value(); + if ( !NA->ocs ) + NA->ocs = ndb_ref2oclist( ocbuf[i], op->o_tmpmemctx ); + break; + } + } + } + return LDAP_NO_SUCH_OBJECT; + default: + return LDAP_OTHER; + } + + return 0; +} + +extern "C" int +ndb_entry_del_info( + BackendDB *be, + NdbArgs *NA +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop; + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( myop->deleteTuple()) + return LDAP_OTHER; + + if ( ndb_rdns2keys( myop, NA->rdns )) + return LDAP_OTHER; + + return 0; +} + +extern "C" int +ndb_next_id( + BackendDB *be, + Ndb *ndb, + ID *id +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( NEXTID_TABLE ); + Uint64 nid = 0; + int rc; + + if ( !myTable ) { + Debug( LDAP_DEBUG_ANY, "ndb_next_id: " NEXTID_TABLE " table is missing\n", + 0, 0, 0 ); + return LDAP_OTHER; + } + + rc = ndb->getAutoIncrementValue( myTable, nid, 1000 ); + if ( !rc ) + *id = nid; + return rc; +} + +extern "C" { static void ndb_thread_hfree( void *key, void *data ); }; +static void +ndb_thread_hfree( void *key, void *data ) +{ + Ndb *ndb = (Ndb *)data; + delete ndb; +} + +extern "C" int +ndb_thread_handle( + Operation *op, + Ndb **ndb ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + void *data; + + if ( ldap_pvt_thread_pool_getkey( op->o_threadctx, ni, &data, NULL )) { + Ndb *myNdb; + int rc; + ldap_pvt_thread_mutex_lock( &ni->ni_conn_mutex ); + myNdb = new Ndb( ni->ni_cluster[ni->ni_nextconn++], ni->ni_dbname ); + if ( ni->ni_nextconn >= ni->ni_nconns ) + ni->ni_nextconn = 0; + ldap_pvt_thread_mutex_unlock( &ni->ni_conn_mutex ); + if ( !myNdb ) { + return LDAP_OTHER; + } + rc = myNdb->init(1024); + if ( rc ) { + delete myNdb; + Debug( LDAP_DEBUG_ANY, "ndb_thread_handle: err %d\n", + rc, 0, 0 ); + return rc; + } + data = (void *)myNdb; + if (( rc = ldap_pvt_thread_pool_setkey( op->o_threadctx, ni, + data, ndb_thread_hfree, NULL, NULL ))) { + delete myNdb; + Debug( LDAP_DEBUG_ANY, "ndb_thread_handle: err %d\n", + rc, 0, 0 ); + return rc; + } + } + *ndb = (Ndb *)data; + return 0; +} + +extern "C" int +ndb_entry_get( + Operation *op, + struct berval *ndn, + ObjectClass *oc, + AttributeDescription *ad, + int rw, + Entry **ent ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + NdbArgs NA; + Entry e = {0}; + int rc; + + /* Get our NDB handle */ + rc = ndb_thread_handle( op, &NA.ndb ); + + NA.txn = NA.ndb->startTransaction(); + if( !NA.txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_entry_get) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + return 1; + } + + e.e_name = *ndn; + NA.e = &e; + /* get entry */ + { + NdbRdns rdns; + rdns.nr_num = 0; + NA.ocs = NULL; + NA.rdns = &rdns; + rc = ndb_entry_get_info( op, &NA, rw, NULL ); + } + if ( rc == 0 ) { + e.e_name = *ndn; + e.e_nname = *ndn; + rc = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free( NA.ocs ); + if ( rc == 0 ) { + if ( oc && !is_entry_objectclass_or_sub( &e, oc )) { + attrs_free( e.e_attrs ); + rc = 1; + } + } + } + if ( rc == 0 ) { + *ent = entry_alloc(); + **ent = e; + ber_dupbv( &(*ent)->e_name, ndn ); + ber_dupbv( &(*ent)->e_nname, ndn ); + } else { + rc = 1; + } + NA.txn->close(); + return rc; +} + +/* Congestion avoidance code + * for Deadlock Rollback + */ + +extern "C" void +ndb_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 ); +} + +extern "C" void +ndb_check_referral( Operation *op, SlapReply *rs, NdbArgs *NA ) +{ + struct berval dn, ndn; + int i, dif; + dif = NA->erdns - NA->rdns->nr_num; + + /* Set full DN of matched into entry */ + for ( i=0; i<dif; i++ ) { + dnParent( &NA->e->e_name, &dn ); + dnParent( &NA->e->e_nname, &ndn ); + NA->e->e_name = dn; + NA->e->e_nname = ndn; + } + + /* return referral only if "disclose" is granted on the object */ + if ( access_allowed( op, NA->e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL )) { + Attribute a; + for ( i=0; !BER_BVISNULL( &NA->ocs[i] ); i++ ); + a.a_numvals = i; + a.a_desc = slap_schema.si_ad_objectClass; + a.a_vals = NA->ocs; + a.a_nvals = NA->ocs; + a.a_next = NULL; + NA->e->e_attrs = &a; + if ( is_entry_referral( NA->e )) { + NA->e->e_attrs = NULL; + ndb_entry_get_data( op, NA, 0 ); + rs->sr_ref = get_entry_referrals( op, NA->e ); + if ( rs->sr_ref ) { + rs->sr_err = LDAP_REFERRAL; + rs->sr_flags |= REP_REF_MUSTBEFREED; + } + attrs_free( NA->e->e_attrs ); + } + NA->e->e_attrs = NULL; + } +} diff --git a/servers/slapd/back-ndb/proto-ndb.h b/servers/slapd/back-ndb/proto-ndb.h new file mode 100644 index 0000000..6a6e092 --- /dev/null +++ b/servers/slapd/back-ndb/proto-ndb.h @@ -0,0 +1,166 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#ifndef _PROTO_NDB_H +#define _PROTO_NDB_H + +LDAP_BEGIN_DECL + +extern BI_init ndb_back_initialize; + +extern BI_open ndb_back_open; +extern BI_close ndb_back_close; +extern BI_destroy ndb_back_destroy; + +extern BI_db_init ndb_back_db_init; +extern BI_db_destroy ndb_back_db_destroy; + +extern BI_op_bind ndb_back_bind; +extern BI_op_unbind ndb_back_unbind; +extern BI_op_search ndb_back_search; +extern BI_op_compare ndb_back_compare; +extern BI_op_modify ndb_back_modify; +extern BI_op_modrdn ndb_back_modrdn; +extern BI_op_add ndb_back_add; +extern BI_op_delete ndb_back_delete; + +extern BI_operational ndb_operational; +extern BI_has_subordinates ndb_has_subordinates; +extern BI_entry_get_rw ndb_entry_get; + +extern BI_tool_entry_open ndb_tool_entry_open; +extern BI_tool_entry_close ndb_tool_entry_close; +extern BI_tool_entry_first ndb_tool_entry_first; +extern BI_tool_entry_next ndb_tool_entry_next; +extern BI_tool_entry_get ndb_tool_entry_get; +extern BI_tool_entry_put ndb_tool_entry_put; +extern BI_tool_dn2id_get ndb_tool_dn2id_get; + +extern int ndb_modify_internal( + Operation *op, + NdbArgs *NA, + const char **text, + char *textbuf, + size_t textlen ); + +extern int +ndb_entry_get_data( + Operation *op, + NdbArgs *args, + int update ); + +extern int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_entry_del_data( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_entry_put_info( + BackendDB *be, + NdbArgs *args, + int update ); + +extern int +ndb_entry_get_info( + Operation *op, + NdbArgs *args, + int update, + struct berval *matched ); + +extern "C" int +ndb_entry_del_info( + BackendDB *be, + NdbArgs *args ); + +extern int +ndb_dn2rdns( + struct berval *dn, + NdbRdns *buf ); + +extern NdbAttrInfo * +ndb_ai_find( struct ndb_info *ni, AttributeType *at ); + +extern NdbAttrInfo * +ndb_ai_get( struct ndb_info *ni, struct berval *at ); + +extern int +ndb_aset_get( struct ndb_info *ni, struct berval *sname, struct berval *attrs, NdbOcInfo **ret ); + +extern int +ndb_aset_create( struct ndb_info *ni, NdbOcInfo *oci ); + +extern int +ndb_oc_read( struct ndb_info *ni, const NdbDictionary::Dictionary *dict ); + +extern int +ndb_oc_attrs( + NdbTransaction *txn, + const NdbDictionary::Table *myTable, + Entry *e, + NdbOcInfo *no, + NdbAttrInfo **attrs, + int nattrs, + Attribute *old ); + +extern int +ndb_has_children( + NdbArgs *NA, + int *hasChildren ); + +extern struct berval * +ndb_str2bvarray( + char *str, + int len, + char delim, + void *ctx ); + +extern struct berval * +ndb_ref2oclist( + const char *ref, + void *ctx ); + +extern int +ndb_next_id( + BackendDB *be, + Ndb *ndb, + ID *id ); + +extern int +ndb_thread_handle( + Operation *op, + Ndb **ndb ); + +extern int +ndb_back_init_cf( + BackendInfo *bi ); + +extern "C" void +ndb_trans_backoff( int num_retries ); + +extern "C" void +ndb_check_referral( Operation *op, SlapReply *rs, NdbArgs *NA ); + +LDAP_END_DECL + +#endif /* _PROTO_NDB_H */ diff --git a/servers/slapd/back-ndb/search.cpp b/servers/slapd/back-ndb/search.cpp new file mode 100644 index 0000000..3d0f4b0 --- /dev/null +++ b/servers/slapd/back-ndb/search.cpp @@ -0,0 +1,854 @@ +/* search.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "lutil.h" + +#include "back-ndb.h" + +static int +ndb_dn2bound( + NdbIndexScanOperation *myop, + NdbRdns *rdns +) +{ + unsigned int i; + + /* Walk thru RDNs */ + for ( i=0; i<rdns->nr_num; i++ ) { + /* Note: RDN_COLUMN offset not needed here */ + if ( myop->setBound( i, NdbIndexScanOperation::BoundEQ, rdns->nr_buf[i] )) + return LDAP_OTHER; + } + return i; +} + +/* Check that all filter terms reside in the same table. + * + * If any of the filter terms are indexed, then only an IndexScan of the OL_index + * will be performed. If none are indexed, but all the terms reside in a single + * table, a Scan can be performed with the LDAP filter transformed into a ScanFilter. + * + * Otherwise, a full scan of the DB must be done with all filtering done by slapd. + */ +static int ndb_filter_check( struct ndb_info *ni, Filter *f, + NdbOcInfo **oci, int *indexed, int *ocfilter ) +{ + AttributeDescription *ad = NULL; + ber_tag_t choice = f->f_choice; + int rc = 0, undef = 0; + + if ( choice & SLAPD_FILTER_UNDEFINED ) { + choice &= SLAPD_FILTER_MASK; + undef = 1; + } + switch( choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + for ( f = f->f_list; f; f=f->f_next ) { + rc = ndb_filter_check( ni, f, oci, indexed, ocfilter ); + if ( rc ) return rc; + } + break; + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + break; + default: + break; + } + if ( ad && !undef ) { + NdbAttrInfo *ai; + /* ObjectClass filtering is in dn2id table */ + if ( ad == slap_schema.si_ad_objectClass ) { + if ( choice == LDAP_FILTER_EQUALITY ) + (*ocfilter)++; + return 0; + } + ai = ndb_ai_find( ni, ad->ad_type ); + if ( ai ) { + if ( ai->na_flag & NDB_INFO_INDEX ) + (*indexed)++; + if ( *oci ) { + if ( ai->na_oi != *oci ) + rc = -1; + } else { + *oci = ai->na_oi; + } + } + } + return rc; +} + +static int ndb_filter_set( Operation *op, struct ndb_info *ni, Filter *f, int indexed, + NdbIndexScanOperation *scan, NdbScanFilter *sf, int *bounds ) +{ + AttributeDescription *ad = NULL; + ber_tag_t choice = f->f_choice; + int undef = 0; + + if ( choice & SLAPD_FILTER_UNDEFINED ) { + choice &= SLAPD_FILTER_MASK; + undef = 1; + } + switch( choice ) { + case LDAP_FILTER_NOT: + /* no indexing for these */ + break; + case LDAP_FILTER_OR: + /* FIXME: these bounds aren't right. */ + if ( indexed ) { + scan->end_of_bound( (*bounds)++ ); + } + case LDAP_FILTER_AND: + if ( sf ) { + sf->begin( choice == LDAP_FILTER_OR ? NdbScanFilter::OR : NdbScanFilter::AND ); + } + for ( f = f->f_list; f; f=f->f_next ) { + if ( ndb_filter_set( op, ni, f, indexed, scan, sf, bounds )) + return -1; + } + if ( sf ) { + sf->end(); + } + break; + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + break; + default: + break; + } + if ( ad && !undef ) { + NdbAttrInfo *ai; + /* ObjectClass filtering is in dn2id table */ + if ( ad == slap_schema.si_ad_objectClass ) { + return 0; + } + ai = ndb_ai_find( ni, ad->ad_type ); + if ( ai ) { + int rc; + if ( ai->na_flag & NDB_INFO_INDEX ) { + char *buf, *ptr; + NdbIndexScanOperation::BoundType bt; + + switch(choice) { + case LDAP_FILTER_PRESENT: + rc = scan->setBound( ai->na_ixcol - IDX_COLUMN, + NdbIndexScanOperation::BoundGT, NULL ); + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + bt = NdbIndexScanOperation::BoundEQ; + goto setit; + case LDAP_FILTER_GE: + bt = NdbIndexScanOperation::BoundGE; + goto setit; + case LDAP_FILTER_LE: + bt = NdbIndexScanOperation::BoundLE; + setit: + rc = f->f_av_value.bv_len+1; + if ( ai->na_len > 255 ) + rc++; + buf = (char *)op->o_tmpalloc( rc, op->o_tmpmemctx ); + rc = f->f_av_value.bv_len; + buf[0] = rc & 0xff; + ptr = buf+1; + if ( ai->na_len > 255 ) { + buf[1] = (rc >> 8); + ptr++; + } + memcpy( ptr, f->f_av_value.bv_val, f->f_av_value.bv_len ); + rc = scan->setBound( ai->na_ixcol - IDX_COLUMN, bt, buf ); + op->o_tmpfree( buf, op->o_tmpmemctx ); + break; + default: + break; + } + } else if ( sf ) { + char *buf, *ptr; + NdbScanFilter::BinaryCondition bc; + + switch(choice) { + case LDAP_FILTER_PRESENT: + rc = sf->isnotnull( ai->na_column ); + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_APPROX: + bc = NdbScanFilter::COND_EQ; + goto setf; + case LDAP_FILTER_GE: + bc = NdbScanFilter::COND_GE; + goto setf; + case LDAP_FILTER_LE: + bc = NdbScanFilter::COND_LE; + setf: + rc = sf->cmp( bc, ai->na_column, f->f_av_value.bv_val, f->f_av_value.bv_len ); + break; + case LDAP_FILTER_SUBSTRINGS: + rc = 0; + if ( f->f_sub_initial.bv_val ) + rc += f->f_sub_initial.bv_len + 1; + if ( f->f_sub_any ) { + int i; + if ( !rc ) rc++; + for (i=0; f->f_sub_any[i].bv_val; i++) + rc += f->f_sub_any[i].bv_len + 1; + } + if ( f->f_sub_final.bv_val ) { + if ( !rc ) rc++; + rc += f->f_sub_final.bv_len; + } + buf = (char *)op->o_tmpalloc( rc+1, op->o_tmpmemctx ); + ptr = buf; + if ( f->f_sub_initial.bv_val ) { + memcpy( ptr, f->f_sub_initial.bv_val, f->f_sub_initial.bv_len ); + ptr += f->f_sub_initial.bv_len; + *ptr++ = '%'; + } + if ( f->f_sub_any ) { + int i; + if ( ptr == buf ) + *ptr++ = '%'; + for (i=0; f->f_sub_any[i].bv_val; i++) { + memcpy( ptr, f->f_sub_any[i].bv_val, f->f_sub_any[i].bv_len ); + ptr += f->f_sub_any[i].bv_len; + *ptr++ = '%'; + } + } + if ( f->f_sub_final.bv_val ) { + if ( ptr == buf ) + *ptr++ = '%'; + memcpy( ptr, f->f_sub_final.bv_val, f->f_sub_final.bv_len ); + ptr += f->f_sub_final.bv_len; + } + *ptr = '\0'; + rc = sf->cmp( NdbScanFilter::COND_LIKE, ai->na_column, buf, ptr - buf ); + op->o_tmpfree( buf, op->o_tmpmemctx ); + break; + } + } + } + } + return 0; +} + +static int ndb_oc_search( Operation *op, SlapReply *rs, Ndb *ndb, NdbTransaction *txn, + NdbRdns *rbase, NdbOcInfo *oci, int indexed ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + const NdbDictionary::Dictionary *myDict = ndb->getDictionary(); + const NdbDictionary::Table *myTable; + const NdbDictionary::Index *myIndex; + NdbIndexScanOperation *scan; + NdbIndexOperation *ixop; + NdbScanFilter *sf = NULL; + struct berval *ocs; + NdbRecAttr *scanID, *scanOC, *scanDN[NDB_MAX_RDNS]; + char dnBuf[2048], *ptr; + NdbRdns rdns; + NdbArgs NA; + char idbuf[2*sizeof(ID)]; + char ocbuf[NDB_OC_BUFLEN]; + int i, rc, bounds; + Entry e = {0}; + Uint64 eid; + time_t stoptime; + int manageDSAit; + + stoptime = op->o_time + op->ors_tlimit; + manageDSAit = get_manageDSAit( op ); + + myTable = myDict->getTable( oci->no_table.bv_val ); + if ( indexed ) { + scan = txn->getNdbIndexScanOperation( INDEX_NAME, DN2ID_TABLE ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_CommittedRead ); + } else { + myIndex = myDict->getIndex( "eid$unique", DN2ID_TABLE ); + if ( !myIndex ) { + Debug( LDAP_DEBUG_ANY, DN2ID_TABLE " eid index is missing!\n", 0, 0, 0 ); + rs->sr_err = LDAP_OTHER; + goto leave; + } + scan = (NdbIndexScanOperation *)txn->getNdbScanOperation( myTable ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_CommittedRead ); +#if 1 + sf = new NdbScanFilter(scan); + if ( !sf ) + return LDAP_OTHER; + switch ( op->ors_filter->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + break; + default: + if ( sf->begin() < 0 ) { + rc = LDAP_OTHER; + goto leave; + } + } +#endif + } + + bounds = 0; + rc = ndb_filter_set( op, ni, op->ors_filter, indexed, scan, sf, &bounds ); + if ( rc ) + goto leave; + if ( sf ) sf->end(); + + scanID = scan->getValue( EID_COLUMN, idbuf ); + if ( indexed ) { + scanOC = scan->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = scan->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + } + + if ( txn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + e.e_name.bv_val = dnBuf; + NA.e = &e; + NA.ndb = ndb; + while ( scan->nextResult( true, true ) == 0 ) { + NdbTransaction *tx2; + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + break; + } + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + break; + } + if ( op->ors_tlimit != SLAP_NO_LIMIT && + slap_get_time() > stoptime ) { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + break; + } + + eid = scanID->u_64_value(); + e.e_id = eid; + if ( !indexed ) { + tx2 = ndb->startTransaction( myTable ); + if ( !tx2 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + ixop = tx2->getNdbIndexOperation( myIndex ); + if ( !ixop ) { + tx2->close(); + rs->sr_err = LDAP_OTHER; + goto leave; + } + ixop->readTuple( NdbOperation::LM_CommittedRead ); + ixop->equal( EID_COLUMN, eid ); + + scanOC = ixop->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = ixop->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + rc = tx2->execute( NdbTransaction::Commit, NdbOperation::AbortOnError, 1 ); + tx2->close(); + if ( rc ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + } + + ocs = ndb_ref2oclist( ocbuf, op->o_tmpmemctx ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( scanDN[i]->isNULL() || !rdns.nr_buf[i][0] ) + break; + } + rdns.nr_num = i; + + /* entry must be subordinate to the base */ + if ( i < rbase->nr_num ) { + continue; + } + + ptr = dnBuf; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = rdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) *ptr++ = ','; + } + *ptr = '\0'; + e.e_name.bv_len = ptr - dnBuf; + + /* More scope checks */ + /* If indexed, these can be moved into the ScanFilter */ + switch( op->ors_scope ) { + case LDAP_SCOPE_ONELEVEL: + if ( rdns.nr_num != rbase->nr_num+1 ) + continue; + case LDAP_SCOPE_SUBORDINATE: + if ( rdns.nr_num == rbase->nr_num ) + continue; + case LDAP_SCOPE_SUBTREE: + default: + if ( e.e_name.bv_len <= op->o_req_dn.bv_len ) { + if ( op->ors_scope != LDAP_SCOPE_SUBTREE || + strcasecmp( op->o_req_dn.bv_val, e.e_name.bv_val )) + continue; + } else if ( strcasecmp( op->o_req_dn.bv_val, e.e_name.bv_val + + e.e_name.bv_len - op->o_req_dn.bv_len )) + continue; + } + + dnNormalize( 0, NULL, NULL, &e.e_name, &e.e_nname, op->o_tmpmemctx ); + { +#if 1 /* NDBapi was broken here but seems to work now */ + Ndb::Key_part_ptr keys[2]; + char xbuf[512]; + keys[0].ptr = &eid; + keys[0].len = sizeof(eid); + keys[1].ptr = NULL; + keys[1].len = 0; + tx2 = ndb->startTransaction( myTable, keys, xbuf, sizeof(xbuf)); +#else + tx2 = ndb->startTransaction( myTable ); +#endif + if ( !tx2 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + NA.txn = tx2; + NA.ocs = ocs; + rc = ndb_entry_get_data( op, &NA, 0 ); + tx2->close(); + } + ber_bvarray_free_x( ocs, op->o_tmpmemctx ); + if ( !manageDSAit && is_entry_referral( &e )) { + BerVarray erefs = get_entry_referrals( op, &e ); + rs->sr_ref = referral_rewrite( erefs, &e.e_name, NULL, + op->ors_scope == LDAP_SCOPE_ONELEVEL ? + LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + rc = send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + } else if ( manageDSAit || !is_entry_glue( &e )) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + rc = send_search_entry( op, rs ); + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + } else { + rc = 0; + } + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + op->o_tmpfree( e.e_nname.bv_val, op->o_tmpmemctx ); + if ( rc ) break; + } +leave: + if ( sf ) delete sf; + return rc; +} + +extern "C" +int ndb_back_search( Operation *op, SlapReply *rs ) +{ + struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private; + NdbTransaction *txn; + NdbIndexScanOperation *scan; + NdbScanFilter *sf = NULL; + Entry e = {0}; + int rc, i, ocfilter, indexed; + struct berval matched; + NdbRecAttr *scanID, *scanOC, *scanDN[NDB_MAX_RDNS]; + char dnBuf[2048], *ptr; + char idbuf[2*sizeof(ID)]; + char ocbuf[NDB_OC_BUFLEN]; + NdbRdns rdns; + NdbOcInfo *oci; + NdbArgs NA; + slap_mask_t mask; + time_t stoptime; + int manageDSAit; + + rc = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + + manageDSAit = get_manageDSAit( op ); + + txn = NA.ndb->startTransaction(); + if ( !txn ) { + Debug( LDAP_DEBUG_TRACE, + LDAP_XSTRING(ndb_back_search) ": startTransaction failed: %s (%d)\n", + NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 ); + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + goto leave; + } + + NA.txn = txn; + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + NA.e = &e; + NA.rdns = &rdns; + NA.ocs = NULL; + + rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched ); + if ( rs->sr_err ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = matched.bv_val; + if ( NA.ocs ) + ndb_check_referral( op, rs, &NA ); + } + goto leave; + } + + 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; + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + goto leave; + } + + rs->sr_err = ndb_entry_get_data( op, &NA, 0 ); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + if ( rs->sr_err ) + goto leave; + + if ( !manageDSAit && is_entry_referral( &e )) { + rs->sr_ref = get_entry_referrals( op, &e ); + rs->sr_err = LDAP_REFERRAL; + if ( rs->sr_ref ) + rs->sr_flags |= REP_REF_MUSTBEFREED; + rs->sr_matched = e.e_name.bv_val; + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + goto leave; + } + + if ( !manageDSAit && is_entry_glue( &e )) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + goto leave; + } + + if ( get_assert( op ) && test_filter( op, &e, (Filter *)get_assertion( op )) != + LDAP_COMPARE_TRUE ) { + rs->sr_err = LDAP_ASSERTION_FAILED; + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + goto leave; + } + + /* admin ignores tlimits */ + stoptime = op->o_time + op->ors_tlimit; + + if ( op->ors_scope == LDAP_SCOPE_BASE ) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + send_search_entry( op, rs ); + rs->sr_entry = NULL; + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + rs->sr_err = LDAP_SUCCESS; + goto leave; + } else { + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + if ( rdns.nr_num == NDB_MAX_RDNS ) { + if ( op->ors_scope == LDAP_SCOPE_ONELEVEL || + op->ors_scope == LDAP_SCOPE_CHILDREN ) + rs->sr_err = LDAP_SUCCESS; + goto leave; + } + } + + /* See if we can handle the filter. Filtering on objectClass is only done + * in the DN2ID table scan. If all other filter terms reside in one table, + * then we scan the OC table instead of the DN2ID table. + */ + oci = NULL; + indexed = 0; + ocfilter = 0; + rc = ndb_filter_check( ni, op->ors_filter, &oci, &indexed, &ocfilter ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "ndb_back_search: " + "filter attributes from multiple tables, indexing ignored\n", + 0, 0, 0 ); + } else if ( oci ) { + rc = ndb_oc_search( op, rs, NA.ndb, txn, &rdns, oci, indexed ); + goto leave; + } + + scan = txn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !scan ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + scan->readTuples( NdbOperation::LM_CommittedRead ); + rc = ndb_dn2bound( scan, &rdns ); + + /* TODO: if ( ocfilter ) set up scanfilter for objectclass matches + * column COND_LIKE "% <class> %" + */ + + switch( op->ors_scope ) { + case LDAP_SCOPE_ONELEVEL: + sf = new NdbScanFilter(scan); + if ( sf->begin() < 0 || + sf->cmp(NdbScanFilter::COND_NOT_LIKE, rc+3, "_%", + STRLENOF("_%")) < 0 || + sf->end() < 0 ) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + /* FALLTHRU */ + case LDAP_SCOPE_CHILDREN: + /* Note: RDN_COLUMN offset not needed here */ + scan->setBound( rc, NdbIndexScanOperation::BoundLT, "\0" ); + /* FALLTHRU */ + case LDAP_SCOPE_SUBTREE: + break; + } + scanID = scan->getValue( EID_COLUMN, idbuf ); + scanOC = scan->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + rdns.nr_buf[i][0] = '\0'; + scanDN[i] = scan->getValue( RDN_COLUMN+i, rdns.nr_buf[i] ); + } + if ( txn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) { + rs->sr_err = LDAP_OTHER; + goto leave; + } + + e.e_name.bv_val = dnBuf; + while ( scan->nextResult( true, true ) == 0 ) { + if ( op->o_abandon ) { + rs->sr_err = SLAPD_ABANDON; + break; + } + if ( slapd_shutdown ) { + rs->sr_err = LDAP_UNAVAILABLE; + break; + } + if ( op->ors_tlimit != SLAP_NO_LIMIT && + slap_get_time() > stoptime ) { + rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + break; + } + e.e_id = scanID->u_64_value(); + NA.ocs = ndb_ref2oclist( ocbuf, op->o_tmpmemctx ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( scanDN[i]->isNULL() || !rdns.nr_buf[i][0] ) + break; + } + ptr = dnBuf; + rdns.nr_num = i; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = rdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) *ptr++ = ','; + } + *ptr = '\0'; + e.e_name.bv_len = ptr - dnBuf; + dnNormalize( 0, NULL, NULL, &e.e_name, &e.e_nname, op->o_tmpmemctx ); + NA.txn = NA.ndb->startTransaction(); + rc = ndb_entry_get_data( op, &NA, 0 ); + NA.txn->close(); + ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx ); + if ( !manageDSAit && is_entry_referral( &e )) { + BerVarray erefs = get_entry_referrals( op, &e ); + rs->sr_ref = referral_rewrite( erefs, &e.e_name, NULL, + op->ors_scope == LDAP_SCOPE_ONELEVEL ? + LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE ); + rc = send_search_reference( op, rs ); + ber_bvarray_free( rs->sr_ref ); + ber_bvarray_free( erefs ); + rs->sr_ref = NULL; + } else if ( manageDSAit || !is_entry_glue( &e )) { + rc = test_filter( op, &e, op->ors_filter ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_entry = &e; + rs->sr_attrs = op->ors_attrs; + rs->sr_flags = 0; + rc = send_search_entry( op, rs ); + rs->sr_entry = NULL; + } else { + rc = 0; + } + } + attrs_free( e.e_attrs ); + e.e_attrs = NULL; + op->o_tmpfree( e.e_nname.bv_val, op->o_tmpmemctx ); + if ( rc ) break; + } +leave: + if ( sf ) + delete sf; + if ( txn ) + txn->close(); + send_ldap_result( op, rs ); + return rs->sr_err; +} + +extern NdbInterpretedCode *ndb_lastrow_code; /* init.cpp */ + +extern "C" int +ndb_has_children( + NdbArgs *NA, + int *hasChildren +) +{ + NdbIndexScanOperation *scan; + char idbuf[2*sizeof(ID)]; + int rc; + + if ( NA->rdns->nr_num >= NDB_MAX_RDNS ) { + *hasChildren = LDAP_COMPARE_FALSE; + return 0; + } + + scan = NA->txn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !scan ) + return LDAP_OTHER; + scan->readTuples( NdbOperation::LM_Read, 0U, 0U, 1U ); + rc = ndb_dn2bound( scan, NA->rdns ); + if ( rc < NDB_MAX_RDNS ) { + scan->setBound( rc, NdbIndexScanOperation::BoundLT, "\0" ); + } +#if 0 + scan->interpret_exit_last_row(); +#else + scan->setInterpretedCode(ndb_lastrow_code); +#endif + scan->getValue( EID_COLUMN, idbuf ); + if ( NA->txn->execute( NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1 )) { + return LDAP_OTHER; + } + if (rc < NDB_MAX_RDNS && scan->nextResult( true, true ) == 0 ) + *hasChildren = LDAP_COMPARE_TRUE; + else + *hasChildren = LDAP_COMPARE_FALSE; + scan->close(); + return 0; +} + +extern "C" int +ndb_has_subordinates( + Operation *op, + Entry *e, + int *hasSubordinates ) +{ + NdbArgs NA; + NdbRdns rdns; + int rc; + + NA.rdns = &rdns; + rc = ndb_dn2rdns( &e->e_nname, &rdns ); + + if ( rc == 0 ) { + rc = ndb_thread_handle( op, &NA.ndb ); + NA.txn = NA.ndb->startTransaction(); + if ( NA.txn ) { + rc = ndb_has_children( &NA, hasSubordinates ); + NA.txn->close(); + } + } + + return rc; +} + +/* + * sets the supported operational attributes (if required) + */ +extern "C" int +ndb_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 = ndb_has_subordinates( 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-ndb/tools.cpp b/servers/slapd/back-ndb/tools.cpp new file mode 100644 index 0000000..13ea7c3 --- /dev/null +++ b/servers/slapd/back-ndb/tools.cpp @@ -0,0 +1,544 @@ +/* tools.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2008-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> +#include <ac/errno.h> + +#include "lutil.h" + +#include "back-ndb.h" + +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 Avlnode *myParents; + +static Ndb *myNdb; +static NdbTransaction *myScanTxn; +static NdbIndexScanOperation *myScanOp; + +static NdbRecAttr *myScanID, *myScanOC; +static NdbRecAttr *myScanDN[NDB_MAX_RDNS]; +static char myDNbuf[2048]; +static char myIdbuf[2*sizeof(ID)]; +static char myOcbuf[NDB_OC_BUFLEN]; +static NdbRdns myRdns; + +static NdbTransaction *myPutTxn; +static int myPutCnt; + +static struct berval *myOcList; +static struct berval myDn; + +extern "C" +int ndb_tool_entry_open( + BackendDB *be, int mode ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + myNdb = new Ndb( ni->ni_cluster[0], ni->ni_dbname ); + return myNdb->init(1024); +} + +extern "C" +int ndb_tool_entry_close( + BackendDB *be ) +{ + if ( myPutTxn ) { + int rc = myPutTxn->execute(NdbTransaction::Commit); + if( rc != 0 ) { + char text[1024]; + snprintf( text, sizeof(text), + "txn_commit failed: %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text, 0, 0 ); + } + myPutTxn->close(); + myPutTxn = NULL; + } + myPutCnt = 0; + + 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; +} + +extern "C" +ID ndb_tool_entry_next( + BackendDB *be ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + char *ptr; + ID id; + int i; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + if ( myScanOp->nextResult() ) { + myScanOp->close(); + myScanOp = NULL; + myScanTxn->close(); + myScanTxn = NULL; + return NOID; + } + id = myScanID->u_64_value(); + + if ( myOcList ) { + ber_bvarray_free( myOcList ); + } + myOcList = ndb_ref2oclist( myOcbuf, NULL ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + if ( myScanDN[i]->isNULL() || !myRdns.nr_buf[i][0] ) + break; + } + myRdns.nr_num = i; + ptr = myDNbuf; + for ( --i; i>=0; i-- ) { + char *buf; + int len; + buf = myRdns.nr_buf[i]; + len = *buf++; + ptr = lutil_strncopy( ptr, buf, len ); + if ( i ) + *ptr++ = ','; + } + *ptr = '\0'; + myDn.bv_val = myDNbuf; + myDn.bv_len = ptr - myDNbuf; + + return id; +} + +extern "C" +ID ndb_tool_entry_first( + BackendDB *be ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + int i; + + myScanTxn = myNdb->startTransaction(); + if ( !myScanTxn ) + return NOID; + + myScanOp = myScanTxn->getNdbIndexScanOperation( "PRIMARY", DN2ID_TABLE ); + if ( !myScanOp ) + return NOID; + + if ( myScanOp->readTuples( NdbOperation::LM_CommittedRead, NdbScanOperation::SF_KeyInfo )) + return NOID; + + myScanID = myScanOp->getValue( EID_COLUMN, myIdbuf ); + myScanOC = myScanOp->getValue( OCS_COLUMN, myOcbuf ); + for ( i=0; i<NDB_MAX_RDNS; i++ ) { + myScanDN[i] = myScanOp->getValue( i+RDN_COLUMN, myRdns.nr_buf[i] ); + } + if ( myScanTxn->execute( NdbTransaction::NoCommit, NdbOperation::AbortOnError, 1 )) + return NOID; + + return ndb_tool_entry_next( be ); +} + +extern "C" +ID ndb_tool_dn2id_get( + Backend *be, + struct berval *dn +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + NdbArgs NA; + NdbRdns rdns; + Entry e; + char text[1024]; + Operation op = {0}; + Opheader ohdr = {0}; + int rc; + + if ( BER_BVISEMPTY(dn) ) + return 0; + + NA.ndb = myNdb; + NA.txn = myNdb->startTransaction(); + if ( !NA.txn ) { + snprintf( text, sizeof(text), + "startTransaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_dn2id_get) ": %s\n", + text, 0, 0 ); + return NOID; + } + if ( myOcList ) { + ber_bvarray_free( myOcList ); + myOcList = NULL; + } + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + NA.e = &e; + e.e_name = *dn; + NA.rdns = &rdns; + NA.ocs = NULL; + rc = ndb_entry_get_info( &op, &NA, 0, NULL ); + myOcList = NA.ocs; + NA.txn->close(); + if ( rc ) + return NOID; + + myDn = *dn; + + return e.e_id; +} + +extern "C" +Entry* ndb_tool_entry_get( BackendDB *be, ID id ) +{ + NdbArgs NA; + int rc; + char text[1024]; + Operation op = {0}; + Opheader ohdr = {0}; + + assert( be != NULL ); + assert( slapMode & SLAP_TOOL_MODE ); + + NA.txn = myNdb->startTransaction(); + if ( !NA.txn ) { + snprintf( text, sizeof(text), + "start_transaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_get) ": %s\n", + text, 0, 0 ); + return NULL; + } + + NA.e = entry_alloc(); + NA.e->e_id = id; + ber_dupbv( &NA.e->e_name, &myDn ); + dnNormalize( 0, NULL, NULL, &NA.e->e_name, &NA.e->e_nname, NULL ); + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + NA.ndb = myNdb; + NA.ocs = myOcList; + rc = ndb_entry_get_data( &op, &NA, 0 ); + + if ( rc ) { + entry_free( NA.e ); + NA.e = NULL; + } + NA.txn->close(); + + return NA.e; +} + +static struct berval glueval[] = { + BER_BVC("glue"), + BER_BVNULL +}; + +static int ndb_dnid_cmp( const void *v1, const void *v2 ) +{ + struct dn_id *dn1 = (struct dn_id *)v1, + *dn2 = (struct dn_id *)v2; + return ber_bvcmp( &dn1->dn, &dn2->dn ); +} + +static int ndb_tool_next_id( + Operation *op, + NdbArgs *NA, + struct berval *text, + int hole ) +{ + struct berval ndn = NA->e->e_nname; + int rc; + + if (ndn.bv_len == 0) { + NA->e->e_id = 0; + return 0; + } + + rc = ndb_entry_get_info( op, NA, 0, NULL ); + if ( rc ) { + Attribute *a, tmp = {0}; + if ( !be_issuffix( op->o_bd, &ndn ) ) { + struct dn_id *dptr; + struct berval npdn; + dnParent( &ndn, &npdn ); + NA->e->e_nname = npdn; + NA->rdns->nr_num--; + rc = ndb_tool_next_id( op, NA, text, 1 ); + NA->e->e_nname = ndn; + NA->rdns->nr_num++; + if ( rc ) { + return rc; + } + /* If parent didn't exist, it was created just now + * and its ID is now in e->e_id. + */ + dptr = (struct dn_id *)ch_malloc( sizeof( struct dn_id ) + npdn.bv_len + 1); + dptr->id = NA->e->e_id; + dptr->dn.bv_val = (char *)(dptr+1); + strcpy(dptr->dn.bv_val, npdn.bv_val ); + dptr->dn.bv_len = npdn.bv_len; + if ( avl_insert( &myParents, dptr, ndb_dnid_cmp, avl_dup_error )) { + ch_free( dptr ); + } + } + rc = ndb_next_id( op->o_bd, myNdb, &NA->e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + if ( hole ) { + a = NA->e->e_attrs; + NA->e->e_attrs = &tmp; + tmp.a_desc = slap_schema.si_ad_objectClass; + tmp.a_vals = glueval; + tmp.a_nvals = tmp.a_vals; + tmp.a_numvals = 1; + } + rc = ndb_entry_put_info( op->o_bd, NA, 0 ); + if ( hole ) { + NA->e->e_attrs = a; + } + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "ndb_entry_put_info failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + } else if ( hole ) { + if ( nholes == nhmax - 1 ) { + if ( holes == hbuf ) { + holes = (dn_id *)ch_malloc( nhmax * sizeof(dn_id) * 2 ); + AC_MEMCPY( holes, hbuf, sizeof(hbuf) ); + } else { + holes = (dn_id *)ch_realloc( holes, nhmax * sizeof(dn_id) * 2 ); + } + nhmax *= 2; + } + ber_dupbv( &holes[nholes].dn, &ndn ); + holes[nholes++].id = NA->e->e_id; + } + } else if ( !hole ) { + unsigned i; + + for ( i=0; i<nholes; i++) { + if ( holes[i].id == NA->e->e_id ) { + int j; + free(holes[i].dn.bv_val); + for (j=i;j<nholes;j++) holes[j] = holes[j+1]; + holes[j].id = 0; + nholes--; + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + break; + } else if ( holes[i].id > NA->e->e_id ) { + break; + } + } + } + return rc; +} + +extern "C" +ID ndb_tool_entry_put( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + struct dn_id dtmp, *dptr; + NdbArgs NA; + NdbRdns rdns; + int rc, slow = 0; + 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(ndb_tool_entry_put) + "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn, 0 ); + + if ( !be_issuffix( be, &e->e_nname )) { + dnParent( &e->e_nname, &dtmp.dn ); + dptr = (struct dn_id *)avl_find( myParents, &dtmp, ndb_dnid_cmp ); + if ( !dptr ) + slow = 1; + } + + rdns.nr_num = 0; + + op.o_hdr = &ohdr; + op.o_bd = be; + op.o_tmpmemctx = NULL; + op.o_tmpmfuncs = &ch_mfuncs; + + if ( !slow ) { + rc = ndb_next_id( be, myNdb, &e->e_id ); + if ( rc ) { + snprintf( text->bv_val, text->bv_len, + "next_id failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> ndb_tool_next_id: %s\n", text->bv_val, 0, 0 ); + return rc; + } + } + + if ( !myPutTxn ) + myPutTxn = myNdb->startTransaction(); + if ( !myPutTxn ) { + snprintf( text->bv_val, text->bv_len, + "start_transaction failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + return NOID; + } + + /* add dn2id indices */ + ndb_dn2rdns( &e->e_name, &rdns ); + NA.rdns = &rdns; + NA.e = e; + NA.ndb = myNdb; + NA.txn = myPutTxn; + if ( slow ) { + rc = ndb_tool_next_id( &op, &NA, text, 0 ); + if( rc != 0 ) { + goto done; + } + } else { + rc = ndb_entry_put_info( be, &NA, 0 ); + if ( rc != 0 ) { + goto done; + } + } + + /* id2entry index */ + rc = ndb_entry_put_data( be, &NA ); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "ndb_entry_put_data failed: %s (%d)", + myNdb->getNdbError().message, myNdb->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + goto done; + } + +done: + if( rc == 0 ) { + myPutCnt++; + if ( !( myPutCnt & 0x0f )) { + rc = myPutTxn->execute(NdbTransaction::Commit); + if( rc != 0 ) { + snprintf( text->bv_val, text->bv_len, + "txn_commit failed: %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + } + myPutTxn->close(); + myPutTxn = NULL; + } + } else { + snprintf( text->bv_val, text->bv_len, + "txn_aborted! %s (%d)", + myPutTxn->getNdbError().message, myPutTxn->getNdbError().code ); + Debug( LDAP_DEBUG_ANY, + "=> " LDAP_XSTRING(ndb_tool_entry_put) ": %s\n", + text->bv_val, 0, 0 ); + e->e_id = NOID; + myPutTxn->close(); + } + + return e->e_id; +} + +extern "C" +int ndb_tool_entry_reindex( + BackendDB *be, + ID id, + AttributeDescription **adv ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + Debug( LDAP_DEBUG_ARGS, + "=> " LDAP_XSTRING(ndb_tool_entry_reindex) "( %ld )\n", + (long) id, 0, 0 ); + + return 0; +} + +extern "C" +ID ndb_tool_entry_modify( + BackendDB *be, + Entry *e, + struct berval *text ) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + int rc; + + Debug( LDAP_DEBUG_TRACE, + "=> " LDAP_XSTRING(ndb_tool_entry_modify) "( %ld, \"%s\" )\n", + (long) e->e_id, e->e_dn, 0 ); + +done: + return e->e_id; +} + |