/* seqmod.c - sequenced modifies */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2004-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>.
 */
/* ACKNOWLEDGEMENTS:
 * This work was initially developed by Howard Chu for inclusion in
 * OpenLDAP Software.
 */

#include "portable.h"

#ifdef SLAPD_OVER_SEQMOD

#include "slap.h"
#include "slap-config.h"

/* This overlay serializes concurrent attempts to modify a single entry */

typedef struct modtarget {
	struct modtarget *mt_next;
	struct modtarget *mt_tail;
	Operation *mt_op;
} modtarget;

typedef struct seqmod_info {
	Avlnode		*sm_mods;	/* entries being modified */
	ldap_pvt_thread_mutex_t	sm_mutex;
} seqmod_info;

static int
sm_avl_cmp( const void *c1, const void *c2 )
{
	const modtarget *m1, *m2;
	int rc;

	m1 = c1; m2 = c2;
	rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len;

	if ( rc ) return rc;
	return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn );
}

static int
seqmod_op_cleanup( Operation *op, SlapReply *rs )
{
	slap_callback *sc = op->o_callback;
	seqmod_info *sm = sc->sc_private;
	modtarget *mt, mtdummy;
	Avlnode	 *av;

	mtdummy.mt_op = op;
	/* This op is done, remove it */
	ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
	av = ldap_avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp );
	assert(av != NULL);

	mt = av->avl_data;

	/* If there are more, promote the next one */
	if ( mt->mt_next ) {
		av->avl_data = mt->mt_next;
		mt->mt_next->mt_tail = mt->mt_tail;
	} else {
		ldap_avl_delete( &sm->sm_mods, mt, sm_avl_cmp );
	}
	ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
	op->o_callback = sc->sc_next;
	op->o_tmpfree( sc, op->o_tmpmemctx );

	return 0;
}

static int
seqmod_op_mod( Operation *op, SlapReply *rs )
{
	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	seqmod_info		*sm = on->on_bi.bi_private;
	modtarget	*mt;
	Avlnode	*av;
	slap_callback *cb;

	cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget),
		op->o_tmpmemctx );
	mt = (modtarget *)(cb+1);
	mt->mt_next = NULL;
	mt->mt_tail = mt;
	mt->mt_op = op;

	/* See if we're already modifying this entry - don't allow
	 * near-simultaneous mods of the same entry
	 */
	ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
	av = ldap_avl_find2( sm->sm_mods, mt, sm_avl_cmp );
	if ( av ) {
		modtarget *mtp = av->avl_data;
		mtp->mt_tail->mt_next = mt;
		mtp->mt_tail = mt;
		/* Wait for this op to get to head of list */
		while ( mtp != mt ) {
			ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
			ldap_pvt_thread_yield();
			/* Let it finish - should use a condition
			 * variable here... */
			ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
			mtp = av->avl_data;
		}
	} else {
		/* Record that we're modifying this now */
		ldap_avl_insert( &sm->sm_mods, mt, sm_avl_cmp, ldap_avl_dup_error );
	}
	ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );

	cb->sc_cleanup = seqmod_op_cleanup;
	cb->sc_private = sm;
	cb->sc_next = op->o_callback;
	op->o_callback = cb;

	return SLAP_CB_CONTINUE;
}

static int
seqmod_op_extended(
	Operation *op,
	SlapReply *rs
)
{
	if ( exop_is_write( op )) return seqmod_op_mod( op, rs );
	else return SLAP_CB_CONTINUE;
}

static int
seqmod_db_open(
	BackendDB *be,
	ConfigReply *cr
)
{
	slap_overinst	*on = (slap_overinst *)be->bd_info;
	seqmod_info	*sm;

	sm = ch_calloc(1, sizeof(seqmod_info));
	on->on_bi.bi_private = sm;

	ldap_pvt_thread_mutex_init( &sm->sm_mutex );

	return 0;
}

static int
seqmod_db_close(
	BackendDB *be,
	ConfigReply *cr
)
{
	slap_overinst	*on = (slap_overinst *)be->bd_info;
	seqmod_info	*sm = (seqmod_info *)on->on_bi.bi_private;

	if ( sm ) {
		ldap_pvt_thread_mutex_destroy( &sm->sm_mutex );

		ch_free( sm );
		on->on_bi.bi_private = NULL;
	}

	return 0;
}

/* This overlay is set up for dynamic loading via moduleload. For static
 * configuration, you'll need to arrange for the slap_overinst to be
 * initialized and registered by some other function inside slapd.
 */

static slap_overinst 		seqmod;

int
seqmod_initialize()
{
	seqmod.on_bi.bi_type = "seqmod";
	seqmod.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
	seqmod.on_bi.bi_db_open = seqmod_db_open;
	seqmod.on_bi.bi_db_close = seqmod_db_close;

	seqmod.on_bi.bi_op_modify = seqmod_op_mod;
	seqmod.on_bi.bi_op_modrdn = seqmod_op_mod;
	seqmod.on_bi.bi_extended = seqmod_op_extended;

	return overlay_register( &seqmod );
}

#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
	return seqmod_initialize();
}
#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */

#endif /* defined(SLAPD_OVER_SEQMOD) */
