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