1/*	$NetBSD$	*/
2
3/* seqmod.c - sequenced modifies */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 2004-2010 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17/* ACKNOWLEDGEMENTS:
18 * This work was initially developed by Howard Chu for inclusion in
19 * OpenLDAP Software.
20 */
21
22#include "portable.h"
23
24#ifdef SLAPD_OVER_SEQMOD
25
26#include "slap.h"
27#include "config.h"
28
29/* This overlay serializes concurrent attempts to modify a single entry */
30
31typedef struct modtarget {
32	struct modtarget *mt_next;
33	struct modtarget *mt_tail;
34	Operation *mt_op;
35} modtarget;
36
37typedef struct seqmod_info {
38	Avlnode		*sm_mods;	/* entries being modified */
39	ldap_pvt_thread_mutex_t	sm_mutex;
40} seqmod_info;
41
42static int
43sm_avl_cmp( const void *c1, const void *c2 )
44{
45	const modtarget *m1, *m2;
46	int rc;
47
48	m1 = c1; m2 = c2;
49	rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len;
50
51	if ( rc ) return rc;
52	return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn );
53}
54
55static int
56seqmod_op_cleanup( Operation *op, SlapReply *rs )
57{
58	slap_callback *sc = op->o_callback;
59	seqmod_info *sm = sc->sc_private;
60	modtarget *mt, mtdummy;
61	Avlnode	 *av;
62
63	mtdummy.mt_op = op;
64	/* This op is done, remove it */
65	ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
66	av = avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp );
67	assert(av != NULL);
68
69	mt = av->avl_data;
70
71	/* If there are more, promote the next one */
72	if ( mt->mt_next ) {
73		av->avl_data = mt->mt_next;
74		mt->mt_next->mt_tail = mt->mt_tail;
75	} else {
76		avl_delete( &sm->sm_mods, mt, sm_avl_cmp );
77	}
78	ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
79	op->o_callback = sc->sc_next;
80	op->o_tmpfree( sc, op->o_tmpmemctx );
81
82	return 0;
83}
84
85static int
86seqmod_op_mod( Operation *op, SlapReply *rs )
87{
88	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
89	seqmod_info		*sm = on->on_bi.bi_private;
90	modtarget	*mt;
91	Avlnode	*av;
92	slap_callback *cb;
93
94	cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget),
95		op->o_tmpmemctx );
96	mt = (modtarget *)(cb+1);
97	mt->mt_next = NULL;
98	mt->mt_tail = mt;
99	mt->mt_op = op;
100
101	/* See if we're already modifying this entry - don't allow
102	 * near-simultaneous mods of the same entry
103	 */
104	ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
105	av = avl_find2( sm->sm_mods, mt, sm_avl_cmp );
106	if ( av ) {
107		modtarget *mtp = av->avl_data;
108		mtp->mt_tail->mt_next = mt;
109		mtp->mt_tail = mt;
110		/* Wait for this op to get to head of list */
111		while ( mtp != mt ) {
112			ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
113			ldap_pvt_thread_yield();
114			/* Let it finish - should use a condition
115			 * variable here... */
116			ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
117			mtp = av->avl_data;
118		}
119	} else {
120		/* Record that we're modifying this now */
121		avl_insert( &sm->sm_mods, mt, sm_avl_cmp, avl_dup_error );
122	}
123	ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
124
125	cb->sc_cleanup = seqmod_op_cleanup;
126	cb->sc_private = sm;
127	cb->sc_next = op->o_callback;
128	op->o_callback = cb;
129
130	return SLAP_CB_CONTINUE;
131}
132
133static int
134seqmod_op_extended(
135	Operation *op,
136	SlapReply *rs
137)
138{
139	if ( exop_is_write( op )) return seqmod_op_mod( op, rs );
140	else return SLAP_CB_CONTINUE;
141}
142
143static int
144seqmod_db_open(
145	BackendDB *be,
146	ConfigReply *cr
147)
148{
149	slap_overinst	*on = (slap_overinst *)be->bd_info;
150	seqmod_info	*sm;
151
152	sm = ch_calloc(1, sizeof(seqmod_info));
153	on->on_bi.bi_private = sm;
154
155	ldap_pvt_thread_mutex_init( &sm->sm_mutex );
156
157	return 0;
158}
159
160static int
161seqmod_db_close(
162	BackendDB *be,
163	ConfigReply *cr
164)
165{
166	slap_overinst	*on = (slap_overinst *)be->bd_info;
167	seqmod_info	*sm = (seqmod_info *)on->on_bi.bi_private;
168
169	if ( sm ) {
170		ldap_pvt_thread_mutex_destroy( &sm->sm_mutex );
171
172		ch_free( sm );
173	}
174
175	return 0;
176}
177
178/* This overlay is set up for dynamic loading via moduleload. For static
179 * configuration, you'll need to arrange for the slap_overinst to be
180 * initialized and registered by some other function inside slapd.
181 */
182
183static slap_overinst 		seqmod;
184
185int
186seqmod_initialize()
187{
188	seqmod.on_bi.bi_type = "seqmod";
189	seqmod.on_bi.bi_db_open = seqmod_db_open;
190	seqmod.on_bi.bi_db_close = seqmod_db_close;
191
192	seqmod.on_bi.bi_op_modify = seqmod_op_mod;
193	seqmod.on_bi.bi_op_modrdn = seqmod_op_mod;
194	seqmod.on_bi.bi_extended = seqmod_op_extended;
195
196	return overlay_register( &seqmod );
197}
198
199#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC
200int
201init_module( int argc, char *argv[] )
202{
203	return seqmod_initialize();
204}
205#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */
206
207#endif /* defined(SLAPD_OVER_SEQMOD) */
208