1/* modify.cpp - ndb backend modify routine */
2/* $OpenLDAP$ */
3/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 *
5 * Copyright 2008-2021 The OpenLDAP Foundation.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
10 * Public License.
11 *
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
15 */
16/* ACKNOWLEDGEMENTS:
17 * This work was initially developed by Howard Chu for inclusion
18 * in OpenLDAP Software. This work was sponsored by MySQL.
19 */
20
21#include "portable.h"
22
23#include <stdio.h>
24#include <ac/string.h>
25#include <ac/time.h>
26
27#include "back-ndb.h"
28
29/* This is a copy from slapd/mods.c, but with compaction tweaked
30 * to swap values from the tail into deleted slots, to reduce the
31 * overall update traffic.
32 */
33static int
34ndb_modify_delete(
35	Entry	*e,
36	Modification	*mod,
37	int	permissive,
38	const char	**text,
39	char *textbuf, size_t textlen,
40	int *idx )
41{
42	Attribute	*a;
43	MatchingRule 	*mr = mod->sm_desc->ad_type->sat_equality;
44	struct berval *cvals;
45	int		*id2 = NULL;
46	int		i, j, rc = 0, num;
47	unsigned flags;
48	char		dummy = '\0';
49
50	/* For ordered vals, we have no choice but to preserve order */
51	if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL )
52		return modify_delete_vindex( e, mod, permissive, text,
53			textbuf, textlen, idx );
54
55	/*
56	 * If permissive is set, then the non-existence of an
57	 * attribute is not treated as an error.
58	 */
59
60	/* delete the entire attribute */
61	if ( mod->sm_values == NULL ) {
62		rc = attr_delete( &e->e_attrs, mod->sm_desc );
63
64		if( permissive ) {
65			rc = LDAP_SUCCESS;
66		} else if( rc != LDAP_SUCCESS ) {
67			*text = textbuf;
68			snprintf( textbuf, textlen,
69				"modify/delete: %s: no such attribute",
70				mod->sm_desc->ad_cname.bv_val );
71			rc = LDAP_NO_SUCH_ATTRIBUTE;
72		}
73		return rc;
74	}
75
76	/* FIXME: Catch old code that doesn't set sm_numvals.
77	 */
78	if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) {
79		for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ );
80		assert( mod->sm_numvals == i );
81	}
82	if ( !idx ) {
83		id2 = (int *)ch_malloc( mod->sm_numvals * sizeof( int ));
84		idx = id2;
85	}
86
87	if( mr == NULL || !mr->smr_match ) {
88		/* disallow specific attributes from being deleted if
89			no equality rule */
90		*text = textbuf;
91		snprintf( textbuf, textlen,
92			"modify/delete: %s: no equality matching rule",
93			mod->sm_desc->ad_cname.bv_val );
94		rc = LDAP_INAPPROPRIATE_MATCHING;
95		goto return_result;
96	}
97
98	/* delete specific values - find the attribute first */
99	if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) {
100		if( permissive ) {
101			rc = LDAP_SUCCESS;
102			goto return_result;
103		}
104		*text = textbuf;
105		snprintf( textbuf, textlen,
106			"modify/delete: %s: no such attribute",
107			mod->sm_desc->ad_cname.bv_val );
108		rc = LDAP_NO_SUCH_ATTRIBUTE;
109		goto return_result;
110	}
111
112	if ( mod->sm_nvalues ) {
113		flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX
114			| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
115			| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH;
116		cvals = mod->sm_nvalues;
117	} else {
118		flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX;
119		cvals = mod->sm_values;
120	}
121
122	/* Locate values to delete */
123	for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ) {
124		unsigned sort;
125		rc = attr_valfind( a, flags, &cvals[i], &sort, NULL );
126		if ( rc == LDAP_SUCCESS ) {
127			idx[i] = sort;
128		} else if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) {
129			if ( permissive ) {
130				idx[i] = -1;
131				continue;
132			}
133			*text = textbuf;
134			snprintf( textbuf, textlen,
135				"modify/delete: %s: no such value",
136				mod->sm_desc->ad_cname.bv_val );
137			goto return_result;
138		} else {
139			*text = textbuf;
140			snprintf( textbuf, textlen,
141				"modify/delete: %s: matching rule failed",
142				mod->sm_desc->ad_cname.bv_val );
143			goto return_result;
144		}
145	}
146
147	num = a->a_numvals;
148
149	/* Delete the values */
150	for ( i = 0; i < mod->sm_numvals; i++ ) {
151		/* Skip permissive values that weren't found */
152		if ( idx[i] < 0 )
153			continue;
154		/* Skip duplicate delete specs */
155		if ( a->a_vals[idx[i]].bv_val == &dummy )
156			continue;
157		/* delete value and mark it as gone */
158		free( a->a_vals[idx[i]].bv_val );
159		a->a_vals[idx[i]].bv_val = &dummy;
160		if( a->a_nvals != a->a_vals ) {
161			free( a->a_nvals[idx[i]].bv_val );
162			a->a_nvals[idx[i]].bv_val = &dummy;
163		}
164		a->a_numvals--;
165	}
166
167	/* compact array */
168	for ( i=0; i<num; i++ ) {
169		if ( a->a_vals[i].bv_val != &dummy )
170			continue;
171		for ( --num; num > i && a->a_vals[num].bv_val == &dummy; num-- )
172			;
173		a->a_vals[i] = a->a_vals[num];
174		if ( a->a_nvals != a->a_vals )
175			a->a_nvals[i] = a->a_nvals[num];
176	}
177
178	BER_BVZERO( &a->a_vals[num] );
179	if (a->a_nvals != a->a_vals) {
180		BER_BVZERO( &a->a_nvals[num] );
181	}
182
183	/* if no values remain, delete the entire attribute */
184	if ( !a->a_numvals ) {
185		if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) {
186			/* Can never happen */
187			*text = textbuf;
188			snprintf( textbuf, textlen,
189				"modify/delete: %s: no such attribute",
190				mod->sm_desc->ad_cname.bv_val );
191			rc = LDAP_NO_SUCH_ATTRIBUTE;
192		}
193	}
194return_result:
195	if ( id2 )
196		ch_free( id2 );
197	return rc;
198}
199
200int ndb_modify_internal(
201	Operation *op,
202	NdbArgs *NA,
203	const char **text,
204	char *textbuf,
205	size_t textlen )
206{
207	struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private;
208	Modification	*mod;
209	Modifications	*ml;
210	Modifications	*modlist = op->orm_modlist;
211	NdbAttrInfo **modai, *atmp;
212	const NdbDictionary::Dictionary *myDict;
213	const NdbDictionary::Table *myTable;
214	int got_oc = 0, nmods = 0, nai = 0, i, j;
215	int rc, indexed = 0;
216	Attribute *old = NULL;
217
218	Debug( LDAP_DEBUG_TRACE, "ndb_modify_internal: 0x%08lx: %s\n",
219		NA->e->e_id, NA->e->e_dn, 0);
220
221	if ( !acl_check_modlist( op, NA->e, modlist )) {
222		return LDAP_INSUFFICIENT_ACCESS;
223	}
224
225	old = attrs_dup( NA->e->e_attrs );
226
227	for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
228		mod = &ml->sml_mod;
229		nmods++;
230
231		switch ( mod->sm_op ) {
232		case LDAP_MOD_ADD:
233			Debug(LDAP_DEBUG_ARGS,
234				"ndb_modify_internal: add %s\n",
235				mod->sm_desc->ad_cname.bv_val, 0, 0);
236			rc = modify_add_values( NA->e, mod, get_permissiveModify(op),
237				text, textbuf, textlen );
238			if( rc != LDAP_SUCCESS ) {
239				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
240					rc, *text, 0);
241			}
242			break;
243
244		case LDAP_MOD_DELETE:
245			Debug(LDAP_DEBUG_ARGS,
246				"ndb_modify_internal: delete %s\n",
247				mod->sm_desc->ad_cname.bv_val, 0, 0);
248			rc = ndb_modify_delete( NA->e, mod, get_permissiveModify(op),
249				text, textbuf, textlen, NULL );
250			assert( rc != LDAP_TYPE_OR_VALUE_EXISTS );
251			if( rc != LDAP_SUCCESS ) {
252				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
253					rc, *text, 0);
254			}
255			break;
256
257		case LDAP_MOD_REPLACE:
258			Debug(LDAP_DEBUG_ARGS,
259				"ndb_modify_internal: replace %s\n",
260				mod->sm_desc->ad_cname.bv_val, 0, 0);
261			rc = modify_replace_values( NA->e, mod, get_permissiveModify(op),
262				text, textbuf, textlen );
263			if( rc != LDAP_SUCCESS ) {
264				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
265					rc, *text, 0);
266			}
267			break;
268
269		case LDAP_MOD_INCREMENT:
270			Debug(LDAP_DEBUG_ARGS,
271				"ndb_modify_internal: increment %s\n",
272				mod->sm_desc->ad_cname.bv_val, 0, 0);
273			rc = modify_increment_values( NA->e, mod, get_permissiveModify(op),
274				text, textbuf, textlen );
275			if( rc != LDAP_SUCCESS ) {
276				Debug(LDAP_DEBUG_ARGS,
277					"ndb_modify_internal: %d %s\n",
278					rc, *text, 0);
279			}
280			break;
281
282		case SLAP_MOD_SOFTADD:
283			Debug(LDAP_DEBUG_ARGS,
284				"ndb_modify_internal: softadd %s\n",
285				mod->sm_desc->ad_cname.bv_val, 0, 0);
286 			mod->sm_op = LDAP_MOD_ADD;
287
288			rc = modify_add_values( NA->e, mod, get_permissiveModify(op),
289				text, textbuf, textlen );
290
291 			mod->sm_op = SLAP_MOD_SOFTADD;
292
293 			if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) {
294 				rc = LDAP_SUCCESS;
295 			}
296
297			if( rc != LDAP_SUCCESS ) {
298				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
299					rc, *text, 0);
300			}
301 			break;
302
303		case SLAP_MOD_SOFTDEL:
304			Debug(LDAP_DEBUG_ARGS,
305				"ndb_modify_internal: softdel %s\n",
306				mod->sm_desc->ad_cname.bv_val, 0, 0);
307 			mod->sm_op = LDAP_MOD_DELETE;
308
309			rc = modify_delete_values( NA->e, mod, get_permissiveModify(op),
310				text, textbuf, textlen );
311
312 			mod->sm_op = SLAP_MOD_SOFTDEL;
313
314 			if ( rc == LDAP_NO_SUCH_ATTRIBUTE) {
315 				rc = LDAP_SUCCESS;
316 			}
317
318			if( rc != LDAP_SUCCESS ) {
319				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
320					rc, *text, 0);
321			}
322 			break;
323
324		case SLAP_MOD_ADD_IF_NOT_PRESENT:
325			Debug(LDAP_DEBUG_ARGS,
326				"ndb_modify_internal: add_if_not_present %s\n",
327				mod->sm_desc->ad_cname.bv_val, 0, 0);
328			if ( attr_find( NA->e->e_attrs, mod->sm_desc ) ) {
329				rc = LDAP_SUCCESS;
330				break;
331			}
332
333 			mod->sm_op = LDAP_MOD_ADD;
334
335			rc = modify_add_values( NA->e, mod, get_permissiveModify(op),
336				text, textbuf, textlen );
337
338 			mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
339
340			if( rc != LDAP_SUCCESS ) {
341				Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
342					rc, *text, 0);
343			}
344 			break;
345
346		default:
347			Debug(LDAP_DEBUG_ANY, "ndb_modify_internal: invalid op %d\n",
348				mod->sm_op, 0, 0);
349			*text = "Invalid modify operation";
350			rc = LDAP_OTHER;
351			Debug(LDAP_DEBUG_ARGS, "ndb_modify_internal: %d %s\n",
352				rc, *text, 0);
353		}
354
355		if ( rc != LDAP_SUCCESS ) {
356			attrs_free( old );
357			return rc;
358		}
359
360		/* If objectClass was modified, reset the flags */
361		if ( mod->sm_desc == slap_schema.si_ad_objectClass ) {
362			NA->e->e_ocflags = 0;
363			got_oc = 1;
364		}
365	}
366
367	/* check that the entry still obeys the schema */
368	rc = entry_schema_check( op, NA->e, NULL, get_relax(op), 0, NULL,
369		text, textbuf, textlen );
370	if ( rc != LDAP_SUCCESS || op->o_noop ) {
371		if ( rc != LDAP_SUCCESS ) {
372			Debug( LDAP_DEBUG_ANY,
373				"entry failed schema check: %s\n",
374				*text, 0, 0 );
375		}
376		attrs_free( old );
377		return rc;
378	}
379
380	if ( got_oc ) {
381		rc = ndb_entry_put_info( op->o_bd, NA, 1 );
382		if ( rc ) {
383			attrs_free( old );
384			return rc;
385		}
386	}
387
388	/* apply modifications to DB */
389	modai = (NdbAttrInfo **)op->o_tmpalloc( nmods * sizeof(NdbAttrInfo*), op->o_tmpmemctx );
390
391	/* Get the unique list of modified attributes */
392	ldap_pvt_thread_rdwr_rlock( &ni->ni_ai_rwlock );
393	for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
394		/* Already took care of objectclass */
395		if ( ml->sml_desc == slap_schema.si_ad_objectClass )
396			continue;
397		for ( i=0; i<nai; i++ ) {
398			if ( ml->sml_desc->ad_type == modai[i]->na_attr )
399				break;
400		}
401		/* This attr was already updated */
402		if ( i < nai )
403			continue;
404		modai[nai] = ndb_ai_find( ni, ml->sml_desc->ad_type );
405		if ( modai[nai]->na_flag & NDB_INFO_INDEX )
406			indexed++;
407		nai++;
408	}
409	ldap_pvt_thread_rdwr_runlock( &ni->ni_ai_rwlock );
410
411	/* If got_oc, this was already done above */
412	if ( indexed && !got_oc) {
413		rc = ndb_entry_put_info( op->o_bd, NA, 1 );
414		if ( rc ) {
415			attrs_free( old );
416			return rc;
417		}
418	}
419
420	myDict = NA->ndb->getDictionary();
421
422	/* sort modai so that OcInfo's are contiguous */
423	{
424		int j, k;
425		for ( i=0; i<nai; i++ ) {
426			for ( j=i+1; j<nai; j++ ) {
427				if ( modai[i]->na_oi == modai[j]->na_oi )
428					continue;
429				for ( k=j+1; k<nai; k++ ) {
430					if ( modai[i]->na_oi == modai[k]->na_oi ) {
431						atmp = modai[j];
432						modai[j] = modai[k];
433						modai[k] = atmp;
434						break;
435					}
436				}
437				/* there are no more na_oi's that match modai[i] */
438				if ( k == nai ) {
439					i = j;
440				}
441			}
442		}
443	}
444
445	/* One call per table... */
446	for ( i=0; i<nai; i += j ) {
447		atmp = modai[i];
448		for ( j=i+1; j<nai; j++ )
449			if ( atmp->na_oi != modai[j]->na_oi )
450				break;
451		j -= i;
452		myTable = myDict->getTable( atmp->na_oi->no_table.bv_val );
453		if ( !myTable )
454			continue;
455		rc = ndb_oc_attrs( NA->txn, myTable, NA->e, atmp->na_oi, &modai[i], j, old );
456		if ( rc ) break;
457	}
458	attrs_free( old );
459	return rc;
460}
461
462
463int
464ndb_back_modify( Operation *op, SlapReply *rs )
465{
466	struct ndb_info *ni = (struct ndb_info *) op->o_bd->be_private;
467	Entry		e = {0};
468	int		manageDSAit = get_manageDSAit( op );
469	char textbuf[SLAP_TEXT_BUFLEN];
470	size_t textlen = sizeof textbuf;
471
472	int		num_retries = 0;
473
474	NdbArgs NA;
475	NdbRdns rdns;
476	struct berval matched;
477
478	LDAPControl **preread_ctrl = NULL;
479	LDAPControl **postread_ctrl = NULL;
480	LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
481	int num_ctrls = 0;
482
483	Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(ndb_back_modify) ": %s\n",
484		op->o_req_dn.bv_val, 0, 0 );
485
486	ctrls[num_ctrls] = NULL;
487
488	slap_mods_opattrs( op, &op->orm_modlist, 1 );
489
490	e.e_name = op->o_req_dn;
491	e.e_nname = op->o_req_ndn;
492
493	/* Get our NDB handle */
494	rs->sr_err = ndb_thread_handle( op, &NA.ndb );
495	rdns.nr_num = 0;
496	NA.rdns = &rdns;
497	NA.e = &e;
498
499	if( 0 ) {
500retry:	/* transaction retry */
501		NA.txn->close();
502		NA.txn = NULL;
503		if( e.e_attrs ) {
504			attrs_free( e.e_attrs );
505			e.e_attrs = NULL;
506		}
507		Debug(LDAP_DEBUG_TRACE,
508			LDAP_XSTRING(ndb_back_modify) ": retrying...\n", 0, 0, 0);
509		if ( op->o_abandon ) {
510			rs->sr_err = SLAPD_ABANDON;
511			goto return_results;
512		}
513		if ( NA.ocs ) {
514			ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx );
515		}
516		ndb_trans_backoff( ++num_retries );
517	}
518	NA.ocs = NULL;
519
520	/* begin transaction */
521	NA.txn = NA.ndb->startTransaction();
522	rs->sr_text = NULL;
523	if( !NA.txn ) {
524		Debug( LDAP_DEBUG_TRACE,
525			LDAP_XSTRING(ndb_back_modify) ": startTransaction failed: %s (%d)\n",
526			NA.ndb->getNdbError().message, NA.ndb->getNdbError().code, 0 );
527		rs->sr_err = LDAP_OTHER;
528		rs->sr_text = "internal error";
529		goto return_results;
530	}
531
532	/* get entry or ancestor */
533	rs->sr_err = ndb_entry_get_info( op, &NA, 0, &matched );
534	switch( rs->sr_err ) {
535	case 0:
536		break;
537	case LDAP_NO_SUCH_OBJECT:
538		Debug( LDAP_DEBUG_ARGS,
539			"<=- ndb_back_modify: no such object %s\n",
540			op->o_req_dn.bv_val, 0, 0 );
541		rs->sr_matched = matched.bv_val;
542		if (NA.ocs )
543			ndb_check_referral( op, rs, &NA );
544		goto return_results;
545#if 0
546	case DB_LOCK_DEADLOCK:
547	case DB_LOCK_NOTGRANTED:
548		goto retry;
549#endif
550	case LDAP_BUSY:
551		rs->sr_text = "ldap server busy";
552		goto return_results;
553	default:
554		rs->sr_err = LDAP_OTHER;
555		rs->sr_text = "internal error";
556		goto return_results;
557	}
558
559	/* acquire and lock entry */
560	rs->sr_err = ndb_entry_get_data( op, &NA, 1 );
561
562	if ( !manageDSAit && is_entry_referral( &e ) ) {
563		/* entry is a referral, don't allow modify */
564		rs->sr_ref = get_entry_referrals( op, &e );
565
566		Debug( LDAP_DEBUG_TRACE,
567			LDAP_XSTRING(ndb_back_modify) ": entry is referral\n",
568			0, 0, 0 );
569
570		rs->sr_err = LDAP_REFERRAL;
571		rs->sr_matched = e.e_name.bv_val;
572		rs->sr_flags = REP_REF_MUSTBEFREED;
573		goto return_results;
574	}
575
576	if ( get_assert( op ) &&
577		( test_filter( op, &e, (Filter*)get_assertion( op )) != LDAP_COMPARE_TRUE ))
578	{
579		rs->sr_err = LDAP_ASSERTION_FAILED;
580		goto return_results;
581	}
582
583	if( op->o_preread ) {
584		if( preread_ctrl == NULL ) {
585			preread_ctrl = &ctrls[num_ctrls++];
586			ctrls[num_ctrls] = NULL;
587		}
588		if ( slap_read_controls( op, rs, &e,
589			&slap_pre_read_bv, preread_ctrl ) )
590		{
591			Debug( LDAP_DEBUG_TRACE,
592				"<=- " LDAP_XSTRING(ndb_back_modify) ": pre-read "
593				"failed!\n", 0, 0, 0 );
594			if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
595				/* FIXME: is it correct to abort
596				 * operation if control fails? */
597				goto return_results;
598			}
599		}
600	}
601
602	/* Modify the entry */
603	rs->sr_err = ndb_modify_internal( op, &NA, &rs->sr_text, textbuf, textlen );
604
605	if( rs->sr_err != LDAP_SUCCESS ) {
606		Debug( LDAP_DEBUG_TRACE,
607			LDAP_XSTRING(ndb_back_modify) ": modify failed (%d)\n",
608			rs->sr_err, 0, 0 );
609#if 0
610		switch( rs->sr_err ) {
611		case DB_LOCK_DEADLOCK:
612		case DB_LOCK_NOTGRANTED:
613			goto retry;
614		}
615#endif
616		goto return_results;
617	}
618
619	if( op->o_postread ) {
620		if( postread_ctrl == NULL ) {
621			postread_ctrl = &ctrls[num_ctrls++];
622			ctrls[num_ctrls] = NULL;
623		}
624		if( slap_read_controls( op, rs, &e,
625			&slap_post_read_bv, postread_ctrl ) )
626		{
627			Debug( LDAP_DEBUG_TRACE,
628				"<=- " LDAP_XSTRING(ndb_back_modify)
629				": post-read failed!\n", 0, 0, 0 );
630			if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
631				/* FIXME: is it correct to abort
632				 * operation if control fails? */
633				goto return_results;
634			}
635		}
636	}
637
638	if( op->o_noop ) {
639		if (( rs->sr_err=NA.txn->execute( NdbTransaction::Rollback,
640			NdbOperation::AbortOnError, 1 )) != 0 ) {
641			rs->sr_text = "txn_abort (no-op) failed";
642		} else {
643			rs->sr_err = LDAP_X_NO_OPERATION;
644		}
645	} else {
646		if (( rs->sr_err=NA.txn->execute( NdbTransaction::Commit,
647			NdbOperation::AbortOnError, 1 )) != 0 ) {
648			rs->sr_text = "txn_commit failed";
649		} else {
650			rs->sr_err = LDAP_SUCCESS;
651		}
652	}
653
654	if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_X_NO_OPERATION ) {
655		Debug( LDAP_DEBUG_TRACE,
656			LDAP_XSTRING(ndb_back_modify) ": txn_%s failed: %s (%d)\n",
657			op->o_noop ? "abort (no-op)" : "commit",
658			NA.txn->getNdbError().message, NA.txn->getNdbError().code );
659		rs->sr_err = LDAP_OTHER;
660		goto return_results;
661	}
662	NA.txn->close();
663	NA.txn = NULL;
664
665	Debug( LDAP_DEBUG_TRACE,
666		LDAP_XSTRING(ndb_back_modify) ": updated%s id=%08lx dn=\"%s\"\n",
667		op->o_noop ? " (no-op)" : "",
668		e.e_id, op->o_req_dn.bv_val );
669
670	rs->sr_err = LDAP_SUCCESS;
671	rs->sr_text = NULL;
672	if( num_ctrls ) rs->sr_ctrls = ctrls;
673
674return_results:
675	if ( NA.ocs ) {
676		ber_bvarray_free_x( NA.ocs, op->o_tmpmemctx );
677		NA.ocs = NULL;
678	}
679
680	if ( e.e_attrs != NULL ) {
681		attrs_free( e.e_attrs );
682		e.e_attrs = NULL;
683	}
684
685	if( NA.txn != NULL ) {
686		NA.txn->execute( Rollback );
687		NA.txn->close();
688	}
689
690	send_ldap_result( op, rs );
691	slap_graduate_commit_csn( op );
692
693	if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
694		slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
695		slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
696	}
697	if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
698		slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
699		slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
700	}
701
702	rs->sr_text = NULL;
703	return rs->sr_err;
704}
705