]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#10358 Retry if entry changed (use assert control to detect this) 781/head
authorOndřej Kuzník <ondra@mistotebe.net>
Mon, 8 Dec 2025 12:50:44 +0000 (12:50 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Tue, 9 Dec 2025 00:59:57 +0000 (00:59 +0000)
servers/slapd/syncrepl.c

index 1cb0eb44e4e54d720564979433e90781683c5a18..2d8c72a62e71e30fe05eefab66fb86e16cdf55d7 100644 (file)
@@ -4017,6 +4017,7 @@ typedef struct dninfo {
        int oldNcount;          /* #values of old naming attr */
        AttributeDescription *oldDesc;  /* for renames */
        AttributeDescription *newDesc;  /* for renames */
+       char oldcsn[LDAP_PVT_CSNSTR_BUFSIZE];
 } dninfo;
 
 #define HASHUUID       1
@@ -4133,15 +4134,24 @@ syncrepl_entry(
        slap_callback   cb = { NULL, NULL, NULL, NULL };
        int syncuuid_inserted = 0;
 
+       BerElementBuffer berbuf;
+       BerElement *ber = (BerElement *)&berbuf;
+       LDAPControl c = { .ldctl_oid = LDAP_CONTROL_ASSERT, .ldctl_iscritical = 0 },
+                               *ca[2] = { &c, NULL };
+
        SlapReply       rs_search = {REP_RESULT};
-       Filter f = {0};
-       AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+       Filter f = {0}, csn_assertion = { .f_choice = LDAP_FILTER_EQUALITY };
+       AttributeAssertion ava = ATTRIBUTEASSERTION_INIT,
+                                          csnava = ATTRIBUTEASSERTION_INIT;
        int rc = LDAP_SUCCESS;
 
-       struct berval pdn = BER_BVNULL;
+       struct berval filterstr, pdn = BER_BVNULL;
        dninfo dni = {0};
        int     retry = 1;
-       int     freecsn = 1;
+       int     freecsn = 1, csn_queued = 0;
+
+       ber_init2( ber, NULL, LBER_USE_DER );
+       ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
 
        Debug( LDAP_DEBUG_SYNC,
                "syncrepl_entry: %s LDAP_RES_SEARCH_ENTRY(LDAP_SYNC_%s) csn=%s tid %p\n",
@@ -4180,25 +4190,64 @@ syncrepl_entry(
                }
        }
 
-       f.f_choice = LDAP_FILTER_EQUALITY;
-       f.f_ava = &ava;
-       ava.aa_desc = slap_schema.si_ad_entryUUID;
-       ava.aa_value = *syncUUID;
-
        if ( syncuuid_inserted ) {
                Debug( LDAP_DEBUG_SYNC, "syncrepl_entry: %s inserted UUID %s\n",
                        si->si_ridtxt, syncUUID[1].bv_val );
        }
-       op->ors_filter = &f;
 
-       op->ors_filterstr.bv_len = STRLENOF( "(entryUUID=)" ) + syncUUID[1].bv_len;
-       op->ors_filterstr.bv_val = (char *) slap_sl_malloc(
-               op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); 
-       AC_MEMCPY( op->ors_filterstr.bv_val, "(entryUUID=", STRLENOF( "(entryUUID=" ) );
-       AC_MEMCPY( &op->ors_filterstr.bv_val[STRLENOF( "(entryUUID=" )],
+       filterstr.bv_len = STRLENOF( "(entryUUID=)" ) + syncUUID[1].bv_len;
+       filterstr.bv_val = (char *)slap_sl_malloc( filterstr.bv_len + 1,
+                       op->o_tmpmemctx );
+
+       AC_MEMCPY( filterstr.bv_val, "(entryUUID=", STRLENOF( "(entryUUID=" ) );
+       AC_MEMCPY( &filterstr.bv_val[STRLENOF( "(entryUUID=" )],
                syncUUID[1].bv_val, syncUUID[1].bv_len );
-       op->ors_filterstr.bv_val[op->ors_filterstr.bv_len - 1] = ')';
-       op->ors_filterstr.bv_val[op->ors_filterstr.bv_len] = '\0';
+       filterstr.bv_val[filterstr.bv_len - 1] = ')';
+       filterstr.bv_val[filterstr.bv_len] = '\0';
+
+       csnava.aa_desc = slap_schema.si_ad_entryCSN;
+       csn_assertion.f_ava = &csnava;
+
+retry_diff:
+       /*
+        * ITS#10358: Another thread edited this entry changing its entryCSN, could
+        * have been another serverID with a CSN that's still older than ourselves
+        * so we looped back here: we have to reset our state and try again.
+        *
+        * Since ITS#9584, an entry can only be in process of being change by one
+        * consumer task, this leaves a race with actual clients. Luckily those can
+        * only generate a CSN that's newer than what we just received so we only
+        * retry once. We still accept this pending CSN, it's a modification that's
+        * been eclipsed, not rejected.
+        */
+       if ( !freecsn ) {
+               BER_BVZERO( &op->o_csn );
+               freecsn = 1;
+       }
+       if ( !BER_BVISNULL( &dni.ndn ) ) {
+               op->o_tmpfree( dni.ndn.bv_val, op->o_tmpmemctx );
+               BER_BVZERO( &dni.ndn );
+       }
+       if ( !BER_BVISNULL( &dni.dn ) ) {
+               op->o_tmpfree( dni.dn.bv_val, op->o_tmpmemctx );
+               BER_BVZERO( &dni.dn );
+       }
+       dni.mods = NULL;
+
+       if ( *dni.oldcsn ) {
+               ber_reset( ber, 1 );
+               dni.oldcsn[0] = '\0';
+               op->o_ctrls = NULL;
+               op->o_assert = SLAP_CONTROL_NONE;
+       }
+
+       f.f_choice = LDAP_FILTER_EQUALITY;
+       f.f_ava = &ava;
+       ava.aa_desc = slap_schema.si_ad_entryUUID;
+       ava.aa_value = *syncUUID;
+
+       op->ors_filter = &f;
+       op->ors_filterstr = filterstr;
 
        op->o_tag = LDAP_REQ_SEARCH;
        op->ors_scope = LDAP_SCOPE_SUBTREE;
@@ -4239,8 +4288,22 @@ syncrepl_entry(
                        si->si_ridtxt, rc );
 
        op->o_dont_replicate = 0;
-       if ( !BER_BVISNULL( &op->ors_filterstr ) ) {
-               slap_sl_free( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+       BER_BVZERO( &op->ors_filterstr );
+
+       if ( *dni.oldcsn ) {
+               /*
+                * ITS#10358: We synthesize an assert control, have to create both
+                * versions in case this is push replication where the issue is
+                * also more likely to happen.
+                */
+               ber_str2bv( dni.oldcsn, 0, 0, &csnava.aa_value );
+               ber_printf( ber, "t{OO}", LDAP_FILTER_EQUALITY,
+                               &slap_schema.si_ad_entryCSN->ad_cname,
+                               &csnava.aa_value );
+               ber_flatten2( ber, &c.ldctl_value, 0 );
+               op->o_ctrls = ca;
+               op->o_assertion = &csn_assertion;
+               op->o_assert = SLAP_CONTROL_NONCRITICAL;
        }
 
        cb.sc_response = syncrepl_null_callback;
@@ -4256,9 +4319,10 @@ syncrepl_entry(
                                si->si_ridtxt, dni.dn.bv_val ? dni.dn.bv_val : "(null)" );
        }
 
-       assert( BER_BVISNULL( &op->o_csn ) );
-       if ( syncCSN ) {
+       assert( csn_queued || BER_BVISNULL( &op->o_csn ) );
+       if ( !csn_queued && syncCSN ) {
                slap_queue_csn( op, syncCSN );
+               csn_queued = 1;
        }
 
 #ifdef SLAP_CONTROL_X_LAZY_COMMIT
@@ -4312,6 +4376,8 @@ retry_add:;
                        op->o_tag = LDAP_REQ_ADD;
                        op->ora_e = entry;
                        op->o_bd = si->si_wbe;
+                       op->o_ctrls = NULL;
+                       op->o_assert = SLAP_CONTROL_NONE;
 
                        rc = op->o_bd->be_add( op, &rs_add );
                        Debug( LDAP_DEBUG_SYNC,
@@ -4607,11 +4673,16 @@ retry_modrdn:;
                         * has not been added yet (ITS#6472) */
                        if ( rc == LDAP_NO_SUCH_OBJECT && op->orr_nnewSup != NULL ) {
                                Operation op2 = *op;
+                               op2.o_ctrls = NULL;
+                               op2.o_assert = SLAP_CONTROL_NONE;
                                rc = syncrepl_add_glue_ancestors( &op2, entry );
                                if ( rc == LDAP_SUCCESS ) {
                                        goto retry_modrdn;
                                }
                        }
+                       if ( rc == LDAP_ASSERTION_FAILED ) {
+                               goto retry_diff;
+                       }
                
                        op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx );
                        op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx );
@@ -4627,8 +4698,10 @@ retry_modrdn:;
                        /* Use CSN on the modify */
                        if ( just_rename )
                                syncCSN = NULL;
-                       else if ( syncCSN )
+                       else if ( syncCSN ) {
                                slap_queue_csn( op, syncCSN );
+                               csn_queued = 1;
+                       }
                }
                if ( dni.mods ) {
                        SlapReply rs_modify = {REP_RESULT};
@@ -4644,7 +4717,9 @@ retry_modrdn:;
                        Debug( LDAP_DEBUG_SYNC,
                                        "syncrepl_entry: %s be_modify %s (%d)\n", 
                                        si->si_ridtxt, op->o_req_dn.bv_val, rc );
-                       if ( rs_modify.sr_err != LDAP_SUCCESS ) {
+                       if ( rs_modify.sr_err == LDAP_ASSERTION_FAILED ) {
+                               goto retry_diff;
+                       } else if ( rs_modify.sr_err != LDAP_SUCCESS ) {
                                Debug( LDAP_DEBUG_ANY,
                                        "syncrepl_entry: %s be_modify failed (%d)\n",
                                        si->si_ridtxt, rs_modify.sr_err );
@@ -4670,6 +4745,7 @@ retry_modrdn:;
                        op->o_bd = si->si_wbe;
                        if ( !syncCSN && si->si_syncCookie.ctxcsn ) {
                                slap_queue_csn( op, si->si_syncCookie.ctxcsn );
+                               csn_queued = 1;
                        }
                        rc = op->o_bd->be_delete( op, &rs_delete );
                        Debug( LDAP_DEBUG_SYNC,
@@ -4706,6 +4782,9 @@ retry_modrdn:;
        }
 
 done:
+       op->o_ctrls = NULL;
+       op->o_assert = SLAP_CONTROL_NONE;
+       op->o_assertion = NULL;
        slap_sl_free( syncUUID[1].bv_val, op->o_tmpmemctx );
        BER_BVZERO( &syncUUID[1] );
        if ( !BER_BVISNULL( &dni.ndn ) ) {
@@ -4723,6 +4802,9 @@ done:
        if ( !BER_BVISNULL( &op->o_csn ) && freecsn ) {
                op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
        }
+       if ( !BER_BVISNULL( &filterstr ) ) {
+               slap_sl_free( filterstr.bv_val, op->o_tmpmemctx );
+       }
        BER_BVZERO( &op->o_csn );
        return rc;
 }
@@ -5914,6 +5996,8 @@ dn_callback(
                                                                        old->a_vals[0].bv_val );
                                                                return LDAP_SUCCESS;
                                                        }
+                                                       memcpy( dni->oldcsn, old->a_vals[0].bv_val,
+                                                                       old->a_vals[0].bv_len+1 );
                                                }
                                        }