1 /* modify.c - mdb backend modify routine */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2000-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 
17 #include "portable.h"
18 
19 #include <stdio.h>
20 #include <ac/string.h>
21 #include <ac/time.h>
22 
23 #include "back-mdb.h"
24 
25 static struct berval scbva[] = {
26 	BER_BVC("glue"),
27 	BER_BVNULL
28 };
29 
30 static void
mdb_modify_idxflags(Operation * op,AttributeDescription * desc,int got_delete,Attribute * newattrs,Attribute * oldattrs)31 mdb_modify_idxflags(
32 	Operation *op,
33 	AttributeDescription *desc,
34 	int got_delete,
35 	Attribute *newattrs,
36 	Attribute *oldattrs )
37 {
38 	struct berval	ix_at;
39 	AttrInfo	*ai;
40 
41 	/* check if modified attribute was indexed
42 	 * but not in case of NOOP... */
43 	ai = mdb_index_mask( op->o_bd, desc, &ix_at );
44 	if ( ai ) {
45 		if ( got_delete ) {
46 			Attribute 	*ap;
47 			struct berval	ix2;
48 
49 			ap = attr_find( oldattrs, desc );
50 			if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL;
51 
52 			/* Find all other attrs that index to same slot */
53 			for ( ap = newattrs; ap; ap = ap->a_next ) {
54 				ai = mdb_index_mask( op->o_bd, ap->a_desc, &ix2 );
55 				if ( ai && ix2.bv_val == ix_at.bv_val )
56 					ap->a_flags |= SLAP_ATTR_IXADD;
57 			}
58 
59 		} else {
60 			Attribute 	*ap;
61 
62 			ap = attr_find( newattrs, desc );
63 			if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD;
64 		}
65 	}
66 }
67 
mdb_modify_internal(Operation * op,MDB_txn * tid,Modifications * modlist,Entry * e,const char ** text,char * textbuf,size_t textlen)68 int mdb_modify_internal(
69 	Operation *op,
70 	MDB_txn *tid,
71 	Modifications *modlist,
72 	Entry *e,
73 	const char **text,
74 	char *textbuf,
75 	size_t textlen )
76 {
77 	int rc, err;
78 	Modification	*mod;
79 	Modifications	*ml;
80 	Attribute	*save_attrs;
81 	Attribute 	*ap;
82 	int			glue_attr_delete = 0;
83 	int			got_delete;
84 
85 	Debug( LDAP_DEBUG_TRACE, "mdb_modify_internal: 0x%08lx: %s\n",
86 		e->e_id, e->e_dn, 0);
87 
88 	if ( !acl_check_modlist( op, e, modlist )) {
89 		return LDAP_INSUFFICIENT_ACCESS;
90 	}
91 
92 	/* save_attrs will be disposed of by caller */
93 	save_attrs = e->e_attrs;
94 	e->e_attrs = attrs_dup( e->e_attrs );
95 
96 	for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
97 		int match;
98 		mod = &ml->sml_mod;
99 		switch( mod->sm_op ) {
100 		case LDAP_MOD_ADD:
101 		case LDAP_MOD_REPLACE:
102 			if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) {
103 				value_match( &match, slap_schema.si_ad_structuralObjectClass,
104 					slap_schema.si_ad_structuralObjectClass->
105 						ad_type->sat_equality,
106 					SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
107 					&mod->sm_values[0], &scbva[0], text );
108 				if ( !match ) glue_attr_delete = 1;
109 			}
110 		}
111 		if ( glue_attr_delete )
112 			break;
113 	}
114 
115 	if ( glue_attr_delete ) {
116 		Attribute	**app = &e->e_attrs;
117 		while ( *app != NULL ) {
118 			if ( !is_at_operational( (*app)->a_desc->ad_type )) {
119 				Attribute *save = *app;
120 				*app = (*app)->a_next;
121 				attr_free( save );
122 				continue;
123 			}
124 			app = &(*app)->a_next;
125 		}
126 	}
127 
128 	for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
129 		mod = &ml->sml_mod;
130 		got_delete = 0;
131 
132 		switch ( mod->sm_op ) {
133 		case LDAP_MOD_ADD:
134 			Debug(LDAP_DEBUG_ARGS,
135 				"mdb_modify_internal: add %s\n",
136 				mod->sm_desc->ad_cname.bv_val, 0, 0);
137 			err = modify_add_values( e, mod, get_permissiveModify(op),
138 				text, textbuf, textlen );
139 			if( err != LDAP_SUCCESS ) {
140 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
141 					err, *text, 0);
142 			}
143 			break;
144 
145 		case LDAP_MOD_DELETE:
146 			if ( glue_attr_delete ) {
147 				err = LDAP_SUCCESS;
148 				break;
149 			}
150 
151 			Debug(LDAP_DEBUG_ARGS,
152 				"mdb_modify_internal: delete %s\n",
153 				mod->sm_desc->ad_cname.bv_val, 0, 0);
154 			err = modify_delete_values( e, mod, get_permissiveModify(op),
155 				text, textbuf, textlen );
156 			if( err != LDAP_SUCCESS ) {
157 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
158 					err, *text, 0);
159 			} else {
160 				got_delete = 1;
161 			}
162 			break;
163 
164 		case LDAP_MOD_REPLACE:
165 			Debug(LDAP_DEBUG_ARGS,
166 				"mdb_modify_internal: replace %s\n",
167 				mod->sm_desc->ad_cname.bv_val, 0, 0);
168 			err = modify_replace_values( e, mod, get_permissiveModify(op),
169 				text, textbuf, textlen );
170 			if( err != LDAP_SUCCESS ) {
171 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
172 					err, *text, 0);
173 			} else {
174 				got_delete = 1;
175 			}
176 			break;
177 
178 		case LDAP_MOD_INCREMENT:
179 			Debug(LDAP_DEBUG_ARGS,
180 				"mdb_modify_internal: increment %s\n",
181 				mod->sm_desc->ad_cname.bv_val, 0, 0);
182 			err = modify_increment_values( e, mod, get_permissiveModify(op),
183 				text, textbuf, textlen );
184 			if( err != LDAP_SUCCESS ) {
185 				Debug(LDAP_DEBUG_ARGS,
186 					"mdb_modify_internal: %d %s\n",
187 					err, *text, 0);
188 			} else {
189 				got_delete = 1;
190 			}
191 			break;
192 
193 		case SLAP_MOD_SOFTADD:
194 			Debug(LDAP_DEBUG_ARGS,
195 				"mdb_modify_internal: softadd %s\n",
196 				mod->sm_desc->ad_cname.bv_val, 0, 0);
197  			/* Avoid problems in index_add_mods()
198  			 * We need to add index if necessary.
199  			 */
200  			mod->sm_op = LDAP_MOD_ADD;
201 
202 			err = modify_add_values( e, mod, get_permissiveModify(op),
203 				text, textbuf, textlen );
204 
205  			mod->sm_op = SLAP_MOD_SOFTADD;
206 
207  			if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
208  				err = LDAP_SUCCESS;
209  			}
210 
211 			if( err != LDAP_SUCCESS ) {
212 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
213 					err, *text, 0);
214 			}
215  			break;
216 
217 		case SLAP_MOD_SOFTDEL:
218 			Debug(LDAP_DEBUG_ARGS,
219 				"mdb_modify_internal: softdel %s\n",
220 				mod->sm_desc->ad_cname.bv_val, 0, 0);
221  			/* Avoid problems in index_delete_mods()
222  			 * We need to add index if necessary.
223  			 */
224  			mod->sm_op = LDAP_MOD_DELETE;
225 
226 			err = modify_delete_values( e, mod, get_permissiveModify(op),
227 				text, textbuf, textlen );
228 
229  			mod->sm_op = SLAP_MOD_SOFTDEL;
230 
231 			if ( err == LDAP_SUCCESS ) {
232 				got_delete = 1;
233 			} else if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
234  				err = LDAP_SUCCESS;
235  			}
236 
237 			if( err != LDAP_SUCCESS ) {
238 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
239 					err, *text, 0);
240 			}
241  			break;
242 
243 		case SLAP_MOD_ADD_IF_NOT_PRESENT:
244 			if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) {
245 				/* skip */
246 				err = LDAP_SUCCESS;
247 				break;
248 			}
249 
250 			Debug(LDAP_DEBUG_ARGS,
251 				"mdb_modify_internal: add_if_not_present %s\n",
252 				mod->sm_desc->ad_cname.bv_val, 0, 0);
253  			/* Avoid problems in index_add_mods()
254  			 * We need to add index if necessary.
255  			 */
256  			mod->sm_op = LDAP_MOD_ADD;
257 
258 			err = modify_add_values( e, mod, get_permissiveModify(op),
259 				text, textbuf, textlen );
260 
261  			mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
262 
263 			if( err != LDAP_SUCCESS ) {
264 				Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
265 					err, *text, 0);
266 			}
267  			break;
268 
269 		default:
270 			Debug(LDAP_DEBUG_ANY, "mdb_modify_internal: invalid op %d\n",
271 				mod->sm_op, 0, 0);
272 			*text = "Invalid modify operation";
273 			err = LDAP_OTHER;
274 			Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
275 				err, *text, 0);
276 		}
277 
278 		if ( err != LDAP_SUCCESS ) {
279 			attrs_free( e->e_attrs );
280 			e->e_attrs = save_attrs;
281 			/* unlock entry, delete from cache */
282 			return err;
283 		}
284 
285 		/* If objectClass was modified, reset the flags */
286 		if ( mod->sm_desc == slap_schema.si_ad_objectClass ) {
287 			e->e_ocflags = 0;
288 		}
289 
290 		if ( glue_attr_delete ) e->e_ocflags = 0;
291 
292 
293 		/* check if modified attribute was indexed
294 		 * but not in case of NOOP... */
295 		if ( !op->o_noop ) {
296 			mdb_modify_idxflags( op, mod->sm_desc, got_delete, e->e_attrs, save_attrs );
297 		}
298 	}
299 
300 	/* check that the entry still obeys the schema */
301 	ap = NULL;
302 	rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap,
303 		text, textbuf, textlen );
304 	if ( rc != LDAP_SUCCESS || op->o_noop ) {
305 		attrs_free( e->e_attrs );
306 		/* clear the indexing flags */
307 		for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
308 			ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL);
309 		}
310 		e->e_attrs = save_attrs;
311 
312 		if ( rc != LDAP_SUCCESS ) {
313 			Debug( LDAP_DEBUG_ANY,
314 				"entry failed schema check: %s\n",
315 				*text, 0, 0 );
316 		}
317 
318 		/* if NOOP then silently revert to saved attrs */
319 		return rc;
320 	}
321 
322 	/* structuralObjectClass modified! */
323 	if ( ap ) {
324 		assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass );
325 		if ( !op->o_noop ) {
326 			mdb_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass,
327 				1, e->e_attrs, save_attrs );
328 		}
329 	}
330 
331 	/* update the indices of the modified attributes */
332 
333 	/* start with deleting the old index entries */
334 	for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
335 		if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
336 			struct berval *vals;
337 			Attribute *a2;
338 			ap->a_flags &= ~SLAP_ATTR_IXDEL;
339 			a2 = attr_find( e->e_attrs, ap->a_desc );
340 			if ( a2 ) {
341 				/* need to detect which values were deleted */
342 				int i, j;
343 				/* let add know there were deletes */
344 				if ( a2->a_flags & SLAP_ATTR_IXADD )
345 					a2->a_flags |= SLAP_ATTR_IXDEL;
346 				vals = op->o_tmpalloc( (ap->a_numvals + 1) *
347 					sizeof(struct berval), op->o_tmpmemctx );
348 				j = 0;
349 				for ( i=0; i < ap->a_numvals; i++ ) {
350 					rc = attr_valfind( a2, SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
351 						&ap->a_nvals[i], NULL, op->o_tmpmemctx );
352 					/* Save deleted values */
353 					if ( rc == LDAP_NO_SUCH_ATTRIBUTE )
354 						vals[j++] = ap->a_nvals[i];
355 				}
356 				BER_BVZERO(vals+j);
357 			} else {
358 				/* attribute was completely deleted */
359 				vals = ap->a_nvals;
360 			}
361 			rc = 0;
362 			if ( !BER_BVISNULL( vals )) {
363 				rc = mdb_index_values( op, tid, ap->a_desc,
364 					vals, e->e_id, SLAP_INDEX_DELETE_OP );
365 				if ( rc != LDAP_SUCCESS ) {
366 					Debug( LDAP_DEBUG_ANY,
367 						"%s: attribute \"%s\" index delete failure\n",
368 						op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 );
369 					attrs_free( e->e_attrs );
370 					e->e_attrs = save_attrs;
371 				}
372 			}
373 			if ( vals != ap->a_nvals )
374 				op->o_tmpfree( vals, op->o_tmpmemctx );
375 			if ( rc ) return rc;
376 		}
377 	}
378 
379 	/* add the new index entries */
380 	for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) {
381 		if (ap->a_flags & SLAP_ATTR_IXADD) {
382 			ap->a_flags &= ~SLAP_ATTR_IXADD;
383 			if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
384 				/* if any values were deleted, we must readd index
385 				 * for all remaining values.
386 				 */
387 				ap->a_flags &= ~SLAP_ATTR_IXDEL;
388 				rc = mdb_index_values( op, tid, ap->a_desc,
389 					ap->a_nvals,
390 					e->e_id, SLAP_INDEX_ADD_OP );
391 			} else {
392 				int found = 0;
393 				/* if this was only an add, we only need to index
394 				 * the added values.
395 				 */
396 				for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
397 					struct berval *vals;
398 					if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals )
399 						continue;
400 					found = 1;
401 					switch( ml->sml_op ) {
402 					case LDAP_MOD_ADD:
403 					case LDAP_MOD_REPLACE:
404 					case LDAP_MOD_INCREMENT:
405 					case SLAP_MOD_SOFTADD:
406 					case SLAP_MOD_ADD_IF_NOT_PRESENT:
407 						if ( ml->sml_op == LDAP_MOD_INCREMENT )
408 							vals = ap->a_nvals;
409 						else if ( ml->sml_nvalues )
410 							vals = ml->sml_nvalues;
411 						else
412 							vals = ml->sml_values;
413 						rc = mdb_index_values( op, tid, ap->a_desc,
414 							vals, e->e_id, SLAP_INDEX_ADD_OP );
415 						break;
416 					}
417 					if ( rc )
418 						break;
419 				}
420 				/* This attr was affected by a modify of a subtype, so
421 				 * there was no direct match in the modlist. Just readd
422 				 * all of its values.
423 				 */
424 				if ( !found ) {
425 					rc = mdb_index_values( op, tid, ap->a_desc,
426 						ap->a_nvals,
427 						e->e_id, SLAP_INDEX_ADD_OP );
428 				}
429 			}
430 			if ( rc != LDAP_SUCCESS ) {
431 				Debug( LDAP_DEBUG_ANY,
432 				       "%s: attribute \"%s\" index add failure\n",
433 					op->o_log_prefix, ap->a_desc->ad_cname.bv_val, 0 );
434 				attrs_free( e->e_attrs );
435 				e->e_attrs = save_attrs;
436 				return rc;
437 			}
438 		}
439 	}
440 
441 	return rc;
442 }
443 
444 
445 int
mdb_modify(Operation * op,SlapReply * rs)446 mdb_modify( Operation *op, SlapReply *rs )
447 {
448 	struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
449 	Entry		*e = NULL;
450 	int		manageDSAit = get_manageDSAit( op );
451 	char textbuf[SLAP_TEXT_BUFLEN];
452 	size_t textlen = sizeof textbuf;
453 	MDB_txn	*txn = NULL;
454 	mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
455 	Entry		dummy = {0};
456 
457 	LDAPControl **preread_ctrl = NULL;
458 	LDAPControl **postread_ctrl = NULL;
459 	LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
460 	int num_ctrls = 0;
461 	int numads = mdb->mi_numads;
462 
463 #ifdef LDAP_X_TXN
464 	int settle = 0;
465 #endif
466 
467 	Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(mdb_modify) ": %s\n",
468 		op->o_req_dn.bv_val, 0, 0 );
469 
470 #ifdef LDAP_X_TXN
471 	if( op->o_txnSpec ) {
472 		/* acquire connection lock */
473 		ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
474 		if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
475 			rs->sr_text = "invalid transaction identifier";
476 			rs->sr_err = LDAP_X_TXN_ID_INVALID;
477 			goto txnReturn;
478 		} else if( op->o_conn->c_txn == CONN_TXN_SETTLE ) {
479 			settle=1;
480 			goto txnReturn;
481 		}
482 
483 		if( op->o_conn->c_txn_backend == NULL ) {
484 			op->o_conn->c_txn_backend = op->o_bd;
485 
486 		} else if( op->o_conn->c_txn_backend != op->o_bd ) {
487 			rs->sr_text = "transaction cannot span multiple database contexts";
488 			rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
489 			goto txnReturn;
490 		}
491 
492 		/* insert operation into transaction */
493 
494 		rs->sr_text = "transaction specified";
495 		rs->sr_err = LDAP_X_TXN_SPECIFY_OKAY;
496 
497 txnReturn:
498 		/* release connection lock */
499 		ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
500 
501 		if( !settle ) {
502 			send_ldap_result( op, rs );
503 			return rs->sr_err;
504 		}
505 	}
506 #endif
507 
508 	ctrls[num_ctrls] = NULL;
509 
510 	/* begin transaction */
511 	rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi );
512 	rs->sr_text = NULL;
513 	if( rs->sr_err != 0 ) {
514 		Debug( LDAP_DEBUG_TRACE,
515 			LDAP_XSTRING(mdb_modify) ": txn_begin failed: "
516 			"%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err, 0 );
517 		rs->sr_err = LDAP_OTHER;
518 		rs->sr_text = "internal error";
519 		goto return_results;
520 	}
521 	txn = moi->moi_txn;
522 
523 	/* Don't touch the opattrs, if this is a contextCSN update
524 	 * initiated from updatedn */
525 	if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next ||
526 		 op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) {
527 
528 		slap_mods_opattrs( op, &op->orm_modlist, 1 );
529 	}
530 
531 	/* get entry or ancestor */
532 	rs->sr_err = mdb_dn2entry( op, txn, NULL, &op->o_req_ndn, &e, NULL, 1 );
533 
534 	if ( rs->sr_err != 0 ) {
535 		Debug( LDAP_DEBUG_TRACE,
536 			LDAP_XSTRING(mdb_modify) ": dn2entry failed (%d)\n",
537 			rs->sr_err, 0, 0 );
538 		switch( rs->sr_err ) {
539 		case MDB_NOTFOUND:
540 			break;
541 		case LDAP_BUSY:
542 			rs->sr_text = "ldap server busy";
543 			goto return_results;
544 		default:
545 			rs->sr_err = LDAP_OTHER;
546 			rs->sr_text = "internal error";
547 			goto return_results;
548 		}
549 	}
550 
551 	/* acquire and lock entry */
552 	/* FIXME: dn2entry() should return non-glue entry */
553 	if (( rs->sr_err == MDB_NOTFOUND ) ||
554 		( !manageDSAit && e && is_entry_glue( e )))
555 	{
556 		if ( e != NULL ) {
557 			rs->sr_matched = ch_strdup( e->e_dn );
558 			if ( is_entry_referral( e )) {
559 				BerVarray ref = get_entry_referrals( op, e );
560 				rs->sr_ref = referral_rewrite( ref, &e->e_name,
561 					&op->o_req_dn, LDAP_SCOPE_DEFAULT );
562 				ber_bvarray_free( ref );
563 			} else {
564 				rs->sr_ref = NULL;
565 			}
566 			mdb_entry_return( op, e );
567 			e = NULL;
568 
569 		} else {
570 			rs->sr_ref = referral_rewrite( default_referral, NULL,
571 				&op->o_req_dn, LDAP_SCOPE_DEFAULT );
572 		}
573 
574 		rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
575 		rs->sr_err = LDAP_REFERRAL;
576 		send_ldap_result( op, rs );
577 		goto done;
578 	}
579 
580 	if ( !manageDSAit && is_entry_referral( e ) ) {
581 		/* entry is a referral, don't allow modify */
582 		rs->sr_ref = get_entry_referrals( op, e );
583 
584 		Debug( LDAP_DEBUG_TRACE,
585 			LDAP_XSTRING(mdb_modify) ": entry is referral\n",
586 			0, 0, 0 );
587 
588 		rs->sr_err = LDAP_REFERRAL;
589 		rs->sr_matched = e->e_name.bv_val;
590 		rs->sr_flags = REP_REF_MUSTBEFREED;
591 		send_ldap_result( op, rs );
592 		rs->sr_matched = NULL;
593 		goto done;
594 	}
595 
596 	if ( get_assert( op ) &&
597 		( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
598 	{
599 		rs->sr_err = LDAP_ASSERTION_FAILED;
600 		goto return_results;
601 	}
602 
603 	if( op->o_preread ) {
604 		if( preread_ctrl == NULL ) {
605 			preread_ctrl = &ctrls[num_ctrls++];
606 			ctrls[num_ctrls] = NULL;
607 		}
608 		if ( slap_read_controls( op, rs, e,
609 			&slap_pre_read_bv, preread_ctrl ) )
610 		{
611 			Debug( LDAP_DEBUG_TRACE,
612 				"<=- " LDAP_XSTRING(mdb_modify) ": pre-read "
613 				"failed!\n", 0, 0, 0 );
614 			if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
615 				/* FIXME: is it correct to abort
616 				 * operation if control fails? */
617 				goto return_results;
618 			}
619 		}
620 	}
621 
622 	/* Modify the entry */
623 	dummy = *e;
624 	rs->sr_err = mdb_modify_internal( op, txn, op->orm_modlist,
625 		&dummy, &rs->sr_text, textbuf, textlen );
626 
627 	if( rs->sr_err != LDAP_SUCCESS ) {
628 		Debug( LDAP_DEBUG_TRACE,
629 			LDAP_XSTRING(mdb_modify) ": modify failed (%d)\n",
630 			rs->sr_err, 0, 0 );
631 		/* Only free attrs if they were dup'd.  */
632 		if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
633 		goto return_results;
634 	}
635 
636 	/* change the entry itself */
637 	rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy );
638 	if ( rs->sr_err != 0 ) {
639 		Debug( LDAP_DEBUG_TRACE,
640 			LDAP_XSTRING(mdb_modify) ": id2entry update failed " "(%d)\n",
641 			rs->sr_err, 0, 0 );
642 		rs->sr_text = "entry update failed";
643 		goto return_results;
644 	}
645 
646 	if( op->o_postread ) {
647 		if( postread_ctrl == NULL ) {
648 			postread_ctrl = &ctrls[num_ctrls++];
649 			ctrls[num_ctrls] = NULL;
650 		}
651 		if( slap_read_controls( op, rs, &dummy,
652 			&slap_post_read_bv, postread_ctrl ) )
653 		{
654 			Debug( LDAP_DEBUG_TRACE,
655 				"<=- " LDAP_XSTRING(mdb_modify)
656 				": post-read failed!\n", 0, 0, 0 );
657 			if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
658 				/* FIXME: is it correct to abort
659 				 * operation if control fails? */
660 				goto return_results;
661 			}
662 		}
663 	}
664 
665 	/* Only free attrs if they were dup'd.  */
666 	if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
667 	if( moi == &opinfo ) {
668 		LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
669 		opinfo.moi_oe.oe_key = NULL;
670 		if( op->o_noop ) {
671 			mdb->mi_numads = numads;
672 			mdb_txn_abort( txn );
673 			rs->sr_err = LDAP_X_NO_OPERATION;
674 			txn = NULL;
675 			goto return_results;
676 		} else {
677 			rs->sr_err = mdb_txn_commit( txn );
678 			if ( rs->sr_err )
679 				mdb->mi_numads = numads;
680 			txn = NULL;
681 		}
682 	}
683 
684 	if( rs->sr_err != 0 ) {
685 		Debug( LDAP_DEBUG_ANY,
686 			LDAP_XSTRING(mdb_modify) ": txn_%s failed: %s (%d)\n",
687 			op->o_noop ? "abort (no-op)" : "commit",
688 			mdb_strerror(rs->sr_err), rs->sr_err );
689 		rs->sr_err = LDAP_OTHER;
690 		rs->sr_text = "commit failed";
691 
692 		goto return_results;
693 	}
694 
695 	Debug( LDAP_DEBUG_TRACE,
696 		LDAP_XSTRING(mdb_modify) ": updated%s id=%08lx dn=\"%s\"\n",
697 		op->o_noop ? " (no-op)" : "",
698 		dummy.e_id, op->o_req_dn.bv_val );
699 
700 	rs->sr_err = LDAP_SUCCESS;
701 	rs->sr_text = NULL;
702 	if( num_ctrls ) rs->sr_ctrls = ctrls;
703 
704 return_results:
705 	if( dummy.e_attrs ) {
706 		attrs_free( dummy.e_attrs );
707 	}
708 	send_ldap_result( op, rs );
709 
710 #if 0
711 	if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) {
712 		TXN_CHECKPOINT( mdb->bi_dbenv,
713 			mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 );
714 	}
715 #endif
716 
717 done:
718 	slap_graduate_commit_csn( op );
719 
720 	if( moi == &opinfo ) {
721 		if( txn != NULL ) {
722 			mdb->mi_numads = numads;
723 			mdb_txn_abort( txn );
724 		}
725 		if ( opinfo.moi_oe.oe_key ) {
726 			LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
727 		}
728 	} else {
729 		moi->moi_ref--;
730 	}
731 
732 	if( e != NULL ) {
733 		mdb_entry_return( op, e );
734 	}
735 
736 	if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
737 		slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
738 		slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
739 	}
740 	if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
741 		slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
742 		slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
743 	}
744 
745 	rs->sr_text = NULL;
746 
747 	return rs->sr_err;
748 }
749