/* txn.c - LDAP Transactions */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 1998-2022 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #include "portable.h" #include #include #include #include #include "slap.h" #include #include const struct berval slap_EXOP_TXN_START = BER_BVC(LDAP_EXOP_TXN_START); const struct berval slap_EXOP_TXN_END = BER_BVC(LDAP_EXOP_TXN_END); int txn_start_extop( Operation *op, SlapReply *rs ) { int rc; struct berval *bv; Debug( LDAP_DEBUG_STATS, "%s TXN START\n", op->o_log_prefix ); if( op->ore_reqdata != NULL ) { rs->sr_text = "no request data expected"; return LDAP_PROTOCOL_ERROR; } op->o_bd = op->o_conn->c_authz_backend; if( backend_check_restrictions( op, rs, (struct berval *)&slap_EXOP_TXN_START ) != LDAP_SUCCESS ) { return rs->sr_err; } /* acquire connection lock */ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); if( op->o_conn->c_txn != CONN_TXN_INACTIVE ) { rs->sr_text = "Too many transactions"; rc = LDAP_BUSY; goto done; } assert( op->o_conn->c_txn_backend == NULL ); op->o_conn->c_txn = CONN_TXN_SPECIFY; bv = (struct berval *) ch_malloc( sizeof (struct berval) ); bv->bv_len = 0; bv->bv_val = NULL; rs->sr_rspdata = bv; rc = LDAP_SUCCESS; done: /* release connection lock */ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); return rc; } int txn_spec_ctrl( Operation *op, SlapReply *rs, LDAPControl *ctrl ) { if ( !ctrl->ldctl_iscritical ) { rs->sr_text = "txnSpec control must be marked critical"; return LDAP_PROTOCOL_ERROR; } if( op->o_txnSpec ) { rs->sr_text = "txnSpec control provided multiple times"; return LDAP_PROTOCOL_ERROR; } if ( ctrl->ldctl_value.bv_val == NULL ) { rs->sr_text = "no transaction identifier provided"; return LDAP_PROTOCOL_ERROR; } if ( ctrl->ldctl_value.bv_len != 0 ) { rs->sr_text = "invalid transaction identifier"; return LDAP_TXN_ID_INVALID; } if ( op->o_preread ) { /* temporary limitation */ rs->sr_text = "cannot perform pre-read in transaction"; return LDAP_UNWILLING_TO_PERFORM; } if ( op->o_postread ) { /* temporary limitation */ rs->sr_text = "cannot perform post-read in transaction"; return LDAP_UNWILLING_TO_PERFORM; } op->o_txnSpec = SLAP_CONTROL_CRITICAL; return LDAP_SUCCESS; } typedef struct txn_rctrls { struct txn_rctrls *tr_next; ber_int_t tr_msgid; LDAPControl ** tr_ctrls; } txn_rctrls; static int txn_result( Operation *op, SlapReply *rs ) { if ( rs->sr_ctrls ) { txn_rctrls **t0, *tr; for ( t0 = (txn_rctrls **) &op->o_callback->sc_private; *t0; t0 = &(*t0)->tr_next ) ; tr = op->o_tmpalloc( sizeof( txn_rctrls ), op->o_tmpmemctx ); tr->tr_next = NULL; *t0 = tr; tr->tr_msgid = op->o_msgid; tr->tr_ctrls = ldap_controls_dup( rs->sr_ctrls ); } return rs->sr_err; } static int txn_put_ctrls( Operation *op, BerElement *ber, txn_rctrls *tr ) { txn_rctrls *next; int i; ber_printf( ber, "{" ); for ( ; tr; tr = next ) { next = tr->tr_next; ber_printf( ber, "{it{", tr->tr_msgid, LDAP_TAG_CONTROLS ); for ( i = 0; tr->tr_ctrls[i]; i++ ) ldap_pvt_put_control( tr->tr_ctrls[i], ber ); ber_printf( ber, "}}" ); ldap_controls_free( tr->tr_ctrls ); op->o_tmpfree( tr, op->o_tmpmemctx ); } ber_printf( ber, "}" ); return 0; } int txn_end_extop( Operation *op, SlapReply *rs ) { int rc; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; ber_tag_t tag; ber_len_t len; ber_int_t commit=1; struct berval txnid; Operation *o, *p; Connection *c = op->o_conn; Debug( LDAP_DEBUG_STATS, "%s TXN END\n", op->o_log_prefix ); if( op->ore_reqdata == NULL ) { rs->sr_text = "request data expected"; return LDAP_PROTOCOL_ERROR; } if( op->ore_reqdata->bv_len == 0 ) { rs->sr_text = "empty request data"; return LDAP_PROTOCOL_ERROR; } op->o_bd = c->c_authz_backend; if( backend_check_restrictions( op, rs, (struct berval *)&slap_EXOP_TXN_END ) != LDAP_SUCCESS ) { return rs->sr_err; } ber_init2( ber, op->ore_reqdata, 0 ); tag = ber_scanf( ber, "{" /*}*/ ); if( tag == LBER_ERROR ) { rs->sr_text = "request data decoding error"; return LDAP_PROTOCOL_ERROR; } tag = ber_peek_tag( ber, &len ); if( tag == LBER_BOOLEAN ) { tag = ber_scanf( ber, "b", &commit ); if( tag == LBER_ERROR ) { rs->sr_text = "request data decoding error"; return LDAP_PROTOCOL_ERROR; } } tag = ber_scanf( ber, /*{*/ "m}", &txnid ); if( tag == LBER_ERROR ) { rs->sr_text = "request data decoding error"; return LDAP_PROTOCOL_ERROR; } if( txnid.bv_len ) { rs->sr_text = "invalid transaction identifier"; return LDAP_TXN_ID_INVALID; } /* acquire connection lock */ ldap_pvt_thread_mutex_lock( &c->c_mutex ); if( c->c_txn != CONN_TXN_SPECIFY ) { rs->sr_text = "invalid transaction identifier"; rc = LDAP_TXN_ID_INVALID; goto done; } c->c_txn = CONN_TXN_SETTLE; if( commit ) { slap_callback cb = {0}; OpExtra *txn = NULL; if ( op->o_abandon ) { goto drain; } if( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ) { /* no updates to commit */ rs->sr_text = "no updates to commit"; rc = LDAP_OPERATIONS_ERROR; goto settled; } cb.sc_response = txn_result; LDAP_STAILQ_FOREACH( o, &c->c_txn_ops, o_next ) { o->o_bd = c->c_txn_backend; p = o; if ( !txn ) { rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_BEGIN, &txn ); if ( rc ) { rs->sr_text = "couldn't start DB transaction"; rc = LDAP_OTHER; goto drain; } } else { LDAP_SLIST_INSERT_HEAD( &o->o_extra, txn, oe_next ); } cb.sc_next = o->o_callback; o->o_callback = &cb; { SlapReply rs = {REP_RESULT}; int opidx = slap_req2op( o->o_tag ); assert( opidx != SLAP_OP_LAST ); o->o_threadctx = op->o_threadctx; o->o_tid = op->o_tid; ldap_pvt_thread_mutex_unlock( &c->c_mutex ); rc = (&o->o_bd->bd_info->bi_op_bind)[opidx]( o, &rs ); ldap_pvt_thread_mutex_lock( &c->c_mutex ); } LDAP_SLIST_REMOVE( &o->o_extra, txn, OpExtra, oe_next ); if ( rc ) { struct berval *bv = NULL; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; ber_init_w_nullc( ber, LBER_USE_DER ); ber_printf( ber, "{i", o->o_msgid ); if ( cb.sc_private ) txn_put_ctrls( op, ber, cb.sc_private ); ber_printf( ber, "}" ); ber_flatten( ber, &bv ); ber_free_buf( ber ); rs->sr_rspdata = bv; o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_ABORT, &txn ); goto drain; } } if ( cb.sc_private ) { struct berval *bv = NULL; BerElementBuffer berbuf; BerElement *ber = (BerElement *)&berbuf; ber_init_w_nullc( ber, LBER_USE_DER ); ber_printf( ber, "{" ); txn_put_ctrls( op, ber, cb.sc_private ); ber_printf( ber, "}" ); ber_flatten( ber, &bv ); ber_free_buf( ber ); rs->sr_rspdata = bv; } o = p; rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_COMMIT, &txn ); if ( rc ) { rs->sr_text = "transaction commit failed"; rc = LDAP_OTHER; } } else { rs->sr_text = "transaction aborted"; rc = LDAP_SUCCESS; } drain: /* drain txn ops list */ while (( o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL ) { int freevals = 1; LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next ); LDAP_STAILQ_NEXT( o, o_next ) = NULL; switch ( o->o_tag ) { case LDAP_REQ_ADD: { if ( o->ora_e != NULL ) { OpExtra *oex; OpExtraDB *oexdb = NULL; LDAP_SLIST_FOREACH(oex, &o->o_extra, oe_next) { if ( oex->oe_key == (void *)do_add ) { oexdb = (OpExtraDB *)oex; break; } } if ( oexdb && oexdb->oe_db ) { BackendDB *bd = o->o_bd; o->o_bd = oexdb->oe_db; be_entry_release_w( o, o->ora_e ); o->ora_e = NULL; o->o_bd = bd; } else { entry_free( o->ora_e ); } if ( oexdb ) { o->o_tmpfree( oexdb, o->o_tmpmemctx ); } } freevals = 0; } /* fallthru */ case LDAP_REQ_MODIFY: case LDAP_REQ_MODRDN: if ( o->orr_modlist != NULL ) { slap_mods_free( o->orr_modlist, freevals ); } break; case LDAP_REQ_DELETE: case LDAP_REQ_EXTENDED: break; default: assert( 0 ); } o->o_tmpfree( o->o_req_dn.bv_val, o->o_tmpmemctx ); o->o_tmpfree( o->o_req_ndn.bv_val, o->o_tmpmemctx ); slap_op_free( o, NULL ); } settled: assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ); assert( c->c_txn == CONN_TXN_SETTLE ); c->c_txn = CONN_TXN_INACTIVE; c->c_txn_backend = NULL; done: /* release connection lock */ ldap_pvt_thread_mutex_unlock( &c->c_mutex ); return rc; } int txn_preop( Operation *op, SlapReply *rs ) { /* acquire connection lock */ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { rs->sr_text = "invalid transaction identifier"; rs->sr_err = LDAP_TXN_ID_INVALID; goto txnReturn; } if( op->o_conn->c_txn_backend == NULL ) { op->o_conn->c_txn_backend = op->o_bd; } else if( op->o_conn->c_txn_backend != op->o_bd ) { rs->sr_text = "transaction cannot span multiple database contexts"; rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; goto txnReturn; } if ( !SLAP_TXNS( op->o_bd )) { rs->sr_text = "backend doesn't support transactions"; rs->sr_err = LDAP_UNWILLING_TO_PERFORM; goto txnReturn; } /* insert operation into transaction */ LDAP_STAILQ_REMOVE( &op->o_conn->c_ops, op, Operation, o_next ); LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_txn_ops, op, o_next ); txnReturn: /* release connection lock */ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); if ( op->o_tag != LDAP_REQ_EXTENDED ) send_ldap_result( op, rs ); if ( !rs->sr_err ) rs->sr_err = LDAP_TXN_SPECIFY_OKAY; return rs->sr_err; }