txn.c revision 1.3
1/* $NetBSD: txn.c,v 1.3 2021/08/14 16:14:58 christos Exp $ */ 2 3/* txn.c - LDAP Transactions */ 4/* $OpenLDAP$ */ 5/* This work is part of OpenLDAP Software <http://www.openldap.org/>. 6 * 7 * Copyright 1998-2021 The OpenLDAP Foundation. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted only as authorized by the OpenLDAP 12 * Public License. 13 * 14 * A copy of this license is available in the file LICENSE in the 15 * top-level directory of the distribution or, alternatively, at 16 * <http://www.OpenLDAP.org/license.html>. 17 */ 18 19#include <sys/cdefs.h> 20__RCSID("$NetBSD: txn.c,v 1.3 2021/08/14 16:14:58 christos Exp $"); 21 22#include "portable.h" 23 24#include <stdio.h> 25 26#include <ac/socket.h> 27#include <ac/string.h> 28#include <ac/unistd.h> 29 30#include "slap.h" 31 32#include <lber_pvt.h> 33#include <lutil.h> 34 35const struct berval slap_EXOP_TXN_START = BER_BVC(LDAP_EXOP_TXN_START); 36const struct berval slap_EXOP_TXN_END = BER_BVC(LDAP_EXOP_TXN_END); 37 38int txn_start_extop( 39 Operation *op, SlapReply *rs ) 40{ 41 int rc; 42 struct berval *bv; 43 44 Debug( LDAP_DEBUG_STATS, "%s TXN START\n", 45 op->o_log_prefix ); 46 47 if( op->ore_reqdata != NULL ) { 48 rs->sr_text = "no request data expected"; 49 return LDAP_PROTOCOL_ERROR; 50 } 51 52 op->o_bd = op->o_conn->c_authz_backend; 53 if( backend_check_restrictions( op, rs, 54 (struct berval *)&slap_EXOP_TXN_START ) != LDAP_SUCCESS ) 55 { 56 return rs->sr_err; 57 } 58 59 /* acquire connection lock */ 60 ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); 61 62 if( op->o_conn->c_txn != CONN_TXN_INACTIVE ) { 63 rs->sr_text = "Too many transactions"; 64 rc = LDAP_BUSY; 65 goto done; 66 } 67 68 assert( op->o_conn->c_txn_backend == NULL ); 69 op->o_conn->c_txn = CONN_TXN_SPECIFY; 70 71 bv = (struct berval *) ch_malloc( sizeof (struct berval) ); 72 bv->bv_len = 0; 73 bv->bv_val = NULL; 74 75 rs->sr_rspdata = bv; 76 rc = LDAP_SUCCESS; 77 78done: 79 /* release connection lock */ 80 ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); 81 return rc; 82} 83 84int txn_spec_ctrl( 85 Operation *op, SlapReply *rs, LDAPControl *ctrl ) 86{ 87 if ( !ctrl->ldctl_iscritical ) { 88 rs->sr_text = "txnSpec control must be marked critical"; 89 return LDAP_PROTOCOL_ERROR; 90 } 91 if( op->o_txnSpec ) { 92 rs->sr_text = "txnSpec control provided multiple times"; 93 return LDAP_PROTOCOL_ERROR; 94 } 95 96 if ( ctrl->ldctl_value.bv_val == NULL ) { 97 rs->sr_text = "no transaction identifier provided"; 98 return LDAP_PROTOCOL_ERROR; 99 } 100 if ( ctrl->ldctl_value.bv_len != 0 ) { 101 rs->sr_text = "invalid transaction identifier"; 102 return LDAP_TXN_ID_INVALID; 103 } 104 105 if ( op->o_preread ) { /* temporary limitation */ 106 rs->sr_text = "cannot perform pre-read in transaction"; 107 return LDAP_UNWILLING_TO_PERFORM; 108 } 109 if ( op->o_postread ) { /* temporary limitation */ 110 rs->sr_text = "cannot perform post-read in transaction"; 111 return LDAP_UNWILLING_TO_PERFORM; 112 } 113 114 op->o_txnSpec = SLAP_CONTROL_CRITICAL; 115 return LDAP_SUCCESS; 116} 117 118typedef struct txn_rctrls { 119 struct txn_rctrls *tr_next; 120 ber_int_t tr_msgid; 121 LDAPControl ** tr_ctrls; 122} txn_rctrls; 123 124static int txn_result( Operation *op, SlapReply *rs ) 125{ 126 if ( rs->sr_ctrls ) { 127 txn_rctrls **t0, *tr; 128 for ( t0 = (txn_rctrls **) &op->o_callback->sc_private; *t0; 129 t0 = &(*t0)->tr_next ) 130 ; 131 tr = op->o_tmpalloc( sizeof( txn_rctrls ), op->o_tmpmemctx ); 132 tr->tr_next = NULL; 133 *t0 = tr; 134 tr->tr_msgid = op->o_msgid; 135 tr->tr_ctrls = ldap_controls_dup( rs->sr_ctrls ); 136 } 137 return rs->sr_err; 138} 139 140static int txn_put_ctrls( Operation *op, BerElement *ber, txn_rctrls *tr ) 141{ 142 txn_rctrls *next; 143 int i; 144 ber_printf( ber, "{" ); 145 for ( ; tr; tr = next ) { 146 next = tr->tr_next; 147 ber_printf( ber, "{it{", tr->tr_msgid, LDAP_TAG_CONTROLS ); 148 for ( i = 0; tr->tr_ctrls[i]; i++ ) 149 ldap_pvt_put_control( tr->tr_ctrls[i], ber ); 150 ber_printf( ber, "}}" ); 151 ldap_controls_free( tr->tr_ctrls ); 152 op->o_tmpfree( tr, op->o_tmpmemctx ); 153 } 154 ber_printf( ber, "}" ); 155 return 0; 156} 157 158int txn_end_extop( 159 Operation *op, SlapReply *rs ) 160{ 161 int rc; 162 BerElementBuffer berbuf; 163 BerElement *ber = (BerElement *)&berbuf; 164 ber_tag_t tag; 165 ber_len_t len; 166 ber_int_t commit=1; 167 struct berval txnid; 168 Operation *o, *p; 169 Connection *c = op->o_conn; 170 171 Debug( LDAP_DEBUG_STATS, "%s TXN END\n", 172 op->o_log_prefix ); 173 174 if( op->ore_reqdata == NULL ) { 175 rs->sr_text = "request data expected"; 176 return LDAP_PROTOCOL_ERROR; 177 } 178 if( op->ore_reqdata->bv_len == 0 ) { 179 rs->sr_text = "empty request data"; 180 return LDAP_PROTOCOL_ERROR; 181 } 182 183 op->o_bd = c->c_authz_backend; 184 if( backend_check_restrictions( op, rs, 185 (struct berval *)&slap_EXOP_TXN_END ) != LDAP_SUCCESS ) 186 { 187 return rs->sr_err; 188 } 189 190 ber_init2( ber, op->ore_reqdata, 0 ); 191 192 tag = ber_scanf( ber, "{" /*}*/ ); 193 if( tag == LBER_ERROR ) { 194 rs->sr_text = "request data decoding error"; 195 return LDAP_PROTOCOL_ERROR; 196 } 197 198 tag = ber_peek_tag( ber, &len ); 199 if( tag == LBER_BOOLEAN ) { 200 tag = ber_scanf( ber, "b", &commit ); 201 if( tag == LBER_ERROR ) { 202 rs->sr_text = "request data decoding error"; 203 return LDAP_PROTOCOL_ERROR; 204 } 205 } 206 207 tag = ber_scanf( ber, /*{*/ "m}", &txnid ); 208 if( tag == LBER_ERROR ) { 209 rs->sr_text = "request data decoding error"; 210 return LDAP_PROTOCOL_ERROR; 211 } 212 213 if( txnid.bv_len ) { 214 rs->sr_text = "invalid transaction identifier"; 215 return LDAP_TXN_ID_INVALID; 216 } 217 218 /* acquire connection lock */ 219 ldap_pvt_thread_mutex_lock( &c->c_mutex ); 220 221 if( c->c_txn != CONN_TXN_SPECIFY ) { 222 rs->sr_text = "invalid transaction identifier"; 223 rc = LDAP_TXN_ID_INVALID; 224 goto done; 225 } 226 c->c_txn = CONN_TXN_SETTLE; 227 228 if( commit ) { 229 slap_callback cb = {0}; 230 OpExtra *txn = NULL; 231 if ( op->o_abandon ) { 232 goto drain; 233 } 234 235 if( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ) { 236 /* no updates to commit */ 237 rs->sr_text = "no updates to commit"; 238 rc = LDAP_OPERATIONS_ERROR; 239 goto settled; 240 } 241 242 cb.sc_response = txn_result; 243 LDAP_STAILQ_FOREACH( o, &c->c_txn_ops, o_next ) { 244 o->o_bd = c->c_txn_backend; 245 p = o; 246 if ( !txn ) { 247 rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_BEGIN, &txn ); 248 if ( rc ) { 249 rs->sr_text = "couldn't start DB transaction"; 250 rc = LDAP_OTHER; 251 goto drain; 252 } 253 } else { 254 LDAP_SLIST_INSERT_HEAD( &o->o_extra, txn, oe_next ); 255 } 256 cb.sc_next = o->o_callback; 257 o->o_callback = &cb; 258 { 259 SlapReply rs = {REP_RESULT}; 260 int opidx = slap_req2op( o->o_tag ); 261 assert( opidx != SLAP_OP_LAST ); 262 o->o_threadctx = op->o_threadctx; 263 o->o_tid = op->o_tid; 264 ldap_pvt_thread_mutex_unlock( &c->c_mutex ); 265 rc = (&o->o_bd->bd_info->bi_op_bind)[opidx]( o, &rs ); 266 ldap_pvt_thread_mutex_lock( &c->c_mutex ); 267 } 268 if ( rc ) { 269 struct berval *bv = NULL; 270 BerElementBuffer berbuf; 271 BerElement *ber = (BerElement *)&berbuf; 272 273 ber_init_w_nullc( ber, LBER_USE_DER ); 274 ber_printf( ber, "{i", o->o_msgid ); 275 if ( cb.sc_private ) 276 txn_put_ctrls( op, ber, cb.sc_private ); 277 ber_printf( ber, "}" ); 278 ber_flatten( ber, &bv ); 279 ber_free_buf( ber ); 280 rs->sr_rspdata = bv; 281 o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_ABORT, &txn ); 282 goto drain; 283 } 284 } 285 if ( cb.sc_private ) { 286 struct berval *bv = NULL; 287 BerElementBuffer berbuf; 288 BerElement *ber = (BerElement *)&berbuf; 289 290 ber_init_w_nullc( ber, LBER_USE_DER ); 291 ber_printf( ber, "{" ); 292 txn_put_ctrls( op, ber, cb.sc_private ); 293 ber_printf( ber, "}" ); 294 ber_flatten( ber, &bv ); 295 ber_free_buf( ber ); 296 rs->sr_rspdata = bv; 297 } 298 o = p; 299 rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_COMMIT, &txn ); 300 if ( rc ) { 301 rs->sr_text = "transaction commit failed"; 302 rc = LDAP_OTHER; 303 } 304 } else { 305 rs->sr_text = "transaction aborted"; 306 rc = LDAP_SUCCESS; 307 } 308 309drain: 310 /* drain txn ops list */ 311 while (( o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL ) { 312 LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next ); 313 LDAP_STAILQ_NEXT( o, o_next ) = NULL; 314 slap_op_free( o, NULL ); 315 } 316 317settled: 318 assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ); 319 assert( c->c_txn == CONN_TXN_SETTLE ); 320 c->c_txn = CONN_TXN_INACTIVE; 321 c->c_txn_backend = NULL; 322 323done: 324 /* release connection lock */ 325 ldap_pvt_thread_mutex_unlock( &c->c_mutex ); 326 327 return rc; 328} 329 330int txn_preop( Operation *op, SlapReply *rs ) 331{ 332 /* acquire connection lock */ 333 ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); 334 if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) { 335 rs->sr_text = "invalid transaction identifier"; 336 rs->sr_err = LDAP_TXN_ID_INVALID; 337 goto txnReturn; 338 } 339 340 if( op->o_conn->c_txn_backend == NULL ) { 341 op->o_conn->c_txn_backend = op->o_bd; 342 343 } else if( op->o_conn->c_txn_backend != op->o_bd ) { 344 rs->sr_text = "transaction cannot span multiple database contexts"; 345 rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS; 346 goto txnReturn; 347 } 348 349 if ( !SLAP_TXNS( op->o_bd )) { 350 rs->sr_text = "backend doesn't support transactions"; 351 rs->sr_err = LDAP_UNWILLING_TO_PERFORM; 352 goto txnReturn; 353 } 354 355 /* insert operation into transaction */ 356 LDAP_STAILQ_REMOVE( &op->o_conn->c_ops, op, Operation, o_next ); 357 LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_txn_ops, op, o_next ); 358 359txnReturn: 360 /* release connection lock */ 361 ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); 362 363 if ( op->o_tag != LDAP_REQ_EXTENDED ) 364 send_ldap_result( op, rs ); 365 if ( !rs->sr_err ) 366 rs->sr_err = LDAP_TXN_SPECIFY_OKAY; 367 return rs->sr_err; 368} 369