From: Howard Chu Date: Sun, 2 Mar 2008 15:14:53 +0000 (+0000) Subject: MySQL NDB Cluster backend (experimental) X-Git-Tag: OPENLDAP_REL_ENG_2_4_9~20^2~123 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=da6fdf70a41dbc800c61e9e8bc0f5373753fb362;p=openldap MySQL NDB Cluster backend (experimental) --- diff --git a/servers/slapd/back-ndb/Makefile.in b/servers/slapd/back-ndb/Makefile.in new file mode 100644 index 0000000000..84253fd4e1 --- /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 . +## +## Copyright 2008 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 +## . +## +## 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_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_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 0000000000..2c4f090903 --- /dev/null +++ b/servers/slapd/back-ndb/TODO @@ -0,0 +1,9 @@ +LDAP features not currently supported: + +multi-valued attributes +tagged attributes +extensibleObjects +aliases +referrals +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 0000000000..44c182d82b --- /dev/null +++ b/servers/slapd/back-ndb/add.cpp @@ -0,0 +1,265 @@ +/* add.cpp - ldap NDB back-end add routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include + +#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}; + 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; + + 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; + + /* check entry's schema */ + rs->sr_err = entry_schema_check( op, op->oq_add.rs_e, NULL, + get_relax(op), 1, &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 ) ) { + p.e_name = slap_empty_bv; + p.e_nname = slap_empty_bv; + } else { + dnParent( &op->oq_add.rs_e->e_nname, &p.e_nname ); + dnParent( &op->oq_add.rs_e->e_name, &p.e_name ); + } + + 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 */ + { + Entry dummy; + dummy.e_name = op->ora_e->e_name; + NA.e = &dummy; + NA.ocs = &matched; + rs->sr_err = ndb_entry_get_info( op->o_bd, &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 ( ber_bvstrcasecmp( &p.e_nname, &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; + goto return_results; + } + + 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;; + } + + rs->sr_err = access_allowed( op, op->oq_add.rs_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;; + } + + /* 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; + } + } + + NA.e = op->ora_e; + /* dn2id index */ + rs->sr_err = ndb_entry_put_info( op->o_bd, &NA, 0 ); + + /* id2entry index */ + rs->sr_err = ndb_entry_put_data( op->o_bd, &NA, 0 ); + + /* 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( Rollback )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + + } else { + if(( rs->sr_err=NA.txn->execute( Commit )) != 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 0000000000..f135e0ab1e --- /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 0000000000..750eb4e13d --- /dev/null +++ b/servers/slapd/back-ndb/back-ndb.h @@ -0,0 +1,163 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* 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 +#include + +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_ACTIVE 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; + 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 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; + NdbOcInfo *no_info[NDB_MAX_OCS]; + struct berval no_text[NDB_MAX_OCS]; +} NdbOcs; + +typedef struct NdbArgs { + Ndb *ndb; + NdbTransaction *txn; + Entry *e; + NdbRdns *rdns; + struct berval *ocs; +} 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 0000000000..d8465becb4 --- /dev/null +++ b/servers/slapd/back-ndb/bind.cpp @@ -0,0 +1,158 @@ +/* bind.cpp - ndb backend bind routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include + +#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 SLAP_CB_CONTINUE: + break; + + default: + return rs->sr_err; + } + + /* 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; + +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->o_bd, &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->o_bd, &NA, 0 ); + ber_bvarray_free( NA.ocs ); + 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 ( 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 0000000000..907ed8b7b7 --- /dev/null +++ b/servers/slapd/back-ndb/compare.cpp @@ -0,0 +1,168 @@ +/* compare.cpp - ndb backend compare routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include + +#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; + +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->o_bd, &NA, 0, &matched ); + switch( rs->sr_err ) { + case 0: + break; + case LDAP_NO_SUCH_OBJECT: + rs->sr_matched = matched.bv_val; + 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->o_bd, &NA, 0 ); + ber_bvarray_free( NA.ocs ); + if (!manageDSAit && is_entry_referral( &e ) ) { + /* return referral only if "disclose" is granted on the object */ + if ( !access_allowed( op, &e, slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } else { + /* entry is a referral, don't allow compare */ + rs->sr_ref = get_entry_referrals( op, &e ); + rs->sr_err = LDAP_REFERRAL; + rs->sr_matched = e.e_name.bv_val; + } + + Debug( LDAP_DEBUG_TRACE, "entry is referral\n", 0, 0, 0 ); + 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 ); + + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + + 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 0000000000..8b8c302abb --- /dev/null +++ b/servers/slapd/back-ndb/config.cpp @@ -0,0 +1,287 @@ +/* config.cpp - ndb backend configuration file routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* 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 "config.h" + +extern "C" { + static ConfigDriver ndb_cf_gen; +}; + +enum { + NDB_ATLEN = 1, + NDB_ATSET, + NDB_INDEX +}; + +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> 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; ino_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; + + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */ + rc = 0; + switch( c->type ) { + case NDB_INDEX: + if ( c->valx == -1 ) { + int i; + + /* 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] ), ',' ); + rc = ndb_aset_get( ni, &bv, bva, &oci ); + ch_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; + } + 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 0000000000..6c969abe3f --- /dev/null +++ b/servers/slapd/back-ndb/delete.cpp @@ -0,0 +1,318 @@ +/* delete.cpp - ndb backend delete routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include + +#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_LUTIL_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->o_bd, &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; + } 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->o_bd, &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( Rollback )) != 0 ) { + rs->sr_text = "txn (no-op) failed"; + } else { + rs->sr_err = LDAP_X_NO_OPERATION; + } + } else { + if(( rs->sr_err=NA.txn->execute( Commit )) != 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( NA.ocs ); + 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 0000000000..82dcfa00b8 --- /dev/null +++ b/servers/slapd/back-ndb/init.cpp @@ -0,0 +1,403 @@ +/* init.cpp - initialize ndb backend */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include +#include +#include +#include +#include "back-ndb.h" +#include +#include "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 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_malloc( ni->ni_nconns * sizeof( Ndb_cluster_connection *)); + for ( i=0; ini_nconns; i++ ) { + ni->ni_cluster[i] = new Ndb_cluster_connection( ni->ni_connectstr ); + rc = ni->ni_cluster[i]->connect( 4, 5, 1 ); + } + for ( i=0; ini_nconns; i++ ) { + rc = ni->ni_cluster[i]->wait_until_ready( 30, 0 ); + } + + 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, rc; + struct ndb_info *ni = (struct ndb_info *) be->be_private; + + mysql_close( &ni->ni_sql ); + for ( i=0; ini_nconns; 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 ) +{ + 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(); + + 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 */ +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 0000000000..d171bbcab5 --- /dev/null +++ b/servers/slapd/back-ndb/modify.cpp @@ -0,0 +1,458 @@ +/* modify.cpp - ndb backend modify routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include + +#include "back-ndb.h" + +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; + int rc, err, indexed = 0; + + 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; + } + + 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); + err = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + err, *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); + err = modify_delete_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + assert( err != LDAP_TYPE_OR_VALUE_EXISTS ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + err, *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); + err = modify_replace_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + err, *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); + err = modify_increment_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, + "ndb_modify_internal: %d %s\n", + err, *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; + + err = modify_add_values( NA->e, mod, get_permissiveModify(op), + text, textbuf, textlen ); + + mod->sm_op = SLAP_MOD_SOFTADD; + + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) { + err = LDAP_SUCCESS; + } + + if( err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + err, *text, 0); + } + break; + + default: + Debug(LDAP_DEBUG_ANY, "ndb_modify_internal: invalid op %d\n", + mod->sm_op, 0, 0); + *text = "Invalid modify operation"; + err = LDAP_OTHER; + Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n", + err, *text, 0); + } + + if ( err != LDAP_SUCCESS ) { + return err; + } + + /* 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, + 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 ); + } + 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; isml_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 || indexed ) { + rc = ndb_entry_put_info( op->o_bd, NA, 1 ); + if ( rc ) return rc; + } + + myDict = NA->ndb->getDictionary(); + + /* One operation per table... */ + for ( i=0; igetTable( atmp->na_oi->no_table.bv_val ); + if ( !myTable ) continue; + myOp = NULL; + nmods = 0; + rc = ndb_oc_attrs( NA->txn, myTable, NA->e, atmp->na_oi, &atmp, 1, 1, &nmods, &myOp ); + if ( rc ) return rc; + for ( j=i+1; jna_oi == atmp->na_oi ) { + atmp = modai[j]; + modai[j] = NULL; + rc = ndb_oc_attrs( NA->txn, myTable, NA->e, atmp->na_oi, &atmp, 1, 1, &nmods, &myOp ); + if ( rc ) return rc; + } + } + } + return 0; +} + + +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; + + int rc; + + 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( NA.ocs ); + } + 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->o_bd, &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; + 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->o_bd, &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( Rollback ) ) != 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( Commit ) ) != 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( NA.ocs ); + 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 0000000000..c9ac0d89e7 --- /dev/null +++ b/servers/slapd/back-ndb/modrdn.cpp @@ -0,0 +1,504 @@ +/* modrdn.cpp - ndb backend modrdn routine */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include + +#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 ( NA.ocs ) { + ber_bvarray_free( NA.ocs ); + } + 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_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->o_bd, &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; + 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->o_bd, &NA, 1 ); + + 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; + NA2.ocs = &matched; + rs->sr_err = ndb_entry_get_info( op->o_bd, &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; + } + } + + /* 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->o_bd, &NA2, 1, 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( Rollback )) != 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( Commit )) != 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 ( NA.ocs ) { + ber_bvarray_free( NA.ocs ); + 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 0000000000..bfca544ecd --- /dev/null +++ b/servers/slapd/back-ndb/ndbio.cpp @@ -0,0 +1,1356 @@ +/* ndbio.cpp - get/set/del data for NDB */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include +#include + +#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, atmp; + 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 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; jno_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 ) { + *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 PRIMARY KEY", + 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 = 1; + 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, " ) ENGINE=ndb" ); + 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; ini_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, eid */ + for ( j = 1; jgetColumn( 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(); + } + } + /* Link to any attrsets */ + for ( i=0; ini_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 = 1; + 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_get( struct ndb_info *ni, const NdbDictionary::Dictionary *myDict, + struct berval *oname, 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_get( ni, myDict, &oc->soc_sups[i]->soc_cname, out ); + if ( rc ) return rc; + } + } + } else { + oc = slap_schema.si_oc_top; + } + /* Only insert once */ + for ( i=0; ino_ntext; i++ ) + if ( out->no_text[i].bv_val == oc->soc_cname.bv_val ) + break; + if ( i == out->no_ntext ) + 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; ino_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; + + 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 PRIMARY KEY", + oci->no_table.bv_val ); + + for ( i=0; ino_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, " ) ENGINE=ndb" ); + 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; + + /* 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_get( ni, myDict, &ocsin[i], out ); + if ( rc ) break; + } + ldap_pvt_thread_rdwr_runlock( &ni->ni_oc_rwlock ); + return rc; +} + +/* set all the unique attrs of this objectclass into the table + * max row size is 8192 bytes; how do we detect if the row is too large? + * + * FIXME: Currently only stores the first value of any multivalued attribute. + */ +extern "C" int +ndb_oc_attrs( + NdbTransaction *txn, + const NdbDictionary::Table *myTable, + Entry *e, + NdbOcInfo *no, + NdbAttrInfo **attrs, + int nattrs, + int update, + int *num, + NdbOperation **retop +) +{ + char buf[65538], *ptr; + Attribute *a; + NdbOperation *myop = retop ? *retop : NULL; + int i; + + for ( i=0; ina_oi != no ) + continue; + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( a->a_desc->ad_type == attrs[i]->na_attr ) + break; + } + /* If we found a match, set its value. If we found no match + * and we're updating, delete its value. + */ + if ( a || update ) { + /* objectclass is in dn_idx_table */ + if ( a && a->a_desc == slap_schema.si_ad_objectClass ) + continue; + + /* First attr, get the op, set the type and primary key */ + if ( !*num ) { + Uint64 eid = e->e_id; + myop = txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( update ) { + if ( myop->writeTuple()) + return LDAP_OTHER; + } else { + if ( myop->insertTuple()) + return LDAP_OTHER; + } + if ( myop->equal( EID_COLUMN, eid )) + return LDAP_OTHER; + } + ptr = buf; + if ( a ) { + if ( a->a_vals[0].bv_len > attrs[i]->na_len ) { + Debug( LDAP_DEBUG_ANY, "ndb_oc_attrs: attribute %s too long for column\n", + attrs[i]->na_name.bv_val, 0, 0 ); + return LDAP_CONSTRAINT_VIOLATION; + } + *ptr++ = a->a_vals[0].bv_len & 0xff; + if ( attrs[i]->na_len > 255 ) { + /* MedVar */ + *ptr++ = a->a_vals[0].bv_len >> 8; + } + memcpy( ptr, a->a_vals[0].bv_val, a->a_vals[0].bv_len ); + ptr = buf; + } else { + ptr = NULL; + } + if ( myop->setValue( attrs[i]->na_column, ptr )) + return LDAP_OTHER; + (*num)++; + } + } + if ( retop ) + *retop = myop; + return LDAP_SUCCESS; +} + +static int +ndb_oc_put( + const NdbDictionary::Dictionary *myDict, + NdbTransaction *txn, NdbOcInfo *no, Entry *e, int update ) +{ + const NdbDictionary::Table *myTable; + int i, rc; + + for ( i=0; ino_nsets; i++ ) { + rc = ndb_oc_put( myDict, txn, no->no_sets[i], e, update ); + if ( rc ) + return rc; + } + + myTable = myDict->getTable( no->no_table.bv_val ); + if ( !myTable ) + return LDAP_OTHER; + + i = 0; + return ndb_oc_attrs( txn, myTable, e, no, no->no_attrs, no->no_nattrs, update, &i, NULL ); +} + +extern "C" int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *NA, + int update +) +{ + struct ndb_info *ni = (struct ndb_info *) be->be_private; + ObjectClass *oc; + Attribute *aoc, *a; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable; + NdbOperation *myop; + 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; itxn, myOcs.no_info[i], NA->e, update ); + if ( rc ) return rc; + } + + return 0; +} + +static void +ndb_oc_get( NdbOcInfo *no, int *j, int *nocs, NdbOcInfo ***oclist ) +{ + int i; + NdbOcInfo **ol2; + + for ( i=0; ino_nsets; i++ ) { + ndb_oc_get( no->no_sets[i], j, nocs, oclist ); + } + if ( *j >= *nocs ) { + *nocs *= 2; + ol2 = (NdbOcInfo **)ch_realloc( *oclist, *nocs * sizeof(NdbOcInfo *)); + *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( + 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; + NdbOperation *myop; + Uint64 eid; + + Attribute *aoc, *a; + NdbOcs myOcs; + NdbOcInfo *oci, **oclist = NULL; + char abuf[65536], *ptr, **attrs = NULL; + + /* FIXME: abuf should be dynamically allocated */ + + int i, j, k, nocs, nattrs, rc = LDAP_OTHER, alen; + + attr_merge( NA->e, slap_schema.si_ad_objectClass, NA->ocs, NULL ); + + eid = NA->e->e_id; + + ndb_oc_check( be, NA->ndb, NA->ocs, &myOcs ); + myOcs.no_info[myOcs.no_ninfo++] = ni->ni_opattrs; + nocs = myOcs.no_ninfo; + + oclist = (NdbOcInfo **)ch_calloc( 1, nocs * sizeof(NdbOcInfo *)); + + for ( i=0, j=0; ino_nattrs; + + attrs = (char **)ch_malloc( nattrs * sizeof(char *)); + + k = 0; + ptr = abuf; + for ( i=0; igetTable( oci->no_table.bv_val ); + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) + goto leave; + if ( myop->readTuple( update ? NdbOperation::LM_Exclusive : NdbOperation::LM_CommittedRead )) + goto leave; + if ( myop->equal( EID_COLUMN, eid )) + goto leave; + + for ( j=0; jno_nattrs; j++ ) { + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + attrs[k] = ptr; + *ptr++ = 0; + if ( oci->no_attrs[j]->na_len > 255 ) + *ptr++ = 0; + ptr += oci->no_attrs[j]->na_len + 1; + myop->getValue( oci->no_attrs[j]->na_column, attrs[k++] ); + } + } + if ( NA->txn->execute( update ? NdbTransaction::NoCommit : NdbTransaction::Commit, + update ? NdbOperation::AO_IgnoreError : NdbOperation::AbortOnError, 1) < 0 ) + goto leave; + + /* count results */ + nattrs = 0; + k = 0; + for ( i=0; ino_nattrs; j++ ) { + unsigned char *buf; + int len; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + buf = (unsigned char *)attrs[k++]; + len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + len |= (buf[1] << 8); + } + if ( !len ) + continue; + nattrs++; + } + } + + a = attrs_alloc( nattrs ); + NA->e->e_attrs->a_next = a; + k = 0; + for ( i=0; ino_nattrs; j++ ) { + unsigned char *buf; + struct berval bv, nbv; + if ( oci->no_attrs[j]->na_oi != oci ) + continue; + buf = (unsigned char *)attrs[k++]; + bv.bv_len = buf[0]; + if ( oci->no_attrs[j]->na_len > 255 ) { + /* MedVar */ + bv.bv_len |= (buf[1] << 8); + bv.bv_val = (char *)buf+2; + } else { + bv.bv_val = (char *)buf+1; + } + if ( bv.bv_len == 0 ) + continue; + bv.bv_val[bv.bv_len] = '\0'; + a->a_desc = oci->no_attrs[j]->na_desc; + attr_normalize_one( a->a_desc, &bv, &nbv, NULL ); + a->a_vals = NULL; + a->a_nvals = NULL; + a->a_numvals = 1; + value_add_one( &a->a_vals, &bv ); + if ( !BER_BVISNULL( &nbv )) { + ber_bvarray_add( &a->a_nvals, &nbv ); + } else { + a->a_nvals = a->a_vals; + } + a = a->a_next; + } + } + + rc = 0; +leave: + if ( attrs ) { + ch_free( attrs ); + } + if ( oclist ) { + ch_free( oclist ); + } + + return rc; +} + +static int +ndb_oc_del( + const NdbDictionary::Dictionary *myDict, + NdbTransaction *txn, Uint64 eid, NdbOcInfo *no ) +{ + const NdbDictionary::Table *myTable; + NdbOperation *myop; + int i, rc; + + for ( i=0; ino_nsets; i++ ) { + rc = ndb_oc_del( myDict, txn, eid, no->no_sets[i] ); + if ( rc ) rc; + } + myTable = myDict->getTable( no->no_table.bv_val ); + + myop = txn->getNdbOperation( myTable ); + if ( !myop ) + return LDAP_OTHER; + if ( myop->deleteTuple() ) + return LDAP_OTHER; + if ( myop->equal( EID_COLUMN, eid )) + return LDAP_OTHER; + + return 0; +} + +extern "C" int +ndb_entry_del_data( + 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; + NdbOperation *myop; + 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; itxn, 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 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; inr_num; i++ ) { + if ( myop->equal( i+RDN_COLUMN, rdns->nr_buf[i] )) + return LDAP_OTHER; + } + for ( ; iequal( 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 */ + if ( aoc ) { + char *ptr, buf[sizeof(MedVar)]; + NdbOcs myOcs; + int i; + + ndb_oc_check( be, NA->ndb, aoc->a_nvals, &myOcs ); + ptr = buf+2; + for ( i=0; i= &buf[sizeof(buf)] ) + break; + if ( i ) *ptr++ = ' '; + ptr = lutil_strcopy( ptr, myOcs.no_text[i].bv_val ); + } + 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 +) +{ + struct berval *list, tmp; + char *beg; + int i, num; + + for ( i = 1, beg = str;; i++ ) { + beg = strchr( beg, delim ); + if ( !beg ) + break; + beg++; + } + + num = i; + list = (struct berval *)ch_malloc( (num+1)*sizeof(struct berval)); + + for ( i = 0, beg = str; ibe_private; + const NdbDictionary::Dictionary *myDict = NA->ndb->getDictionary(); + const NdbDictionary::Table *myTable = myDict->getTable( DN2ID_TABLE ); + NdbOperation *myop; + NdbRecAttr *attr1, *attr2; + char idbuf[2*sizeof(ID)]; + char ocbuf[NDB_OC_BUFLEN]; + + if ( matched ) { + BER_BVZERO( matched ); + } + if ( !myTable ) { + return LDAP_OTHER; + } + + myop = NA->txn->getNdbOperation( myTable ); + if ( !myop ) { + return LDAP_OTHER; + } + + if ( myop->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, NA->rdns )) { + return LDAP_OTHER; + } + + attr1 = myop->getValue( EID_COLUMN, idbuf ); + if ( !attr1 ) { + return LDAP_OTHER; + } + + ocbuf[0] = 0; + ocbuf[1] = 0; + if ( !NA->ocs ) { + attr2 = myop->getValue( OCS_COLUMN, ocbuf ); + if ( !attr2 ) { + return LDAP_OTHER; + } + } + + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + + switch( myop->getNdbError().code ) { + case 0: + if ( !attr1->isNULL() && ( NA->e->e_id = attr1->u_64_value() )) { + /* If we didn't care about OCs, or we got them */ + if ( NA->ocs || ocbuf[0] || ocbuf[1] ) { + /* If wanted, return them */ + if ( !NA->ocs ) + NA->ocs = ndb_ref2oclist( ocbuf ); + break; + } + } + /* FALLTHRU */ + case NDB_NO_SUCH_OBJECT: /* no such tuple: look for closest parent */ + if ( matched ) { + NdbOperation *ops[NDB_MAX_RDNS]; + 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; itxn->getNdbOperation( myTable ); + if ( !ops[i] ) + return LDAP_OTHER; + if ( ops[i]->readTuple( NdbOperation::LM_CommittedRead )) + return LDAP_OTHER; + for ( j=0; j<=i; j++ ) { + if ( ops[i]->equal( j+RDN_COLUMN, NA->rdns->nr_buf[j] )) + return LDAP_OTHER; + } + for ( ;jequal( j+RDN_COLUMN, dummy )) + return LDAP_OTHER; + } + } + if ( NA->txn->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError, 1) < 0 ) { + return LDAP_OTHER; + } + for ( --i; i>=0; i-- ) { + if ( ops[i]->getNdbError().code == 0 ) { + for ( j=0; j<=i; j++ ) + matched->bv_len += NA->rdns->nr_buf[j][0]; + matched->bv_len += i; + matched->bv_val = NA->e->e_name.bv_val + + NA->e->e_name.bv_len - matched->bv_len; + 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; + + /* Let caller invoke the roundtrip */ + /* return txn->execute(NoCommit); */ + 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->o_bd, &NA, rw, NULL ); + } + if ( rc == 0 ) { + e.e_name = *ndn; + e.e_nname = *ndn; + rc = ndb_entry_get_data( op->o_bd, &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 ); +} diff --git a/servers/slapd/back-ndb/proto-ndb.h b/servers/slapd/back-ndb/proto-ndb.h new file mode 100644 index 0000000000..9a2e1b8bc5 --- /dev/null +++ b/servers/slapd/back-ndb/proto-ndb.h @@ -0,0 +1,164 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* 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( + BackendDB *be, + NdbArgs *args, + int update ); + +extern int +ndb_entry_put_data( + BackendDB *be, + NdbArgs *args, + int update ); + +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( + BackendDB *be, + 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, + int update, + int *num, + NdbOperation **retop ); + +extern int +ndb_has_children( + NdbArgs *NA, + int *hasChildren ); + +extern struct berval * +ndb_str2bvarray( + char *str, + int len, + char delim ); + +extern struct berval * +ndb_ref2oclist( + const char *ref ); + +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 void +ndb_trans_backoff( int num_retries ); + +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 0000000000..27ff48c993 --- /dev/null +++ b/servers/slapd/back-ndb/search.cpp @@ -0,0 +1,660 @@ +/* search.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include + +#include "lutil.h" + +#include "back-ndb.h" + +static int +ndb_dn2bound( + NdbIndexScanOperation *myop, + NdbRdns *rdns +) +{ + unsigned int i; + + /* Walk thru RDNs */ + for ( i=0; inr_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: + for ( f = f->f_list; f; f=f->f_next ) { + if ( ndb_filter_set( op, ni, f, indexed, scan, sf, bounds )) + return -1; + } + 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 ) { + if ( ai->na_flag & NDB_INFO_INDEX ) { + char *buf, *ptr; + NdbIndexScanOperation::BoundType bt; + int rc; + + 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 { + } + } + } + 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, matched; + 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; + + myTable = myDict->getTable( oci->no_table.bv_val ); + if ( indexed ) { + scan = txn->getNdbIndexScanOperation( INDEX_NAME, DN2ID_TABLE ); + if ( !scan ) + return LDAP_OTHER; + } 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; + 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; + } + } + } + scan->readTuples( NdbOperation::LM_CommittedRead ); + + bounds = 0; + rc = ndb_filter_set( op, ni, op->ors_filter, indexed, scan, &sf, &bounds ); + if ( rc ) + goto leave; + + scanID = scan->getValue( EID_COLUMN, idbuf ); + if ( indexed ) { + scanOC = scan->getValue( OCS_COLUMN, ocbuf ); + for ( i=0; igetValue( 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() == 0 ) { + NdbTransaction *tx2; + if ( op->o_abandon ) + 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; igetValue( 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 ); + for ( i=0; iisNULL() || !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 ); + { +#ifdef notdef /* NDBapi is broken here */ + Ndb::Key_part_ptr keys[2]; + char xbuf[32]; + 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->o_bd, &NA, 0 ); + tx2->close(); + } + ber_bvarray_free( ocs ); + 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; + 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, *tx2; + 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; + + rc = ndb_thread_handle( op, &NA.ndb ); + rdns.nr_num = 0; + + 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; + NA.e = &e; + NA.rdns = &rdns; + NA.ocs = op->ors_scope == LDAP_SCOPE_BASE ? NULL : &matched; + + rs->sr_err = ndb_entry_get_info( op->o_bd, &NA, 0, &matched ); + if ( rs->sr_err ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) + rs->sr_matched = matched.bv_val; + goto leave; + } + + if ( op->ors_scope == LDAP_SCOPE_BASE ) { + e.e_name = op->o_req_dn; + e.e_nname = op->o_req_ndn; + rc = ndb_entry_get_data( op->o_bd, &NA, 0 ); + ber_bvarray_free( NA.ocs ); + 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 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 ); + scan->readTuples( NdbOperation::LM_CommittedRead ); + rc = ndb_dn2bound( scan, &rdns ); + + 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; igetValue( 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() == 0 ) { + if ( op->o_abandon ) + break; + e.e_id = scanID->u_64_value(); + NA.ocs = ndb_ref2oclist( ocbuf ); + for ( i=0; iisNULL() || !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->o_bd, &NA, 0 ); + NA.txn->close(); + ber_bvarray_free( NA.ocs ); + 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; + txn->close(); + send_ldap_result( op, rs ); + return rs->sr_err; +} + +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" ); + } + scan->interpret_exit_last_row(); + 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() == 0 ) + *hasChildren = LDAP_COMPARE_TRUE; + else + *hasChildren = LDAP_COMPARE_FALSE; + 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 ) + /* just count */ ; + + if ( 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 0000000000..a75bee84d9 --- /dev/null +++ b/servers/slapd/back-ndb/tools.cpp @@ -0,0 +1,524 @@ +/* tools.cpp - tools for slap tools */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2008 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software. This work was sponsored by MySQL. + */ + +#include "portable.h" + +#include +#include +#include + +#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; ibe_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 ); + for ( i=0; iisNULL() || !myRdns.nr_buf[i] ) + 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, rc; + + 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; igetValue( 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]; + 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; + } + NA.e = &e; + e.e_name = *dn; + NA.rdns = &rdns; + NA.ocs = NULL; + rc = ndb_entry_get_info( be, &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]; + + 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 ); + + + NA.ndb = myNdb; + NA.ocs = myOcList; + rc = ndb_entry_get_data( be, &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( + BackendDB *be, + 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( be, NA, 0, NULL ); + if ( rc ) { + Attribute *a, tmp = {0}; + if ( !be_issuffix( be, &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( be, 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( be, 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( be, 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; ie->e_id ) { + int j; + free(holes[i].dn.bv_val); + for (j=i;j 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; + + 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; + + 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( be, &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, 0 ); + 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; +} +