diff options
Diffstat (limited to 'servers/slapd/txn.c')
-rw-r--r-- | servers/slapd/txn.c | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/servers/slapd/txn.c b/servers/slapd/txn.c new file mode 100644 index 0000000..785a2d5 --- /dev/null +++ b/servers/slapd/txn.c @@ -0,0 +1,409 @@ +/* txn.c - LDAP Transactions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * 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 + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/unistd.h> + +#include "slap.h" + +#include <lber_pvt.h> +#include <lutil.h> + +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; +} |