diff options
Diffstat (limited to 'ldap/servers/plugins/replication/urp.c')
-rw-r--r-- | ldap/servers/plugins/replication/urp.c | 1282 |
1 files changed, 1282 insertions, 0 deletions
diff --git a/ldap/servers/plugins/replication/urp.c b/ldap/servers/plugins/replication/urp.c new file mode 100644 index 00000000..a4dc86f9 --- /dev/null +++ b/ldap/servers/plugins/replication/urp.c @@ -0,0 +1,1282 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright 2001 Sun Microsystems, Inc. + * Portions copyright 1999, 2001-2003 Netscape Communications Corporation. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * urp.c - Update Resolution Procedures + */ + +#include "slapi-plugin.h" +#include "repl.h" +#include "repl5.h" +#include "urp.h" + +extern int slapi_log_urp; + +static int urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn); +static int urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype); +static int urp_naming_conflict_removal (Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype); +static int mod_namingconflict_attr (const char *uniqueid, const char*entrydn, const char *conflictdn, CSN *opcsn); +static int del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags); +static char *get_dn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid); +static char *get_rdn_plus_uniqueid(char *sessionid,const char *olddn,const char *uniqueid); +static void set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn); +static int is_suffix_entry (Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parenddn); + +/* + * Return 0 for OK, -1 for Error. + */ +int +urp_modify_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *modifyentry= NULL; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &modifyentry ); + + if(modifyentry!=NULL) + { + /* + * The entry to be modified exists. + * - the entry could be a tombstone... but that's OK. + * - the entry could be glue... that may not be OK. JCMREPL + */ + rc= 0; /* OK, Modify the entry */ + PROFILE_POINT; /* Modify Conflict; Entry Exists; Apply Modification */ + } + else + { + /* + * The entry to be modified could not be found. + */ + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Must discard this Modification */ + PROFILE_POINT; /* Modify Conflict; Entry Does Not Exist; Discard Modification */ + } + return rc; +} + +/* + * Return 0 for OK, + * -1 for Ignore or Error depending on SLAPI_RESULT_CODE, + * >0 for action code + * Action Code Bit 0: Fetch existing entry. + * Action Code Bit 1: Fetch parent entry. + * The function is called as a be pre-op on consumers. + */ +int +urp_add_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *existing_uniqueid_entry; + Slapi_Entry *existing_dn_entry; + Slapi_Entry *addentry; + const char *adduniqueid; + CSN *opcsn; + const char *basedn; + char sessionid[REPL_SESSION_ID_SIZE]; + int r; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get( pb, SLAPI_ADD_EXISTING_UNIQUEID_ENTRY, &existing_uniqueid_entry ); + if (existing_uniqueid_entry!=NULL) + { + /* + * An entry with this uniqueid already exists. + * - It could be a replay of the same Add, or + * - It could be a UUID generation collision, or + */ + op_result = LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* Add Conflict; UniqueID Exists; Ignore */ + goto bailout; + } + + get_repl_session_id (pb, sessionid, &opcsn); + slapi_pblock_get( pb, SLAPI_ADD_ENTRY, &addentry ); + slapi_pblock_get( pb, SLAPI_ADD_EXISTING_DN_ENTRY, &existing_dn_entry ); + if (existing_dn_entry==NULL) /* The target DN does not exist */ + { + /* Check for parent entry... this could be an orphan. */ + Slapi_Entry *parententry; + slapi_pblock_get( pb, SLAPI_ADD_PARENT_ENTRY, &parententry ); + rc = urp_add_resolve_parententry (pb, sessionid, addentry, parententry, opcsn); + PROFILE_POINT; /* Add Entry */ + goto bailout; + } + + /* + * Naming conflict: an entry with the target DN already exists. + * Compare the DistinguishedNameCSN of the existing entry + * and the OperationCSN. The smaller CSN wins. The loser changes + * its RDN to uniqueid+baserdn, and adds operational attribute + * ATTR_NSDS5_REPLCONFLIC. + */ + basedn = slapi_entry_get_ndn (addentry); + adduniqueid = slapi_entry_get_uniqueid (addentry); + r = csn_compare (entry_get_dncsn(existing_dn_entry), opcsn); + if (r<0) + { + /* Entry to be added is a loser */ + char *newdn= get_dn_plus_uniqueid (sessionid, basedn, adduniqueid); + if(newdn==NULL) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort this Operation */ + PROFILE_POINT; /* Add Conflict; Entry Exists; Unique ID already in RDN - Abort this update. */ + } + else + { + /* Add the nsds5ReplConflict attribute in the mods */ + Slapi_Attr *attr = NULL; + Slapi_Value **vals = NULL; + Slapi_RDN *rdn; + char buf[BUFSIZ]; + + sprintf(buf, "%s %s", REASON_ANNOTATE_DN, basedn); + if (slapi_entry_attr_find (addentry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + /* ATTR_NSDS5_REPLCONFLICT exists */ + slapi_log_error (SLAPI_LOG_FATAL, sessionid, "New entry has nsds5ReplConflict already\n"); + vals = attr_get_present_values (attr); /* this returns a pointer to the contents */ + } + if ( vals == NULL || *vals == NULL ) + { + /* Add new attribute */ + slapi_entry_add_string (addentry, ATTR_NSDS5_REPLCONFLICT, buf); + } + else + { + /* + * Replace old attribute. We don't worry about the index + * change here since the entry is yet to be added. + */ + slapi_value_set_string (*vals, buf); + } + slapi_entry_set_dn (addentry,slapi_ch_strdup(newdn)); + set_pblock_dn(pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */ + + rdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn_const(addentry) ); + slapi_log_error (slapi_log_urp, sessionid, + "Naming conflict ADD. Add %s instead\n", slapi_rdn_get_rdn(rdn) ); + slapi_rdn_free(&rdn); + + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Operation Entry */ + } + } + else if(r>0) + { + /* Existing entry is a loser */ + if (!urp_annotate_dn(sessionid, existing_dn_entry, opcsn, "ADD")) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + } + else + { + /* The backend add code should now search for the existing entry again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + } + PROFILE_POINT; /* Add Conflict; Entry Exists; Rename Existing Entry */ + } + else /* r==0 */ + { + /* The CSN of the Operation and the Entry DN are the same. + * This could only happen if: + * a) There are two replicas with the same ReplicaID. + * b) We've seen the Operation before. + * Let's go with (b) and ignore the little bastard. + */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* Add Conflict; Entry Exists; Same CSN */ + } + +bailout: + return rc; +} + +/* + * Return 0 for OK, -1 for Error, >0 for action code + * Action Code Bit 0: Fetch existing entry. + * Action Code Bit 1: Fetch parent entry. + */ +int +urp_modrdn_operation( Slapi_PBlock *pb ) +{ + slapi_operation_parameters *op_params = NULL; + Slapi_Entry *parent_entry; + Slapi_Entry *new_parent_entry; + Slapi_DN *newsuperior = NULL; + char *newsuperiordn; + Slapi_DN *parentdn = NULL; + Slapi_Entry *target_entry; + Slapi_Entry *existing_entry; + const CSN *target_entry_dncsn; + CSN *opcsn= NULL; + char *op_uniqueid = NULL; + const char *existing_uniqueid = NULL; + const char *target_dn; + const char *existing_dn; + char *newrdn; + char sessionid[REPL_SESSION_ID_SIZE]; + int r; + int op_result= 0; + int rc= 0; /* OK */ + int del_old_replconflict_attr = 0; + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get (pb, SLAPI_MODRDN_TARGET_ENTRY, &target_entry); + if(target_entry==NULL) + { + /* An entry can't be found for the Unique Identifier */ + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* No entry to modrdn */ + PROFILE_POINT; /* ModRDN Conflict; Entry does not Exist; Discard ModRDN */ + goto bailout; + } + + get_repl_session_id (pb, sessionid, &opcsn); + target_entry_dncsn = entry_get_dncsn (target_entry); + if ( csn_compare (target_entry_dncsn, opcsn) >= 0 ) + { + /* + * The Operation CSN is not newer than the DN CSN. + * Either we're beaten by another ModRDN or we've applied the op. + */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore the modrdn */ + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */ + goto bailout; + } + + /* The DN CSN is older than the Operation CSN. Apply the operation */ + target_dn = slapi_entry_get_dn_const ( target_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &newrdn); + slapi_pblock_get(pb, SLAPI_TARGET_UNIQUEID, &op_uniqueid); + slapi_pblock_get(pb, SLAPI_MODRDN_PARENT_ENTRY, &parent_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWPARENT_ENTRY, &new_parent_entry); + slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn); + + if ( is_tombstone_entry (target_entry) ) + { + /* + * It is a non-trivial task to rename a tombstone. + * This op has been ignored so far by + * setting SLAPI_RESULT_CODE to LDAP_NO_SUCH_OBJECT + * and rc to -1. + */ + + /* Turn the tombstone to glue before rename it */ + /* + op_result = tombstone_to_glue (pb, sessionid, target_entry, + slapi_entry_get_sdn (target_entry), "renameTombstone", opcsn); + */ + op_result = LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + if (op_result == 0) + { + /* + * Remember to turn this entry back to tombstone in post op. + * We'll just borrow an obsolete pblock type here. + */ + slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, strdup(op_uniqueid)); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_TARGET_ENTRY); + rc = 0; + } + else + { + rc = -1; + } + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; OPCSN is not newer. */ + goto bailout; + } + + slapi_pblock_get(pb, SLAPI_MODRDN_EXISTING_ENTRY, &existing_entry); + if(existing_entry!=NULL) + { + /* + * An entry with the target DN already exists. + * The smaller dncsn wins. The loser changes its RDN to + * uniqueid+baserdn, and adds operational attribute + * ATTR_NSDS5_REPLCONFLIC + */ + + existing_uniqueid = slapi_entry_get_uniqueid (existing_entry); + existing_dn = slapi_entry_get_dn_const ( existing_entry); + + /* + * Dismiss the operation if the existing entry is the same as the target one. + */ + if (strcmp(op_uniqueid, existing_uniqueid) == 0) { + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Ignore the op */ + PROFILE_POINT; /* ModRDN Replay */ + goto bailout; + } + + r= csn_compare ( entry_get_dncsn (existing_entry), opcsn); + if (r == 0) + { + /* + * The CSN of the Operation and the Entry DN are the same + * but the uniqueids are not. + * There might be two replicas with the same ReplicaID. + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Duplicated CSN for different uniqueids [%s][%s]", + existing_uniqueid, op_uniqueid); + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort */ + PROFILE_POINT; /* ModRDN Conflict; Duplicated CSN for Different Entries */ + goto bailout; + } + + if(r<0) + { + /* The target entry is a loser */ + + char *newrdn_with_uniqueid; + newrdn_with_uniqueid= get_rdn_plus_uniqueid (sessionid, newrdn, op_uniqueid); + if(newrdn_with_uniqueid==NULL) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Ignore this Operation */ + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; + Unique ID already in RDN - Change to Lost and Found entry */ + goto bailout; + } + mod_namingconflict_attr (op_uniqueid, target_dn, existing_dn, opcsn); + set_pblock_dn (pb, SLAPI_MODRDN_NEWRDN, newrdn_with_uniqueid); + slapi_log_error(slapi_log_urp, sessionid, + "Naming conflict MODRDN. Rename target entry to %s\n", + newrdn_with_uniqueid ); + + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Operation Entry */ + goto bailout; + } + + if ( r>0 ) + { + /* The existing entry is a loser */ + + int resolve = urp_annotate_dn (sessionid, existing_entry, opcsn, "MODRDN"); + if(!resolve) + { + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Abort this Operation */ + goto bailout; + } + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY); + if (LDAP_NO_SUCH_OBJECT == resolve) { + /* This means that existing_dn_entry did not really exist!!! + * This indicates that a get_copy_of_entry -> dn2entry returned + * an entry (existing_dn_entry) that was already removed from the ldbm. + * This is bad, because it indicates a dn cache or DB corruption. + * However, as far as the conflict is concerned, this error is harmless: + * if the existing_dn_entry did not exist in the first place, there was no + * conflict!! Return 0 for success to break the ldbm_back_modrdn loop + * and get out of this inexistent conflict resolution ASAP. + */ + rc = 0; + } + /* Set flag to remove possible old naming conflict */ + del_old_replconflict_attr = 1; + PROFILE_POINT; /* ModRDN Conflict; Entry with Target DN Exists; Rename Entry with Target DN */ + goto bailout; + } + } + else + { + /* + * No entry with the target DN exists. + */ + + /* Set flag to remove possible old naming conflict */ + del_old_replconflict_attr = 1; + + if(new_parent_entry!=NULL) + { + /* The new superior entry exists */ + rc= 0; /* OK, Apply the ModRDN */ + PROFILE_POINT; /* ModRDN Conflict; OK */ + goto bailout; + } + + /* The new superior entry doesn't exist */ + + slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR, &newsuperiordn); + if(newsuperiordn == NULL) + { + /* (new_parent_entry==NULL && newsuperiordn==NULL) + * This is ok - SLAPI_MODRDN_NEWPARENT_ENTRY will + * only be set if SLAPI_MODRDN_NEWSUPERIOR was + * suplied by the client. If it wasn't, we're just + * changing the RDN of the entry. In that case, + * if the entry exists, its parent won't change + * when it's renamed, and therefore we can assume + * its parent exists. + */ + rc=0; + PROFILE_POINT; /* ModRDN OK */ + goto bailout; + } + + newsuperior= slapi_sdn_new_dn_byval(newsuperiordn); + + if((0 == slapi_sdn_compare (slapi_entry_get_sdn(parent_entry), newsuperior)) || + is_suffix_dn (pb, newsuperior, &parentdn) ) + { + /* + * The new superior is the same as the current one, or + * this entry is a suffix whose parent can be absent. + */ + rc= 0; /* OK, Move the entry */ + PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent; Create Suffix Entry */ + goto bailout; + } + + /* + * This entry is not a suffix entry, so the parent entry should exist. + * (This shouldn't happen in a ds5 server) + */ + slapi_pblock_get ( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_result = create_glue_entry (pb, sessionid, newsuperior, + op_params->p.p_modrdn.modrdn_newsuperior_address.uniqueid, opcsn); + if (LDAP_SUCCESS != op_result) + { + /* + * FATAL ERROR + * We should probably just abort the rename + * this will cause replication divergence requiring + * admin intercession + */ + slapi_log_error( SLAPI_LOG_FATAL, sessionid, + "Parent %s couldn't be found, nor recreated as a glue entry\n", newsuperiordn ); + op_result= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; + PROFILE_POINT; + goto bailout; + } + + /* The backend add code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_NEWPARENT_ENTRY); + PROFILE_POINT; /* ModRDN Conflict; Absent Target Parent - Change to Lost and Found entry */ + goto bailout; + } + +bailout: + if ( del_old_replconflict_attr && rc == 0 ) + { + del_replconflict_attr (target_entry, opcsn, 0); + } + if ( parentdn ) + slapi_sdn_free(&parentdn); + if ( newsuperior ) + slapi_sdn_free(&newsuperior); + return rc; +} + +/* + * Return 0 for OK, -1 for Error + */ +int +urp_delete_operation( Slapi_PBlock *pb ) +{ + Slapi_Entry *deleteentry; + CSN *opcsn= NULL; + char sessionid[REPL_SESSION_ID_SIZE]; + int op_result= 0; + int rc= 0; /* OK */ + + if ( slapi_op_abandoned(pb) ) + { + return rc; + } + + slapi_pblock_get(pb, SLAPI_DELETE_EXISTING_ENTRY, &deleteentry); + + if(deleteentry==NULL) /* uniqueid can't be found */ + { + op_result= LDAP_NO_SUCH_OBJECT; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc= -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Entry not exist. */ + } + else if(is_tombstone_entry(deleteentry)) + { + /* The entry is already a Tombstone, ignore this delete. */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Already a Tombstone. */ + } + else /* The entry to be deleted exists and is not a tombstone */ + { + get_repl_session_id (pb, sessionid, &opcsn); + + /* Check if the entry has children. */ + if(!slapi_entry_has_children(deleteentry)) + { + /* Remove possible conflict attributes */ + del_replconflict_attr (deleteentry, opcsn, 0); + rc= 0; /* OK, to delete the entry */ + PROFILE_POINT; /* Delete Operation; OK. */ + } + else + { + /* Turn this entry into a glue_absent_parent entry */ + entry_to_glue(sessionid, deleteentry, REASON_RESURRECT_ENTRY, opcsn); + + /* Turn the Delete into a No-Op */ + op_result= LDAP_SUCCESS; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &op_result); + rc = -1; /* Don't apply the Delete */ + PROFILE_POINT; /* Delete Operation; Entry has children. */ + } + } + return rc; +} + +int urp_post_modrdn_operation (Slapi_PBlock *pb) +{ + CSN *opcsn; + char sessionid[REPL_SESSION_ID_SIZE]; + char *tombstone_uniqueid; + Slapi_Entry *postentry; + Slapi_Operation *op; + + /* + * Do not abandon the post op - the processed CSN needs to be + * committed to keep the consistency between the changelog + * and the backend DB. + * if ( slapi_op_abandoned(pb) ) return 0; + */ + + slapi_pblock_get (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, &tombstone_uniqueid ); + if (tombstone_uniqueid == NULL) + { + /* + * The entry is not resurrected from tombstone. Hence + * we need to check if any naming conflict with its + * old dn can be resolved. + */ + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + get_repl_session_id (pb, sessionid, &opcsn); + urp_naming_conflict_removal (pb, sessionid, opcsn, "MODRDN"); + } + } + else + { + /* + * The entry was a resurrected tombstone. + * This could happen when we applied a rename + * to a tombstone to avoid server divergence. Now + * it's time to put the entry back to tombstone. + */ + slapi_pblock_get ( pb, SLAPI_ENTRY_POST_OP, &postentry ); + if (postentry && strcmp(tombstone_uniqueid, slapi_entry_get_uniqueid(postentry)) == 0) + { + entry_to_tombstone (pb, postentry); + } + slapi_ch_free ((void**)&tombstone_uniqueid); + slapi_pblock_set (pb, SLAPI_URP_TOMBSTONE_UNIQUEID, NULL); + } + + return 0; +} + +/* + * Conflict removal + */ +int +urp_post_delete_operation( Slapi_PBlock *pb ) +{ + Slapi_Operation *op; + Slapi_Entry *entry; + CSN *opcsn; + char sessionid[REPL_SESSION_ID_SIZE]; + int op_result; + + /* + * Do not abandon the post op - the processed CSN needs to be + * committed to keep the consistency between the changelog + * and the backend DB + * if ( slapi_op_abandoned(pb) ) return 0; + */ + + get_repl_session_id (pb, sessionid, &opcsn); + + /* + * Conflict removal from the parent entry: + * If the parent is glue and has no more children, + * turn the parent to tombstone + */ + slapi_pblock_get ( pb, SLAPI_DELETE_GLUE_PARENT_ENTRY, &entry ); + if ( entry != NULL ) + { + op_result = entry_to_tombstone ( pb, entry ); + if ( op_result == LDAP_SUCCESS ) + { + slapi_log_error ( slapi_log_urp, sessionid, + "Tombstoned glue entry %s since it has no more children\n", + slapi_entry_get_dn_const (entry) ); + } + } + + slapi_pblock_get( pb, SLAPI_OPERATION, &op); + if (!operation_is_flag_set(op, OP_FLAG_REPL_FIXUP)) + { + /* + * Conflict removal from the peers of the old dn + */ + urp_naming_conflict_removal (pb, sessionid, opcsn, "DEL"); + } + + return 0; +} + +int +urp_fixup_add_entry (Slapi_Entry *e, const char *target_uniqueid, const char *parentuniqueid, CSN *opcsn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new (); + + /* + * Mark this operation as replicated, so that the front end + * doesn't add extra attributes. + */ + slapi_add_entry_internal_set_pb ( + newpb, + e, + NULL, /*Controls*/ + repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + if (target_uniqueid) + { + slapi_pblock_set( newpb, SLAPI_TARGET_UNIQUEID, (void*)target_uniqueid); + } + if (parentuniqueid) + { + struct slapi_operation_parameters *op_params; + slapi_pblock_get( newpb, SLAPI_OPERATION_PARAMETERS, &op_params ); + op_params->p.p_add.parentuniqueid = (char*)parentuniqueid; /* Consumes parentuniqueid */ + } + slapi_pblock_get ( newpb, SLAPI_OPERATION, &op ); + operation_set_csn ( op, opcsn ); + + slapi_add_internal_pb ( newpb ); + slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result ); + slapi_pblock_destroy ( newpb ); + + return op_result; +} + +int +urp_fixup_rename_entry (Slapi_Entry *entry, const char *newrdn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + CSN *opcsn; + int op_result; + + newpb = slapi_pblock_new(); + + /* + * Must mark this operation as replicated, + * so that the frontend doesn't add extra attributes. + */ + slapi_rename_internal_set_pb ( + newpb, + slapi_entry_get_dn_const (entry), + newrdn, /*NewRDN*/ + NULL, /*NewSuperior*/ + 0, /* !Delete Old RDNS */ + NULL, /*Controls*/ + slapi_entry_get_uniqueid (entry), /*uniqueid*/ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + + /* set operation csn to the entry's dncsn */ + opcsn = (CSN *)entry_get_dncsn (entry); + slapi_pblock_get (newpb, SLAPI_OPERATION, &op); + operation_set_csn (op, opcsn); + + slapi_modrdn_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + + slapi_pblock_destroy(newpb); + return op_result; +} + +int +urp_fixup_delete_entry (const char *uniqueid, const char *dn, CSN *opcsn, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new (); + + /* + * Mark this operation as replicated, so that the front end + * doesn't add extra attributes. + */ + slapi_delete_internal_set_pb ( + newpb, + dn, + NULL, /*Controls*/ + uniqueid, /*uniqueid*/ + repl_get_plugin_identity ( PLUGIN_MULTIMASTER_REPLICATION ), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags ); + slapi_pblock_get ( newpb, SLAPI_OPERATION, &op ); + operation_set_csn ( op, opcsn ); + + slapi_delete_internal_pb ( newpb ); + slapi_pblock_get ( newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result ); + slapi_pblock_destroy ( newpb ); + + return op_result; +} + +int +urp_fixup_modify_entry (const char *uniqueid, const char *dn, CSN *opcsn, Slapi_Mods *smods, int opflags) +{ + Slapi_PBlock *newpb; + Slapi_Operation *op; + int op_result; + + newpb = slapi_pblock_new(); + + slapi_modify_internal_set_pb ( + newpb, + dn, + slapi_mods_get_ldapmods_byref (smods), + NULL, /* Controls */ + uniqueid, + repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), + OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP | opflags); + + /* set operation csn */ + slapi_pblock_get (newpb, SLAPI_OPERATION, &op); + operation_set_csn (op, opcsn); + + /* do modify */ + slapi_modify_internal_pb (newpb); + slapi_pblock_get (newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + slapi_pblock_destroy(newpb); + + return op_result; +} + +static int +urp_add_resolve_parententry (Slapi_PBlock *pb, char *sessionid, Slapi_Entry *entry, Slapi_Entry *parententry, CSN *opcsn) +{ + Slapi_DN *parentdn = NULL; + Slapi_RDN *add_rdn = NULL; + char *newdn = NULL; + int ldap_rc; + int rc = 0; + + if( is_suffix_entry (pb, entry, &parentdn) ) + { + /* It's OK for the suffix entry's parent to be absent */ + rc= 0; + PROFILE_POINT; /* Add Conflict; Suffix Entry */ + goto bailout; + } + + /* The entry is not a suffix. */ + if(parententry==NULL) /* The parent entry was not found. */ + { + /* Create a glue entry to stand in for the absent parent */ + slapi_operation_parameters *op_params; + slapi_pblock_get( pb, SLAPI_OPERATION_PARAMETERS, &op_params ); + ldap_rc = create_glue_entry (pb, sessionid, parentdn, op_params->p.p_add.parentuniqueid, opcsn); + if ( LDAP_SUCCESS == ldap_rc ) + { + /* The backend code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Glue Parent */ + } + else + { + /* + * Error. The parent can't be created as a glue entry. + * This will cause replication divergence and will + * require admin intercession + */ + ldap_rc= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc); + rc= -1; /* Abort this Operation */ + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Impossible to create parent; Refuse Change. */ + } + goto bailout; + } + + if(is_tombstone_entry(parententry)) /* The parent is a tombstone */ + { + /* The parent entry must be resurected from the dead. */ + ldap_rc = tombstone_to_glue (pb, sessionid, parententry, parentdn, REASON_RESURRECT_ENTRY, opcsn); + if ( ldap_rc != LDAP_SUCCESS ) + { + ldap_rc= LDAP_OPERATIONS_ERROR; + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &ldap_rc); + rc = -1; /* Abort the operation */ + } + else + { + /* The backend add code should now search for the parent again. */ + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_PARENT_ENTRY); + } + PROFILE_POINT; /* Add Conflict; Orphaned Entry; Parent Was Tombstone */ + goto bailout; + } + + /* The parent is healthy */ + /* Now we need to check that the parent has the correct DN */ + if (slapi_sdn_isparent(slapi_entry_get_sdn(parententry), slapi_entry_get_sdn(entry))) + { + rc= 0; /* OK, Add the entry */ + PROFILE_POINT; /* Add Conflict; Parent Exists */ + goto bailout; + } + + /* + * Parent entry doesn't have a DN parent to the entry. + * This can happen if parententry was renamed due to + * conflict and the child entry was created before + * replication occured. See defect 530942. + * We need to rename the entry to be child of its parent. + */ + add_rdn = slapi_rdn_new_dn(slapi_entry_get_dn_const (entry)); + newdn = slapi_dn_plus_rdn(slapi_entry_get_dn_const (parententry), slapi_rdn_get_rdn(add_rdn)); + slapi_entry_set_dn ( entry,slapi_ch_strdup(newdn)); + set_pblock_dn (pb,SLAPI_ADD_TARGET,newdn); /* consumes newdn */ + slapi_log_error ( slapi_log_urp, sessionid, + "Parent was renamed. Renamed the child to %s\n", newdn ); + rc= slapi_setbit_int(rc,SLAPI_RTN_BIT_FETCH_EXISTING_DN_ENTRY); + PROFILE_POINT; /* Add Conflict; Parent Renamed; Rename Operation Entry */ + +bailout: + if (parentdn) + slapi_sdn_free(&parentdn); + return rc; +} + +/* + * urp_annotate_dn: + * Returns 0 on failure + * Returns > 0 on success (1 on general conflict resolution success, LDAP_NO_SUCH_OBJECT on no-conflict success) + * + * Use this function to annotate an existing entry only. To annotate + * a new entry (the operation entry) see urp_add_operation. + */ +static int +urp_annotate_dn (char *sessionid, Slapi_Entry *entry, CSN *opcsn, const char *optype) +{ + int rc = 0; /* Fail */ + int op_result; + char *newrdn; + const char *uniqueid; + const char *basedn; + char ebuf[BUFSIZ]; + + uniqueid = slapi_entry_get_uniqueid (entry); + basedn = slapi_entry_get_ndn (entry); + newrdn = get_rdn_plus_uniqueid ( sessionid, basedn, uniqueid ); + if(newrdn!=NULL) + { + mod_namingconflict_attr (uniqueid, basedn, basedn, opcsn); + op_result = urp_fixup_rename_entry ( entry, newrdn, 0 ); + switch(op_result) + { + case LDAP_SUCCESS: + slapi_log_error(slapi_log_urp, sessionid, + "Naming conflict %s. Renamed existing entry to %s\n", + optype, escape_string (newrdn, ebuf)); + rc = 1; + break; + case LDAP_NO_SUCH_OBJECT: + /* This means that entry did not really exist!!! + * This is clearly indicating that there is a + * get_copy_of_entry -> dn2entry returned + * an entry (entry) that was already removed + * from the ldbm database... + * This is bad, because it clearly indicates + * some kind of db or cache corruption. We need to print + * this fact clearly in the errors log to try + * to solve this corruption one day. + * However, as far as the conflict is concerned, + * this error is completely harmless: + * if thew entry did not exist in the first place, + * there was never a room + * for a conflict!! After fix for 558293, this + * state can't be reproduced anymore (5-Oct-01) + */ + slapi_log_error( SLAPI_LOG_FATAL, sessionid, + "Entry %s exists in cache but not in DB\n", + escape_string (basedn, ebuf) ); + rc = LDAP_NO_SUCH_OBJECT; + break; + default: + slapi_log_error( slapi_log_urp, sessionid, + "Failed to annotate %s, err=%d\n", newrdn, op_result); + } + slapi_ch_free ( (void**)&newrdn ); + } + return rc; +} + +/* + * An URP Naming Collision helper function. Retreives a list of entries + * that have the given dn excluding the unique id of the entry. Any + * entries returned will be entries that have been added with the same + * dn, but caused a naming conflict when replicated. The URP to fix + * this constraint violation is to append the unique id of the entry + * to its RDN. + */ +static Slapi_Entry * +urp_get_min_naming_conflict_entry ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn ) +{ + Slapi_PBlock *newpb = NULL; + LDAPControl **server_ctrls = NULL; + Slapi_Entry **entries = NULL; + Slapi_Entry *min_naming_conflict_entry = NULL; + const CSN *min_csn = NULL; + char *filter = NULL; + char *parent_dn = NULL; + char *basedn; + int i = 0; + int min_i = -1; + int op_result = LDAP_SUCCESS; + + slapi_pblock_get (pb, SLAPI_URP_NAMING_COLLISION_DN, &basedn); + if (NULL == basedn || strncmp (basedn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) == 0) + return NULL; + + slapi_log_error ( SLAPI_LOG_REPL, sessionid, + "Enter urp_get_min_naming_conflict_entry for %s\n", basedn); + + filter = slapi_ch_malloc(50 + strlen(basedn)); + sprintf(filter, "(%s=%s %s)", ATTR_NSDS5_REPLCONFLICT, REASON_ANNOTATE_DN, basedn); + + /* server_ctrls will be freed when newpb is destroyed */ + server_ctrls = (LDAPControl **)slapi_ch_calloc (2, sizeof (LDAPControl *)); + server_ctrls[0] = create_managedsait_control(); + server_ctrls[1] = NULL; + + newpb = slapi_pblock_new(); + parent_dn = slapi_dn_parent (basedn); + slapi_search_internal_set_pb(newpb, + parent_dn, /* Base DN */ + LDAP_SCOPE_ONELEVEL, + filter, + NULL, /* Attrs */ + 0, /* AttrOnly */ + server_ctrls, /* Controls */ + NULL, /* UniqueID */ + repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), + 0); + slapi_search_internal_pb(newpb); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_RESULT, &op_result); + slapi_pblock_get(newpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if ( (op_result != LDAP_SUCCESS) || (entries == NULL) ) + { + /* Log a message */ + goto done; + } + /* For all entries, get the one with the smallest dn csn */ + for (i = 0; NULL != entries[i]; i++) + { + const CSN *dncsn; + dncsn = entry_get_dncsn(entries[i]); + if ((dncsn != opcsn) && + ((min_csn == NULL) || (csn_compare(dncsn, min_csn) < 0)) && + !is_tombstone_entry (entries[i])) + { + min_csn = dncsn; + min_i = i; + } + /* + * If there are too many conflicts, the current urp code has no + * guarantee for all servers to converge anyway, because the + * urp and the backend can't be done in one transaction due + * to either performance or the deadlock problem. + * Don't sacrifice the performance too much for impossible. + */ + if (min_csn && i > 5) + { + break; + } + } + + if (min_csn != NULL) { + /* Found one entry */ + min_naming_conflict_entry = slapi_entry_dup(entries[min_i]); + } + +done: + slapi_ch_free((void **)&parent_dn); + slapi_ch_free((void **)&filter); + slapi_free_search_results_internal(newpb); + slapi_pblock_destroy(newpb); + newpb = NULL; + + slapi_log_error ( SLAPI_LOG_REPL, sessionid, + "Leave urp_get_min_naming_conflict_entry (found %d entries)\n", i); + + return min_naming_conflict_entry; +} + +/* + * If an entry is deleted or renamed, a new winner may be + * chosen from its naming competitors. + * The entry with the smallest dncsn restores its original DN. + */ +static int +urp_naming_conflict_removal ( Slapi_PBlock *pb, char *sessionid, CSN *opcsn, const char *optype ) +{ + Slapi_Entry *min_naming_conflict_entry; + Slapi_RDN *oldrdn, *newrdn; + const char *oldrdnstr, *newrdnstr; + int op_result; + + /* + * Backend op has set SLAPI_URP_NAMING_COLLISION_DN to the basedn. + */ + min_naming_conflict_entry = urp_get_min_naming_conflict_entry (pb, sessionid, opcsn); + if (min_naming_conflict_entry == NULL) + { + return 0; + } + + /* Step 1: Restore the entry's original DN */ + + oldrdn = slapi_rdn_new_sdn ( slapi_entry_get_sdn (min_naming_conflict_entry) ); + oldrdnstr = slapi_rdn_get_rdn ( oldrdn ); + + /* newrdnstr is the old rdn of the entry minus the nsuniqueid part */ + newrdn = slapi_rdn_new_rdn ( oldrdn ); + slapi_rdn_remove_attr (newrdn, SLAPI_ATTR_UNIQUEID ); + newrdnstr = slapi_rdn_get_rdn ( newrdn ); + + /* + * Set OP_FLAG_ACTION_INVOKE_FOR_REPLOP since this operation + * is done after DB lock was released. The backend modrdn + * will acquire the DB lock if it sees this flag. + */ + op_result = urp_fixup_rename_entry (min_naming_conflict_entry, newrdnstr, OP_FLAG_ACTION_INVOKE_FOR_REPLOP); + if ( op_result != LDAP_SUCCESS ) + { + slapi_log_error (slapi_log_urp, sessionid, + "Failed to restore RDN of %s, err=%d\n", oldrdnstr, op_result); + goto bailout; + } + slapi_log_error (slapi_log_urp, sessionid, + "Naming conflict removed by %s. RDN of %s was restored\n", optype, oldrdnstr); + + /* Step2: Remove ATTR_NSDS5_REPLCONFLICT from the winning entry */ + /* + * A fixup op will not invoke urp_modrdn_operation(). Even it does, + * urp_modrdn_operation() will do nothing because of the same CSN. + */ + op_result = del_replconflict_attr (min_naming_conflict_entry, opcsn, OP_FLAG_ACTION_INVOKE_FOR_REPLOP); + if (op_result != LDAP_SUCCESS) { + slapi_log_error(SLAPI_LOG_REPL, sessionid, + "Failed to remove nsds5ReplConflict for %s, err=%d\n", + newrdnstr, op_result); + } + +bailout: + slapi_entry_free (min_naming_conflict_entry); + slapi_rdn_free(&oldrdn); + slapi_rdn_free(&newrdn); + return op_result; +} + +/* The returned value is either null or "uniqueid=<uniqueid>+<basedn>" */ +static char * +get_dn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid) +{ + Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn); + Slapi_RDN *rdn= slapi_rdn_new(); + char *newdn; + + PR_ASSERT(uniqueid!=NULL); + + /* Check if the RDN already contains the Unique ID */ + slapi_sdn_get_rdn(sdn,rdn); + if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid))) + { + /* The Unique ID is already in the RDN. + * This is a highly improbable collision. + * It suggests that a duplicate UUID was generated. + * This will cause replication divergence and will + * require admin intercession + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Annotated DN %s has naming conflict\n", olddn ); + newdn= NULL; + } + else + { + slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid); + slapi_sdn_set_rdn(sdn, rdn); + newdn= slapi_ch_strdup(slapi_sdn_get_dn(sdn)); + } + slapi_sdn_free(&sdn); + slapi_rdn_free(&rdn); + return newdn; +} + +static char * +get_rdn_plus_uniqueid(char *sessionid, const char *olddn, const char *uniqueid) +{ + char *newrdn; + /* Check if the RDN already contains the Unique ID */ + Slapi_DN *sdn= slapi_sdn_new_dn_byval(olddn); + Slapi_RDN *rdn= slapi_rdn_new(); + slapi_sdn_get_rdn(sdn,rdn); + PR_ASSERT(uniqueid!=NULL); + if(slapi_rdn_contains(rdn,SLAPI_ATTR_UNIQUEID,uniqueid,strlen(uniqueid))) + { + /* The Unique ID is already in the RDN. + * This is a highly improbable collision. + * It suggests that a duplicate UUID was generated. + * This will cause replication divergence and will + * require admin intercession + */ + slapi_log_error(SLAPI_LOG_FATAL, sessionid, + "Annotated DN %s has naming conflict\n", olddn ); + newrdn= NULL; + } + else + { + slapi_rdn_add(rdn,SLAPI_ATTR_UNIQUEID,uniqueid); + newrdn= slapi_ch_strdup(slapi_rdn_get_rdn(rdn)); + } + slapi_sdn_free(&sdn); + slapi_rdn_free(&rdn); + return newrdn; +} + +static void +set_pblock_dn (Slapi_PBlock* pb,int pblock_parameter,char *newdn) +{ + char *olddn; + slapi_pblock_get( pb, pblock_parameter, &olddn ); + slapi_ch_free((void**)&olddn); + slapi_pblock_set( pb, pblock_parameter, newdn ); +} + +static int +is_suffix_entry ( Slapi_PBlock *pb, Slapi_Entry *entry, Slapi_DN **parentdn ) +{ + return is_suffix_dn ( pb, slapi_entry_get_sdn(entry), parentdn ); +} + +int +is_suffix_dn ( Slapi_PBlock *pb, const Slapi_DN *dn, Slapi_DN **parentdn ) +{ + Slapi_Backend *backend; + int rc; + + *parentdn = slapi_sdn_new(); + slapi_pblock_get( pb, SLAPI_BACKEND, &backend ); + slapi_sdn_get_backend_parent (dn, *parentdn, backend); + + /* A suffix entry doesn't have parent dn */ + rc = slapi_sdn_isempty (*parentdn) ? 1 : 0; + + return rc; +} + +static int +mod_namingconflict_attr (const char *uniqueid, const char *entrydn, const char *conflictdn, CSN *opcsn) +{ + Slapi_Mods smods; + char buf[BUFSIZ]; + int op_result; + + sprintf (buf, "%s %s", REASON_ANNOTATE_DN, conflictdn); + slapi_mods_init (&smods, 2); + if ( strncmp (entrydn, SLAPI_ATTR_UNIQUEID, strlen(SLAPI_ATTR_UNIQUEID)) != 0 ) + { + slapi_mods_add (&smods, LDAP_MOD_ADD, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf); + } + else + { + /* + * If the existing entry is already a naming conflict loser, + * the following replace operation should result in the + * replace of the ATTR_NSDS5_REPLCONFLICT index as well. + */ + slapi_mods_add (&smods, LDAP_MOD_REPLACE, ATTR_NSDS5_REPLCONFLICT, strlen(buf), buf); + } + op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, 0); + slapi_mods_done (&smods); + return op_result; +} + +static int +del_replconflict_attr (Slapi_Entry *entry, CSN *opcsn, int opflags) +{ + Slapi_Attr *attr; + int op_result = 0; + + if (slapi_entry_attr_find (entry, ATTR_NSDS5_REPLCONFLICT, &attr) == 0) + { + Slapi_Mods smods; + const char *uniqueid; + const char *entrydn; + + uniqueid = slapi_entry_get_uniqueid (entry); + entrydn = slapi_entry_get_dn_const (entry); + slapi_mods_init (&smods, 2); + slapi_mods_add (&smods, LDAP_MOD_DELETE, ATTR_NSDS5_REPLCONFLICT, 0, NULL); + op_result = urp_fixup_modify_entry (uniqueid, entrydn, opcsn, &smods, opflags); + slapi_mods_done (&smods); + } + return op_result; +} |