From 389465d3c43804f1bb213062c8c8ec380c2bb084 Mon Sep 17 00:00:00 2001
From: Howard Chu <hyc@openldap.org>
Date: Sun, 19 Jun 2011 20:03:01 -0700
Subject: [PATCH] delta-mmr conflict resolution

---
 servers/slapd/syncrepl.c | 322 +++++++++++++++++++++++++++++++++++----
 1 file changed, 294 insertions(+), 28 deletions(-)

diff --git a/servers/slapd/syncrepl.c b/servers/slapd/syncrepl.c
index 94e6c7babf..8a92dfdd4e 100644
--- a/servers/slapd/syncrepl.c
+++ b/servers/slapd/syncrepl.c
@@ -154,6 +154,36 @@ static int null_callback( Operation *, SlapReply * );
 
 static AttributeDescription *sync_descs[4];
 
+/* delta-mmr */
+static AttributeDescription *ad_reqMod, *ad_reqDN;
+
+typedef struct logschema {
+	struct berval ls_dn;
+	struct berval ls_req;
+	struct berval ls_mod;
+	struct berval ls_newRdn;
+	struct berval ls_delRdn;
+	struct berval ls_newSup;
+} logschema;
+
+static logschema changelog_sc = {
+	BER_BVC("targetDN"),
+	BER_BVC("changeType"),
+	BER_BVC("changes"),
+	BER_BVC("newRDN"),
+	BER_BVC("deleteOldRDN"),
+	BER_BVC("newSuperior")
+};
+
+static logschema accesslog_sc = {
+	BER_BVC("reqDN"),
+	BER_BVC("reqType"),
+	BER_BVC("reqMod"),
+	BER_BVC("reqNewRDN"),
+	BER_BVC("reqDeleteOldRDN"),
+	BER_BVC("reqNewSuperior")
+};
+
 static const char *
 syncrepl_state2str( int state )
 {
@@ -194,6 +224,13 @@ init_syncrepl(syncinfo_t *si)
 	if ( si->si_syncdata &&
 		!overlay_is_inst( si->si_be, syncrepl_ov.on_bi.bi_type )) {
 		overlay_config( si->si_be, syncrepl_ov.on_bi.bi_type, -1, NULL, NULL );
+		if ( !ad_reqMod ) {
+			const char *text;
+			logschema *ls = &accesslog_sc;
+
+			slap_bv2ad( &ls->ls_mod, &ad_reqMod, &text );
+			slap_bv2ad( &ls->ls_dn, &ad_reqDN, &text );
+		}
 	}
 
 	if ( !sync_descs[0] ) {
@@ -349,33 +386,6 @@ init_syncrepl(syncinfo_t *si)
 	si->si_exattrs = exattrs;	
 }
 
-typedef struct logschema {
-	struct berval ls_dn;
-	struct berval ls_req;
-	struct berval ls_mod;
-	struct berval ls_newRdn;
-	struct berval ls_delRdn;
-	struct berval ls_newSup;
-} logschema;
-
-static logschema changelog_sc = {
-	BER_BVC("targetDN"),
-	BER_BVC("changeType"),
-	BER_BVC("changes"),
-	BER_BVC("newRDN"),
-	BER_BVC("deleteOldRDN"),
-	BER_BVC("newSuperior")
-};
-
-static logschema accesslog_sc = {
-	BER_BVC("reqDN"),
-	BER_BVC("reqType"),
-	BER_BVC("reqMod"),
-	BER_BVC("reqNewRDN"),
-	BER_BVC("reqDeleteOldRDN"),
-	BER_BVC("reqNewSuperior")
-};
-
 static int
 ldap_sync_search(
 	syncinfo_t *si,
@@ -1775,6 +1785,193 @@ typedef struct OpExtraSync {
 	syncinfo_t *oe_si;
 } OpExtraSync;
 
+/* Copy the original modlist, split Replace ops into Delete/Add,
+ * and drop mod opattrs since this modification is in the past.
+ */
+static Modifications *mods_dup( Operation *op, Modifications *modlist )
+{
+	Modifications *mod, *modnew = NULL, *modtail = NULL;
+	int size;
+	for ( ; modlist; modlist = modlist->sml_next ) {
+		if ( modlist->sml_desc == slap_schema.si_ad_modifiersName ||
+			modlist->sml_desc == slap_schema.si_ad_modifyTimestamp ||
+			modlist->sml_desc == slap_schema.si_ad_entryCSN )
+			continue;
+		if ( modlist->sml_op == LDAP_MOD_REPLACE ) {
+			mod = op->o_tmpalloc( sizeof(Modifications), op->o_tmpmemctx );
+			mod->sml_desc = modlist->sml_desc;
+			mod->sml_values = NULL;
+			mod->sml_nvalues = NULL;
+			mod->sml_op = LDAP_MOD_DELETE;
+			mod->sml_numvals = 0;
+			mod->sml_flags = 0;
+			if ( !modnew )
+				modnew = mod;
+			if ( modtail )
+				modtail->sml_next = mod;
+			modtail = mod;
+		}
+		if ( modlist->sml_numvals ) {
+			size = (modlist->sml_numvals+1) * sizeof(struct berval);
+			if ( modlist->sml_nvalues ) size *= 2;
+		} else {
+			size = 0;
+		}
+		size += sizeof(Modifications);
+		mod = op->o_tmpalloc( size, op->o_tmpmemctx );
+		if ( !modnew )
+			modnew = mod;
+		if ( modtail )
+			modtail->sml_next = mod;
+		modtail = mod;
+		mod->sml_desc = modlist->sml_desc;
+		mod->sml_numvals = modlist->sml_numvals;
+		mod->sml_flags = 0;
+		if ( modlist->sml_numvals ) {
+			int i;
+			mod->sml_values = (BerVarray)(mod+1);
+			for (i=0; i<mod->sml_numvals; i++)
+				mod->sml_values[i] = modlist->sml_values[i];
+			if ( modlist->sml_nvalues ) {
+				mod->sml_nvalues = mod->sml_values + mod->sml_numvals + 1;
+				for (i=0; i<mod->sml_numvals; i++)
+					mod->sml_nvalues[i] = modlist->sml_nvalues[i];
+			}
+		}
+		if ( modlist->sml_op == LDAP_MOD_REPLACE )
+			mod->sml_op = LDAP_MOD_ADD;
+		else
+			mod->sml_op = modlist->sml_op;
+		mod->sml_next = NULL;
+	}
+	return modnew;
+}
+
+typedef struct resolve_ctxt {
+	syncinfo_t *rx_si;
+	Modifications *rx_mods;
+} resolve_ctxt;
+
+static void
+compare_vals( Modifications *m1, Modifications *m2 )
+{
+	int i, j;
+	struct berval *bv1, *bv2;
+
+	if ( m2->sml_nvalues ) {
+		bv2 = m2->sml_nvalues;
+		bv1 = m1->sml_nvalues;
+	} else {
+		bv2 = m2->sml_values;
+		bv1 = m1->sml_values;
+	}
+	for ( j=0; j<m2->sml_numvals; j++ ) {
+		for ( i=0; i<m1->sml_numvals; i++ ) {
+			if ( !ber_bvcmp( &bv1[i], &bv2[j] )) {
+				int k;
+				for ( k=i; k<m1->sml_numvals-1; k++ ) {
+					m1->sml_values[k] = m1->sml_values[k+1];
+					if ( m1->sml_nvalues )
+						m1->sml_nvalues[k] = m1->sml_nvalues[k+1];
+				}
+				m1->sml_numvals--;
+				i--;
+			}
+		}
+	}
+}
+
+static int
+syncrepl_resolve_cb( Operation *op, SlapReply *rs )
+{
+	if ( rs->sr_type == REP_SEARCH ) {
+		resolve_ctxt *rx = op->o_callback->sc_private;
+		Attribute *a = attr_find( rs->sr_entry->e_attrs, ad_reqMod );
+		if ( a ) {
+			Modifications *oldmods, *newmods, *m1, *m2, **prev;
+			oldmods = rx->rx_mods;
+			syncrepl_accesslog_mods( rx->rx_si, a->a_vals, &newmods );
+			for ( m2 = newmods; m2; m2=m2->sml_next ) {
+				for ( prev = &oldmods, m1 = *prev; m1; m1 = *prev ) {
+					if ( m1->sml_desc != m2->sml_desc ) {
+						prev = &m1->sml_next;
+						continue;
+					}
+					if ( m2->sml_op == LDAP_MOD_DELETE ||
+						m2->sml_op == LDAP_MOD_REPLACE ) {
+						int numvals = m2->sml_numvals;
+						if ( m2->sml_op == LDAP_MOD_REPLACE )
+							numvals = 0;
+						/* New delete All cancels everything */
+						if ( numvals == 0 ) {
+drop:
+							*prev = m1->sml_next;
+							op->o_tmpfree( m1, op->o_tmpmemctx );
+							continue;
+						}
+						if ( m1->sml_op == LDAP_MOD_DELETE ) {
+							if ( m1->sml_numvals == 0 ) {
+								/* turn this to SOFTDEL later */
+								m1->sml_flags = SLAP_MOD_INTERNAL;
+							} else {
+								compare_vals( m1, m2 );
+								if ( !m1->sml_numvals )
+									goto drop;
+							}
+						} else if ( m1->sml_op == LDAP_MOD_ADD ) {
+							compare_vals( m1, m2 );
+							if ( !m1->sml_numvals )
+								goto drop;
+						}
+					}
+
+					if ( m2->sml_op == LDAP_MOD_ADD ||
+						m2->sml_op == LDAP_MOD_REPLACE ) {
+						if ( m1->sml_op == LDAP_MOD_DELETE ) {
+							if ( !m1->sml_numvals ) goto drop;
+							compare_vals( m1, m2 );
+							if ( !m1->sml_numvals )
+								goto drop;
+						}
+						if ( m2->sml_desc->ad_type->sat_atype.at_single_value )
+							goto drop;
+						compare_vals( m1, m2 );
+						if ( !m1->sml_numvals )
+							goto drop;
+					}
+					prev = &m1->sml_next;
+				}
+			}
+			slap_mods_free( newmods, 1 );
+		}
+	}
+	return LDAP_SUCCESS;
+}
+
+typedef struct modify_ctxt {
+	Modifications *mx_orig;
+	Modifications *mx_free;
+} modify_ctxt;
+
+static int
+syncrepl_modify_cb( Operation *op, SlapReply *rs )
+{
+	slap_callback *sc = op->o_callback;
+	modify_ctxt *mx = sc->sc_private;
+	Modifications *ml;
+
+	op->o_callback = sc->sc_next;
+	op->o_tmpfree( sc, op->o_tmpmemctx );
+
+	op->orm_no_opattrs = 0;
+	op->orm_modlist = mx->mx_orig;
+	for ( ml = mx->mx_free; ml; ml = mx->mx_free ) {
+		mx->mx_free = ml->sml_next;
+		op->o_tmpfree( ml, op->o_tmpmemctx );
+	}
+	return SLAP_CB_CONTINUE;
+}
+
 static int
 syncrepl_op_modify( Operation *op, SlapReply *rs )
 {
@@ -1783,7 +1980,7 @@ syncrepl_op_modify( Operation *op, SlapReply *rs )
 	syncinfo_t *si;
 	Entry *e;
 	int rc, match = 0;
-	Modifications *mod;
+	Modifications *mod, *newlist;
 
 	LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
 		if ( oex->oe_key == (void *)syncrepl_message_to_op )
@@ -1833,6 +2030,7 @@ syncrepl_op_modify( Operation *op, SlapReply *rs )
 	 *    old         new
 	 *    delete all  delete all  drop
 	 *    delete all  delete X    SOFTDEL
+	 *    delete X    delete all  drop
 	 *    delete X    delete X    drop
 	 *    delete X    delete Y    OK
 	 *    delete all  add X       drop
@@ -1847,6 +2045,74 @@ syncrepl_op_modify( Operation *op, SlapReply *rs )
 	 *    that accesslog logs the original mod.
 	 */
 
+	newlist = mods_dup( op, op->orm_modlist );
+
+	{
+		Operation op2 = *op;
+		AttributeName an[2];
+		const char *text;
+		slap_callback cb;
+		struct berval bv;
+		char *ptr;
+		int size, rc;
+		SlapReply rs1 = {0};
+		resolve_ctxt rx = { si, newlist };
+
+		op2.o_tag = LDAP_REQ_SEARCH;
+		op2.ors_scope = LDAP_SCOPE_SUBTREE;
+		op2.ors_deref = LDAP_DEREF_NEVER;
+		op2.o_req_dn = si->si_logbase;
+		op2.o_req_ndn = si->si_logbase;
+		op2.ors_tlimit = SLAP_NO_LIMIT;
+		op2.ors_slimit = SLAP_NO_LIMIT;
+		op2.ors_limit = NULL;
+		memset( an, 0, sizeof(an));
+		an[0].an_desc = ad_reqMod;
+		an[0].an_name = ad_reqMod->ad_cname;
+		op2.ors_attrs = an;
+		op2.ors_attrsonly = 0;
+
+		bv = mod->sml_nvalues[0];
+		ptr = strchr(bv.bv_val, '.');
+		bv.bv_len = ptr - bv.bv_val;
+
+		size = sizeof("(&(reqStart>=)(reqDN=))");
+		size += bv.bv_len + op->o_req_ndn.bv_len + si->si_logfilterstr.bv_len;
+		op2.ors_filterstr.bv_val = op->o_tmpalloc( size, op->o_tmpmemctx );
+		op2.ors_filterstr.bv_len = sprintf(op2.ors_filterstr.bv_val,
+			"(&(reqStart>=%.*s)(reqDN=%s)%s)",
+			(int)bv.bv_len, bv.bv_val, op->o_req_ndn.bv_val, si->si_logfilterstr.bv_val );
+		op2.ors_filter = str2filter_x( op, op2.ors_filterstr.bv_val );
+
+		op2.o_callback = &cb;
+		cb.sc_response = syncrepl_resolve_cb;
+		cb.sc_private = &rx;
+		op2.o_bd = select_backend( &op2.o_req_ndn, 1 );
+		op2.o_bd->be_search( &op2, &rs1 );
+		newlist = rx.rx_mods;
+	}
+
+	{
+		slap_callback *sc = op->o_tmpalloc( sizeof(slap_callback) +
+			sizeof(modify_ctxt), op->o_tmpmemctx );
+		modify_ctxt *mx = (modify_ctxt *)(sc+1);
+		Modifications *ml;
+
+		sc->sc_response = syncrepl_modify_cb;
+		sc->sc_private = mx;
+		sc->sc_next = op->o_callback;
+		op->o_callback = sc;
+		op->orm_no_opattrs = 1;
+		mx->mx_orig = op->orm_modlist;
+		mx->mx_free = newlist;
+		for ( ml = newlist; ml; ml=ml->sml_next ) {
+			if ( ml->sml_flags == SLAP_MOD_INTERNAL ) {
+				ml->sml_flags = 0;
+				ml->sml_op = SLAP_MOD_SOFTDEL;
+			}
+		}
+		op->orm_modlist = newlist;
+	}
 	return SLAP_CB_CONTINUE;
 }
 
-- 
GitLab